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 "common.h"
11 #include "patch.h"
12 #include "email.h"
13 #include "commit.h"
14 #include "index.h"
15 #include "diff_generate.h"
16 
17 #include "git2/version.h"
18 #include "git2/email.h"
19 
20 struct patch_id_args {
21 	git_hash_ctx ctx;
22 	git_oid result;
23 	int first_file;
24 };
25 
diff_delta__path(const git_diff_delta * delta)26 GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
27 {
28 	const char *str = delta->old_file.path;
29 
30 	if (!str ||
31 		delta->status == GIT_DELTA_ADDED ||
32 		delta->status == GIT_DELTA_RENAMED ||
33 		delta->status == GIT_DELTA_COPIED)
34 		str = delta->new_file.path;
35 
36 	return str;
37 }
38 
git_diff_delta__cmp(const void * a,const void * b)39 int git_diff_delta__cmp(const void *a, const void *b)
40 {
41 	const git_diff_delta *da = a, *db = b;
42 	int val = strcmp(diff_delta__path(da), diff_delta__path(db));
43 	return val ? val : ((int)da->status - (int)db->status);
44 }
45 
git_diff_delta__casecmp(const void * a,const void * b)46 int git_diff_delta__casecmp(const void *a, const void *b)
47 {
48 	const git_diff_delta *da = a, *db = b;
49 	int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
50 	return val ? val : ((int)da->status - (int)db->status);
51 }
52 
git_diff__entry_cmp(const void * a,const void * b)53 int git_diff__entry_cmp(const void *a, const void *b)
54 {
55 	const git_index_entry *entry_a = a;
56 	const git_index_entry *entry_b = b;
57 
58 	return strcmp(entry_a->path, entry_b->path);
59 }
60 
git_diff__entry_icmp(const void * a,const void * b)61 int git_diff__entry_icmp(const void *a, const void *b)
62 {
63 	const git_index_entry *entry_a = a;
64 	const git_index_entry *entry_b = b;
65 
66 	return strcasecmp(entry_a->path, entry_b->path);
67 }
68 
git_diff_free(git_diff * diff)69 void git_diff_free(git_diff *diff)
70 {
71 	if (!diff)
72 		return;
73 
74 	GIT_REFCOUNT_DEC(diff, diff->free_fn);
75 }
76 
git_diff_addref(git_diff * diff)77 void git_diff_addref(git_diff *diff)
78 {
79 	GIT_REFCOUNT_INC(diff);
80 }
81 
git_diff_num_deltas(const git_diff * diff)82 size_t git_diff_num_deltas(const git_diff *diff)
83 {
84 	GIT_ASSERT_ARG(diff);
85 	return diff->deltas.length;
86 }
87 
git_diff_num_deltas_of_type(const git_diff * diff,git_delta_t type)88 size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
89 {
90 	size_t i, count = 0;
91 	const git_diff_delta *delta;
92 
93 	GIT_ASSERT_ARG(diff);
94 
95 	git_vector_foreach(&diff->deltas, i, delta) {
96 		count += (delta->status == type);
97 	}
98 
99 	return count;
100 }
101 
git_diff_get_delta(const git_diff * diff,size_t idx)102 const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
103 {
104 	GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL);
105 	return git_vector_get(&diff->deltas, idx);
106 }
107 
git_diff_is_sorted_icase(const git_diff * diff)108 int git_diff_is_sorted_icase(const git_diff *diff)
109 {
110 	return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
111 }
112 
git_diff_get_perfdata(git_diff_perfdata * out,const git_diff * diff)113 int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
114 {
115 	GIT_ASSERT_ARG(out);
116 	GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
117 	out->stat_calls = diff->perf.stat_calls;
118 	out->oid_calculations = diff->perf.oid_calculations;
119 	return 0;
120 }
121 
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)122 int git_diff_foreach(
123 	git_diff *diff,
124 	git_diff_file_cb file_cb,
125 	git_diff_binary_cb binary_cb,
126 	git_diff_hunk_cb hunk_cb,
127 	git_diff_line_cb data_cb,
128 	void *payload)
129 {
130 	int error = 0;
131 	git_diff_delta *delta;
132 	size_t idx;
133 
134 	GIT_ASSERT_ARG(diff);
135 
136 	git_vector_foreach(&diff->deltas, idx, delta) {
137 		git_patch *patch;
138 
139 		/* check flags against patch status */
140 		if (git_diff_delta__should_skip(&diff->opts, delta))
141 			continue;
142 
143 		if ((error = git_patch_from_diff(&patch, diff, idx)) != 0)
144 			break;
145 
146 		error = git_patch__invoke_callbacks(patch, file_cb, binary_cb,
147 						    hunk_cb, data_cb, payload);
148 		git_patch_free(patch);
149 
150 		if (error)
151 			break;
152 	}
153 
154 	return error;
155 }
156 
157 #ifndef GIT_DEPRECATE_HARD
158 
git_diff_format_email(git_buf * out,git_diff * diff,const git_diff_format_email_options * opts)159 int git_diff_format_email(
160 	git_buf *out,
161 	git_diff *diff,
162 	const git_diff_format_email_options *opts)
163 {
164 	git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
165 	int error;
166 
167 	GIT_ASSERT_ARG(out);
168 	GIT_ASSERT_ARG(diff);
169 	GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author);
170 
171 	GIT_ERROR_CHECK_VERSION(opts,
172 		GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
173 		"git_format_email_options");
174 
175 	if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0)
176 		email_create_opts.subject_prefix = "";
177 
178 
179 	error = git_email__append_from_diff(out, diff, opts->patch_no,
180 		opts->total_patches, opts->id, opts->summary, opts->body,
181 		opts->author, &email_create_opts);
182 
183 	return error;
184 }
185 
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)186 int git_diff_commit_as_email(
187 	git_buf *out,
188 	git_repository *repo,
189 	git_commit *commit,
190 	size_t patch_no,
191 	size_t total_patches,
192 	uint32_t flags,
193 	const git_diff_options *diff_opts)
194 {
195 	git_diff *diff = NULL;
196 	git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
197 	const git_oid *commit_id;
198 	const char *summary, *body;
199 	const git_signature *author;
200 	int error;
201 
202 	GIT_ASSERT_ARG(out);
203 	GIT_ASSERT_ARG(repo);
204 	GIT_ASSERT_ARG(commit);
205 
206 	commit_id = git_commit_id(commit);
207 	summary = git_commit_summary(commit);
208 	body = git_commit_body(commit);
209 	author = git_commit_author(commit);
210 
211 	if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0)
212 		opts.subject_prefix = "";
213 
214 	if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
215 		return error;
216 
217 	error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts);
218 
219 	git_diff_free(diff);
220 	return error;
221 }
222 
git_diff_init_options(git_diff_options * opts,unsigned int version)223 int git_diff_init_options(git_diff_options *opts, unsigned int version)
224 {
225 	return git_diff_options_init(opts, version);
226 }
227 
git_diff_find_init_options(git_diff_find_options * opts,unsigned int version)228 int git_diff_find_init_options(
229 	git_diff_find_options *opts, unsigned int version)
230 {
231 	return git_diff_find_options_init(opts, version);
232 }
233 
git_diff_format_email_options_init(git_diff_format_email_options * opts,unsigned int version)234 int git_diff_format_email_options_init(
235 	git_diff_format_email_options *opts, unsigned int version)
236 {
237 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
238 		opts, version, git_diff_format_email_options,
239 		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
240 	return 0;
241 }
242 
git_diff_format_email_init_options(git_diff_format_email_options * opts,unsigned int version)243 int git_diff_format_email_init_options(
244 	git_diff_format_email_options *opts, unsigned int version)
245 {
246 	return git_diff_format_email_options_init(opts, version);
247 }
248 
249 #endif
250 
git_diff_options_init(git_diff_options * opts,unsigned int version)251 int git_diff_options_init(git_diff_options *opts, unsigned int version)
252 {
253 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
254 		opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
255 	return 0;
256 }
257 
git_diff_find_options_init(git_diff_find_options * opts,unsigned int version)258 int git_diff_find_options_init(
259 	git_diff_find_options *opts, unsigned int version)
260 {
261 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
262 		opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
263 	return 0;
264 }
265 
flush_hunk(git_oid * result,git_hash_ctx * ctx)266 static int flush_hunk(git_oid *result, git_hash_ctx *ctx)
267 {
268 	git_oid hash;
269 	unsigned short carry = 0;
270 	int error, i;
271 
272 	if ((error = git_hash_final(&hash, ctx)) < 0 ||
273 	    (error = git_hash_init(ctx)) < 0)
274 		return error;
275 
276 	for (i = 0; i < GIT_OID_RAWSZ; i++) {
277 		carry += result->id[i] + hash.id[i];
278 		result->id[i] = (unsigned char)carry;
279 		carry >>= 8;
280 	}
281 
282 	return 0;
283 }
284 
strip_spaces(git_buf * buf)285 static void strip_spaces(git_buf *buf)
286 {
287 	char *src = buf->ptr, *dst = buf->ptr;
288 	char c;
289 	size_t len = 0;
290 
291 	while ((c = *src++) != '\0') {
292 		if (!git__isspace(c)) {
293 			*dst++ = c;
294 			len++;
295 		}
296 	}
297 
298 	git_buf_truncate(buf, len);
299 }
300 
diff_patchid_print_callback_to_buf(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)301 static int diff_patchid_print_callback_to_buf(
302 	const git_diff_delta *delta,
303 	const git_diff_hunk *hunk,
304 	const git_diff_line *line,
305 	void *payload)
306 {
307 	struct patch_id_args *args = (struct patch_id_args *) payload;
308 	git_buf buf = GIT_BUF_INIT;
309 	int error = 0;
310 
311 	if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL ||
312 	    line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
313 	    line->origin == GIT_DIFF_LINE_DEL_EOFNL)
314 		goto out;
315 
316 	if ((error = git_diff_print_callback__to_buf(delta, hunk,
317 						     line, &buf)) < 0)
318 		goto out;
319 
320 	strip_spaces(&buf);
321 
322 	if (line->origin == GIT_DIFF_LINE_FILE_HDR &&
323 	    !args->first_file &&
324 	    (error = flush_hunk(&args->result, &args->ctx) < 0))
325 			goto out;
326 
327 	if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0)
328 		goto out;
329 
330 	if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file)
331 		args->first_file = 0;
332 
333 out:
334 	git_buf_dispose(&buf);
335 	return error;
336 }
337 
git_diff_patchid_options_init(git_diff_patchid_options * opts,unsigned int version)338 int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version)
339 {
340 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
341 		opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT);
342 	return 0;
343 }
344 
git_diff_patchid(git_oid * out,git_diff * diff,git_diff_patchid_options * opts)345 int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts)
346 {
347 	struct patch_id_args args;
348 	int error;
349 
350 	GIT_ERROR_CHECK_VERSION(
351 		opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options");
352 
353 	memset(&args, 0, sizeof(args));
354 	args.first_file = 1;
355 	if ((error = git_hash_ctx_init(&args.ctx)) < 0)
356 		goto out;
357 
358 	if ((error = git_diff_print(diff,
359 				    GIT_DIFF_FORMAT_PATCH_ID,
360 				    diff_patchid_print_callback_to_buf,
361 				    &args)) < 0)
362 		goto out;
363 
364 	if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)
365 		goto out;
366 
367 	git_oid_cpy(out, &args.result);
368 
369 out:
370 	git_hash_ctx_cleanup(&args.ctx);
371 	return error;
372 }
373