2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
10 #include "git2/version.h"
11 #include "diff_generate.h"
16 struct patch_id_args
{
22 GIT_INLINE(const char *) diff_delta__path(const git_diff_delta
*delta
)
24 const char *str
= delta
->old_file
.path
;
27 delta
->status
== GIT_DELTA_ADDED
||
28 delta
->status
== GIT_DELTA_RENAMED
||
29 delta
->status
== GIT_DELTA_COPIED
)
30 str
= delta
->new_file
.path
;
35 const char *git_diff_delta__path(const git_diff_delta
*delta
)
37 return diff_delta__path(delta
);
40 int git_diff_delta__cmp(const void *a
, const void *b
)
42 const git_diff_delta
*da
= a
, *db
= b
;
43 int val
= strcmp(diff_delta__path(da
), diff_delta__path(db
));
44 return val
? val
: ((int)da
->status
- (int)db
->status
);
47 int git_diff_delta__casecmp(const void *a
, const void *b
)
49 const git_diff_delta
*da
= a
, *db
= b
;
50 int val
= strcasecmp(diff_delta__path(da
), diff_delta__path(db
));
51 return val
? val
: ((int)da
->status
- (int)db
->status
);
54 int git_diff__entry_cmp(const void *a
, const void *b
)
56 const git_index_entry
*entry_a
= a
;
57 const git_index_entry
*entry_b
= b
;
59 return strcmp(entry_a
->path
, entry_b
->path
);
62 int git_diff__entry_icmp(const void *a
, const void *b
)
64 const git_index_entry
*entry_a
= a
;
65 const git_index_entry
*entry_b
= b
;
67 return strcasecmp(entry_a
->path
, entry_b
->path
);
70 void git_diff_free(git_diff
*diff
)
75 GIT_REFCOUNT_DEC(diff
, diff
->free_fn
);
78 void git_diff_addref(git_diff
*diff
)
80 GIT_REFCOUNT_INC(diff
);
83 size_t git_diff_num_deltas(const git_diff
*diff
)
86 return diff
->deltas
.length
;
89 size_t git_diff_num_deltas_of_type(const git_diff
*diff
, git_delta_t type
)
92 const git_diff_delta
*delta
;
96 git_vector_foreach(&diff
->deltas
, i
, delta
) {
97 count
+= (delta
->status
== type
);
103 const git_diff_delta
*git_diff_get_delta(const git_diff
*diff
, size_t idx
)
106 return git_vector_get(&diff
->deltas
, idx
);
109 int git_diff_is_sorted_icase(const git_diff
*diff
)
111 return (diff
->opts
.flags
& GIT_DIFF_IGNORE_CASE
) != 0;
114 int git_diff_get_perfdata(git_diff_perfdata
*out
, const git_diff
*diff
)
117 GIT_ERROR_CHECK_VERSION(out
, GIT_DIFF_PERFDATA_VERSION
, "git_diff_perfdata");
118 out
->stat_calls
= diff
->perf
.stat_calls
;
119 out
->oid_calculations
= diff
->perf
.oid_calculations
;
123 int git_diff_foreach(
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
,
132 git_diff_delta
*delta
;
137 git_vector_foreach(&diff
->deltas
, idx
, delta
) {
140 /* check flags against patch status */
141 if (git_diff_delta__should_skip(&diff
->opts
, delta
))
144 if ((error
= git_patch_from_diff(&patch
, diff
, idx
)) != 0)
147 error
= git_patch__invoke_callbacks(patch
, file_cb
, binary_cb
,
148 hunk_cb
, data_cb
, payload
);
149 git_patch_free(patch
);
158 int git_diff_format_email__append_header_tobuf(
161 const git_signature
*author
,
165 size_t total_patches
,
166 bool exclude_patchno_marker
)
168 char idstr
[GIT_OID_HEXSZ
+ 1];
169 char date_str
[GIT_DATE_RFC2822_SZ
];
172 git_oid_fmt(idstr
, id
);
173 idstr
[GIT_OID_HEXSZ
] = '\0';
175 if ((error
= git__date_rfc2822_fmt(date_str
, sizeof(date_str
),
179 error
= git_buf_printf(out
,
180 "From %s Mon Sep 17 00:00:00 2001\n" \
185 author
->name
, author
->email
,
191 if (!exclude_patchno_marker
) {
192 if (total_patches
== 1) {
193 error
= git_buf_puts(out
, "[PATCH] ");
195 error
= git_buf_printf(out
, "[PATCH %"PRIuZ
"/%"PRIuZ
"] ",
196 patch_no
, total_patches
);
203 error
= git_buf_printf(out
, "%s\n\n", summary
);
206 git_buf_puts(out
, body
);
208 if (out
->ptr
[out
->size
- 1] != '\n')
209 git_buf_putc(out
, '\n');
215 int git_diff_format_email__append_patches_tobuf(
222 deltas
= git_diff_num_deltas(diff
);
224 for (i
= 0; i
< deltas
; ++i
) {
225 git_patch
*patch
= NULL
;
227 if ((error
= git_patch_from_diff(&patch
, diff
, i
)) >= 0)
228 error
= git_patch_to_buf(out
, patch
);
230 git_patch_free(patch
);
239 int git_diff_format_email(
242 const git_diff_format_email_options
*opts
)
244 git_diff_stats
*stats
= NULL
;
245 char *summary
= NULL
, *loc
= NULL
;
247 unsigned int format_flags
= 0;
251 assert(out
&& diff
&& opts
);
252 assert(opts
->summary
&& opts
->id
&& opts
->author
);
254 GIT_ERROR_CHECK_VERSION(opts
,
255 GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION
,
256 "git_format_email_options");
258 ignore_marker
= (opts
->flags
&
259 GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER
) != 0;
261 if (!ignore_marker
) {
262 if (opts
->patch_no
> opts
->total_patches
) {
263 git_error_set(GIT_ERROR_INVALID
,
264 "patch %"PRIuZ
" out of range. max %"PRIuZ
,
265 opts
->patch_no
, opts
->total_patches
);
269 if (opts
->patch_no
== 0) {
270 git_error_set(GIT_ERROR_INVALID
,
271 "invalid patch no %"PRIuZ
". should be >0", opts
->patch_no
);
276 /* the summary we receive may not be clean.
277 * it could potentially contain new line characters
278 * or not be set, sanitize, */
279 if ((loc
= strpbrk(opts
->summary
, "\r\n")) != NULL
) {
282 if ((offset
= (loc
- opts
->summary
)) == 0) {
283 git_error_set(GIT_ERROR_INVALID
, "summary is empty");
288 GIT_ERROR_CHECK_ALLOC_ADD(&allocsize
, offset
, 1);
289 summary
= git__calloc(allocsize
, sizeof(char));
290 GIT_ERROR_CHECK_ALLOC(summary
);
292 strncpy(summary
, opts
->summary
, offset
);
295 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
);
302 format_flags
= GIT_DIFF_STATS_FULL
| GIT_DIFF_STATS_INCLUDE_SUMMARY
;
304 if ((error
= git_buf_puts(out
, "---\n")) < 0 ||
305 (error
= git_diff_get_stats(&stats
, diff
)) < 0 ||
306 (error
= git_diff_stats_to_buf(out
, stats
, format_flags
, 0)) < 0 ||
307 (error
= git_buf_putc(out
, '\n')) < 0 ||
308 (error
= git_diff_format_email__append_patches_tobuf(out
, diff
)) < 0)
311 error
= git_buf_puts(out
, "--\nlibgit2 " LIBGIT2_VERSION
"\n\n");
315 git_diff_stats_free(stats
);
320 int git_diff_commit_as_email(
322 git_repository
*repo
,
325 size_t total_patches
,
326 git_diff_format_email_flags_t flags
,
327 const git_diff_options
*diff_opts
)
329 git_diff
*diff
= NULL
;
330 git_diff_format_email_options opts
=
331 GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT
;
334 assert (out
&& repo
&& commit
);
337 opts
.patch_no
= patch_no
;
338 opts
.total_patches
= total_patches
;
339 opts
.id
= git_commit_id(commit
);
340 opts
.summary
= git_commit_summary(commit
);
341 opts
.body
= git_commit_body(commit
);
342 opts
.author
= git_commit_author(commit
);
344 if ((error
= git_diff__commit(&diff
, repo
, commit
, diff_opts
)) < 0)
347 error
= git_diff_format_email(out
, diff
, &opts
);
353 int git_diff_init_options(git_diff_options
*opts
, unsigned int version
)
355 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
356 opts
, version
, git_diff_options
, GIT_DIFF_OPTIONS_INIT
);
360 int git_diff_find_init_options(
361 git_diff_find_options
*opts
, unsigned int version
)
363 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
364 opts
, version
, git_diff_find_options
, GIT_DIFF_FIND_OPTIONS_INIT
);
368 int git_diff_format_email_init_options(
369 git_diff_format_email_options
*opts
, unsigned int version
)
371 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
372 opts
, version
, git_diff_format_email_options
,
373 GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT
);
377 static int flush_hunk(git_oid
*result
, git_hash_ctx
*ctx
)
380 unsigned short carry
= 0;
383 if ((error
= git_hash_final(&hash
, ctx
)) < 0 ||
384 (error
= git_hash_init(ctx
)) < 0)
387 for (i
= 0; i
< GIT_OID_RAWSZ
; i
++) {
388 carry
+= result
->id
[i
] + hash
.id
[i
];
389 result
->id
[i
] = (unsigned char)carry
;
396 static void strip_spaces(git_buf
*buf
)
398 char *src
= buf
->ptr
, *dst
= buf
->ptr
;
402 while ((c
= *src
++) != '\0') {
403 if (!git__isspace(c
)) {
409 git_buf_truncate(buf
, len
);
413 const git_diff_delta
*delta
,
417 struct patch_id_args
*args
= (struct patch_id_args
*) payload
;
418 git_buf buf
= GIT_BUF_INIT
;
421 GIT_UNUSED(progress
);
423 if (!args
->first_file
&&
424 (error
= flush_hunk(&args
->result
, &args
->ctx
)) < 0)
426 args
->first_file
= 0;
428 if ((error
= git_buf_printf(&buf
,
429 "diff--gita/%sb/%s---a/%s+++b/%s",
430 delta
->old_file
.path
,
431 delta
->new_file
.path
,
432 delta
->old_file
.path
,
433 delta
->new_file
.path
)) < 0)
438 if ((error
= git_hash_update(&args
->ctx
, buf
.ptr
, buf
.size
)) < 0)
442 git_buf_dispose(&buf
);
447 const git_diff_delta
*delta
,
448 const git_diff_hunk
*hunk
,
449 const git_diff_line
*line
,
452 struct patch_id_args
*args
= (struct patch_id_args
*) payload
;
453 git_buf buf
= GIT_BUF_INIT
;
459 switch (line
->origin
) {
460 case GIT_DIFF_LINE_ADDITION
:
461 git_buf_putc(&buf
, '+');
463 case GIT_DIFF_LINE_DELETION
:
464 git_buf_putc(&buf
, '-');
466 case GIT_DIFF_LINE_CONTEXT
:
469 git_error_set(GIT_ERROR_PATCH
, "invalid line origin for patch");
473 git_buf_put(&buf
, line
->content
, line
->content_len
);
476 if ((error
= git_hash_update(&args
->ctx
, buf
.ptr
, buf
.size
)) < 0)
480 git_buf_dispose(&buf
);
484 int git_diff_patchid_init_options(git_diff_patchid_options
*opts
, unsigned int version
)
486 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
487 opts
, version
, git_diff_patchid_options
, GIT_DIFF_PATCHID_OPTIONS_INIT
);
491 int git_diff_patchid(git_oid
*out
, git_diff
*diff
, git_diff_patchid_options
*opts
)
493 struct patch_id_args args
;
496 GIT_ERROR_CHECK_VERSION(
497 opts
, GIT_DIFF_PATCHID_OPTIONS_VERSION
, "git_diff_patchid_options");
499 memset(&args
, 0, sizeof(args
));
501 if ((error
= git_hash_ctx_init(&args
.ctx
)) < 0)
504 if ((error
= git_diff_foreach(diff
, file_cb
, NULL
, NULL
, line_cb
, &args
)) < 0)
507 if ((error
= (flush_hunk(&args
.result
, &args
.ctx
))) < 0)
510 git_oid_cpy(out
, &args
.result
);
513 git_hash_ctx_cleanup(&args
.ctx
);