1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "diff.h"
9
10 #include "git2/version.h"
11 #include "diff_generate.h"
12 #include "patch.h"
13 #include "commit.h"
14 #include "index.h"
15
16 struct patch_id_args {
17 git_hash_ctx ctx;
18 git_oid result;
19 int first_file;
20 };
21
diff_delta__path(const git_diff_delta * delta)22 GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
23 {
24 const char *str = delta->old_file.path;
25
26 if (!str ||
27 delta->status == GIT_DELTA_ADDED ||
28 delta->status == GIT_DELTA_RENAMED ||
29 delta->status == GIT_DELTA_COPIED)
30 str = delta->new_file.path;
31
32 return str;
33 }
34
git_diff_delta__cmp(const void * a,const void * b)35 int git_diff_delta__cmp(const void *a, const void *b)
36 {
37 const git_diff_delta *da = a, *db = b;
38 int val = strcmp(diff_delta__path(da), diff_delta__path(db));
39 return val ? val : ((int)da->status - (int)db->status);
40 }
41
git_diff_delta__casecmp(const void * a,const void * b)42 int git_diff_delta__casecmp(const void *a, const void *b)
43 {
44 const git_diff_delta *da = a, *db = b;
45 int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
46 return val ? val : ((int)da->status - (int)db->status);
47 }
48
git_diff__entry_cmp(const void * a,const void * b)49 int git_diff__entry_cmp(const void *a, const void *b)
50 {
51 const git_index_entry *entry_a = a;
52 const git_index_entry *entry_b = b;
53
54 return strcmp(entry_a->path, entry_b->path);
55 }
56
git_diff__entry_icmp(const void * a,const void * b)57 int git_diff__entry_icmp(const void *a, const void *b)
58 {
59 const git_index_entry *entry_a = a;
60 const git_index_entry *entry_b = b;
61
62 return strcasecmp(entry_a->path, entry_b->path);
63 }
64
git_diff_free(git_diff * diff)65 void git_diff_free(git_diff *diff)
66 {
67 if (!diff)
68 return;
69
70 GIT_REFCOUNT_DEC(diff, diff->free_fn);
71 }
72
git_diff_addref(git_diff * diff)73 void git_diff_addref(git_diff *diff)
74 {
75 GIT_REFCOUNT_INC(diff);
76 }
77
git_diff_num_deltas(const git_diff * diff)78 size_t git_diff_num_deltas(const git_diff *diff)
79 {
80 GIT_ASSERT_ARG(diff);
81 return diff->deltas.length;
82 }
83
git_diff_num_deltas_of_type(const git_diff * diff,git_delta_t type)84 size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
85 {
86 size_t i, count = 0;
87 const git_diff_delta *delta;
88
89 GIT_ASSERT_ARG(diff);
90
91 git_vector_foreach(&diff->deltas, i, delta) {
92 count += (delta->status == type);
93 }
94
95 return count;
96 }
97
git_diff_get_delta(const git_diff * diff,size_t idx)98 const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
99 {
100 GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL);
101 return git_vector_get(&diff->deltas, idx);
102 }
103
git_diff_is_sorted_icase(const git_diff * diff)104 int git_diff_is_sorted_icase(const git_diff *diff)
105 {
106 return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
107 }
108
git_diff_get_perfdata(git_diff_perfdata * out,const git_diff * diff)109 int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
110 {
111 GIT_ASSERT_ARG(out);
112 GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
113 out->stat_calls = diff->perf.stat_calls;
114 out->oid_calculations = diff->perf.oid_calculations;
115 return 0;
116 }
117
git_diff_foreach(git_diff * diff,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)118 int git_diff_foreach(
119 git_diff *diff,
120 git_diff_file_cb file_cb,
121 git_diff_binary_cb binary_cb,
122 git_diff_hunk_cb hunk_cb,
123 git_diff_line_cb data_cb,
124 void *payload)
125 {
126 int error = 0;
127 git_diff_delta *delta;
128 size_t idx;
129
130 GIT_ASSERT_ARG(diff);
131
132 git_vector_foreach(&diff->deltas, idx, delta) {
133 git_patch *patch;
134
135 /* check flags against patch status */
136 if (git_diff_delta__should_skip(&diff->opts, delta))
137 continue;
138
139 if ((error = git_patch_from_diff(&patch, diff, idx)) != 0)
140 break;
141
142 error = git_patch__invoke_callbacks(patch, file_cb, binary_cb,
143 hunk_cb, data_cb, payload);
144 git_patch_free(patch);
145
146 if (error)
147 break;
148 }
149
150 return error;
151 }
152
diff_format_email_append_header_tobuf(git_buf * out,const git_oid * id,const git_signature * author,const char * summary,const char * body,size_t patch_no,size_t total_patches,bool exclude_patchno_marker)153 static int diff_format_email_append_header_tobuf(
154 git_buf *out,
155 const git_oid *id,
156 const git_signature *author,
157 const char *summary,
158 const char *body,
159 size_t patch_no,
160 size_t total_patches,
161 bool exclude_patchno_marker)
162 {
163 char idstr[GIT_OID_HEXSZ + 1];
164 char date_str[GIT_DATE_RFC2822_SZ];
165 int error = 0;
166
167 git_oid_fmt(idstr, id);
168 idstr[GIT_OID_HEXSZ] = '\0';
169
170 if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
171 &author->when)) < 0)
172 return error;
173
174 error = git_buf_printf(out,
175 "From %s Mon Sep 17 00:00:00 2001\n" \
176 "From: %s <%s>\n" \
177 "Date: %s\n" \
178 "Subject: ",
179 idstr,
180 author->name, author->email,
181 date_str);
182
183 if (error < 0)
184 return error;
185
186 if (!exclude_patchno_marker) {
187 if (total_patches == 1) {
188 error = git_buf_puts(out, "[PATCH] ");
189 } else {
190 error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
191 patch_no, total_patches);
192 }
193
194 if (error < 0)
195 return error;
196 }
197
198 error = git_buf_printf(out, "%s\n\n", summary);
199
200 if (body) {
201 git_buf_puts(out, body);
202
203 if (out->ptr[out->size - 1] != '\n')
204 git_buf_putc(out, '\n');
205 }
206
207 return error;
208 }
209
diff_format_email_append_patches_tobuf(git_buf * out,git_diff * diff)210 static int diff_format_email_append_patches_tobuf(
211 git_buf *out,
212 git_diff *diff)
213 {
214 size_t i, deltas;
215 int error = 0;
216
217 deltas = git_diff_num_deltas(diff);
218
219 for (i = 0; i < deltas; ++i) {
220 git_patch *patch = NULL;
221
222 if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
223 error = git_patch_to_buf(out, patch);
224
225 git_patch_free(patch);
226
227 if (error < 0)
228 break;
229 }
230
231 return error;
232 }
233
git_diff_format_email(git_buf * out,git_diff * diff,const git_diff_format_email_options * opts)234 int git_diff_format_email(
235 git_buf *out,
236 git_diff *diff,
237 const git_diff_format_email_options *opts)
238 {
239 git_diff_stats *stats = NULL;
240 char *summary = NULL, *loc = NULL;
241 bool ignore_marker;
242 unsigned int format_flags = 0;
243 size_t allocsize;
244 int error;
245
246 GIT_ASSERT_ARG(out);
247 GIT_ASSERT_ARG(diff);
248 GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author);
249
250 GIT_ERROR_CHECK_VERSION(opts,
251 GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
252 "git_format_email_options");
253
254 ignore_marker = (opts->flags &
255 GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
256
257 if (!ignore_marker) {
258 if (opts->patch_no > opts->total_patches) {
259 git_error_set(GIT_ERROR_INVALID,
260 "patch %"PRIuZ" out of range. max %"PRIuZ,
261 opts->patch_no, opts->total_patches);
262 return -1;
263 }
264
265 if (opts->patch_no == 0) {
266 git_error_set(GIT_ERROR_INVALID,
267 "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
268 return -1;
269 }
270 }
271
272 /* the summary we receive may not be clean.
273 * it could potentially contain new line characters
274 * or not be set, sanitize, */
275 if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
276 size_t offset = 0;
277
278 if ((offset = (loc - opts->summary)) == 0) {
279 git_error_set(GIT_ERROR_INVALID, "summary is empty");
280 error = -1;
281 goto on_error;
282 }
283
284 GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
285 summary = git__calloc(allocsize, sizeof(char));
286 GIT_ERROR_CHECK_ALLOC(summary);
287
288 strncpy(summary, opts->summary, offset);
289 }
290
291 error = diff_format_email_append_header_tobuf(out,
292 opts->id, opts->author, summary == NULL ? opts->summary : summary,
293 opts->body, opts->patch_no, opts->total_patches, ignore_marker);
294
295 if (error < 0)
296 goto on_error;
297
298 format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
299
300 if ((error = git_buf_puts(out, "---\n")) < 0 ||
301 (error = git_diff_get_stats(&stats, diff)) < 0 ||
302 (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
303 (error = git_buf_putc(out, '\n')) < 0 ||
304 (error = diff_format_email_append_patches_tobuf(out, diff)) < 0)
305 goto on_error;
306
307 error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
308
309 on_error:
310 git__free(summary);
311 git_diff_stats_free(stats);
312
313 return error;
314 }
315
git_diff_commit_as_email(git_buf * out,git_repository * repo,git_commit * commit,size_t patch_no,size_t total_patches,uint32_t flags,const git_diff_options * diff_opts)316 int git_diff_commit_as_email(
317 git_buf *out,
318 git_repository *repo,
319 git_commit *commit,
320 size_t patch_no,
321 size_t total_patches,
322 uint32_t flags,
323 const git_diff_options *diff_opts)
324 {
325 git_diff *diff = NULL;
326 git_diff_format_email_options opts =
327 GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
328 int error;
329
330 GIT_ASSERT_ARG(out);
331 GIT_ASSERT_ARG(repo);
332 GIT_ASSERT_ARG(commit);
333
334 opts.flags = flags;
335 opts.patch_no = patch_no;
336 opts.total_patches = total_patches;
337 opts.id = git_commit_id(commit);
338 opts.summary = git_commit_summary(commit);
339 opts.body = git_commit_body(commit);
340 opts.author = git_commit_author(commit);
341
342 if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
343 return error;
344
345 error = git_diff_format_email(out, diff, &opts);
346
347 git_diff_free(diff);
348 return error;
349 }
350
git_diff_options_init(git_diff_options * opts,unsigned int version)351 int git_diff_options_init(git_diff_options *opts, unsigned int version)
352 {
353 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
354 opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
355 return 0;
356 }
357
358 #ifndef GIT_DEPRECATE_HARD
git_diff_init_options(git_diff_options * opts,unsigned int version)359 int git_diff_init_options(git_diff_options *opts, unsigned int version)
360 {
361 return git_diff_options_init(opts, version);
362 }
363 #endif
364
git_diff_find_options_init(git_diff_find_options * opts,unsigned int version)365 int git_diff_find_options_init(
366 git_diff_find_options *opts, unsigned int version)
367 {
368 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
369 opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
370 return 0;
371 }
372
373 #ifndef GIT_DEPRECATE_HARD
git_diff_find_init_options(git_diff_find_options * opts,unsigned int version)374 int git_diff_find_init_options(
375 git_diff_find_options *opts, unsigned int version)
376 {
377 return git_diff_find_options_init(opts, version);
378 }
379 #endif
380
git_diff_format_email_options_init(git_diff_format_email_options * opts,unsigned int version)381 int git_diff_format_email_options_init(
382 git_diff_format_email_options *opts, unsigned int version)
383 {
384 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
385 opts, version, git_diff_format_email_options,
386 GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
387 return 0;
388 }
389
390 #ifndef GIT_DEPRECATE_HARD
git_diff_format_email_init_options(git_diff_format_email_options * opts,unsigned int version)391 int git_diff_format_email_init_options(
392 git_diff_format_email_options *opts, unsigned int version)
393 {
394 return git_diff_format_email_options_init(opts, version);
395 }
396 #endif
397
flush_hunk(git_oid * result,git_hash_ctx * ctx)398 static int flush_hunk(git_oid *result, git_hash_ctx *ctx)
399 {
400 git_oid hash;
401 unsigned short carry = 0;
402 int error, i;
403
404 if ((error = git_hash_final(&hash, ctx)) < 0 ||
405 (error = git_hash_init(ctx)) < 0)
406 return error;
407
408 for (i = 0; i < GIT_OID_RAWSZ; i++) {
409 carry += result->id[i] + hash.id[i];
410 result->id[i] = (unsigned char)carry;
411 carry >>= 8;
412 }
413
414 return 0;
415 }
416
strip_spaces(git_buf * buf)417 static void strip_spaces(git_buf *buf)
418 {
419 char *src = buf->ptr, *dst = buf->ptr;
420 char c;
421 size_t len = 0;
422
423 while ((c = *src++) != '\0') {
424 if (!git__isspace(c)) {
425 *dst++ = c;
426 len++;
427 }
428 }
429
430 git_buf_truncate(buf, len);
431 }
432
diff_patchid_print_callback_to_buf(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)433 static int diff_patchid_print_callback_to_buf(
434 const git_diff_delta *delta,
435 const git_diff_hunk *hunk,
436 const git_diff_line *line,
437 void *payload)
438 {
439 struct patch_id_args *args = (struct patch_id_args *) payload;
440 git_buf buf = GIT_BUF_INIT;
441 int error = 0;
442
443 if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL ||
444 line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
445 line->origin == GIT_DIFF_LINE_DEL_EOFNL)
446 goto out;
447
448 if ((error = git_diff_print_callback__to_buf(delta, hunk,
449 line, &buf)) < 0)
450 goto out;
451
452 strip_spaces(&buf);
453
454 if (line->origin == GIT_DIFF_LINE_FILE_HDR &&
455 !args->first_file &&
456 (error = flush_hunk(&args->result, &args->ctx) < 0))
457 goto out;
458
459 if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0)
460 goto out;
461
462 if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file)
463 args->first_file = 0;
464
465 out:
466 git_buf_dispose(&buf);
467 return error;
468 }
469
git_diff_patchid_options_init(git_diff_patchid_options * opts,unsigned int version)470 int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version)
471 {
472 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
473 opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT);
474 return 0;
475 }
476
git_diff_patchid(git_oid * out,git_diff * diff,git_diff_patchid_options * opts)477 int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts)
478 {
479 struct patch_id_args args;
480 int error;
481
482 GIT_ERROR_CHECK_VERSION(
483 opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options");
484
485 memset(&args, 0, sizeof(args));
486 args.first_file = 1;
487 if ((error = git_hash_ctx_init(&args.ctx)) < 0)
488 goto out;
489
490 if ((error = git_diff_print(diff,
491 GIT_DIFF_FORMAT_PATCH_ID,
492 diff_patchid_print_callback_to_buf,
493 &args)) < 0)
494 goto out;
495
496 if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)
497 goto out;
498
499 git_oid_cpy(out, &args.result);
500
501 out:
502 git_hash_ctx_cleanup(&args.ctx);
503 return error;
504 }
505