]> git.proxmox.com Git - libgit2.git/blob - src/diff.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / diff.c
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
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
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
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
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
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
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
77 void git_diff_addref(git_diff *diff)
78 {
79 GIT_REFCOUNT_INC(diff);
80 }
81
82 size_t git_diff_num_deltas(const git_diff *diff)
83 {
84 GIT_ASSERT_ARG(diff);
85 return diff->deltas.length;
86 }
87
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
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
108 int git_diff_is_sorted_icase(const git_diff *diff)
109 {
110 return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
111 }
112
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
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
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
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
223 int git_diff_init_options(git_diff_options *opts, unsigned int version)
224 {
225 return git_diff_options_init(opts, version);
226 }
227
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
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
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
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
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
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
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
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
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
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 }