File Coverage

deps/libgit2/src/diff.c
Criterion Covered Total %
statement 153 218 70.1
branch 82 158 51.9
condition n/a
subroutine n/a
pod n/a
total 235 376 62.5


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