File Coverage

deps/libgit2/src/diff.c
Criterion Covered Total %
statement 153 220 69.5
branch 82 158 51.9
condition n/a
subroutine n/a
pod n/a
total 235 378 62.1


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