]>
Commit | Line | Data |
---|---|---|
7000f3fa RB |
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 | #include "common.h" | |
8 | #include "diff.h" | |
114f5a6c | 9 | #include "diff_patch.h" |
f240acce | 10 | #include "fileops.h" |
7000f3fa RB |
11 | |
12 | typedef struct { | |
3ff1d123 | 13 | git_diff *diff; |
10672e3e | 14 | git_diff_format_t format; |
3ff1d123 | 15 | git_diff_line_cb print_cb; |
7000f3fa RB |
16 | void *payload; |
17 | git_buf *buf; | |
10672e3e | 18 | uint32_t flags; |
7000f3fa | 19 | int oid_strlen; |
3b5f7954 | 20 | git_diff_line line; |
7000f3fa RB |
21 | } diff_print_info; |
22 | ||
23 | static int diff_print_info_init( | |
24 | diff_print_info *pi, | |
10672e3e RB |
25 | git_buf *out, |
26 | git_diff *diff, | |
27 | git_diff_format_t format, | |
28 | git_diff_line_cb cb, | |
29 | void *payload) | |
7000f3fa | 30 | { |
7000f3fa | 31 | pi->diff = diff; |
10672e3e | 32 | pi->format = format; |
7000f3fa RB |
33 | pi->print_cb = cb; |
34 | pi->payload = payload; | |
35 | pi->buf = out; | |
36 | ||
10672e3e RB |
37 | if (diff) |
38 | pi->flags = diff->opts.flags; | |
39 | ||
40 | if (diff && diff->opts.oid_abbrev != 0) | |
41 | pi->oid_strlen = diff->opts.oid_abbrev; | |
42 | else if (!diff || !diff->repo) | |
74ded024 RB |
43 | pi->oid_strlen = GIT_ABBREV_DEFAULT; |
44 | else if (git_repository__cvar( | |
45 | &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) | |
7000f3fa RB |
46 | return -1; |
47 | ||
48 | pi->oid_strlen += 1; /* for NUL byte */ | |
49 | ||
50 | if (pi->oid_strlen < 2) | |
51 | pi->oid_strlen = 2; | |
52 | else if (pi->oid_strlen > GIT_OID_HEXSZ + 1) | |
53 | pi->oid_strlen = GIT_OID_HEXSZ + 1; | |
54 | ||
3b5f7954 RB |
55 | memset(&pi->line, 0, sizeof(pi->line)); |
56 | pi->line.old_lineno = -1; | |
57 | pi->line.new_lineno = -1; | |
58 | pi->line.num_lines = 1; | |
59 | ||
7000f3fa RB |
60 | return 0; |
61 | } | |
62 | ||
a1683f28 | 63 | static char diff_pick_suffix(int mode) |
7000f3fa RB |
64 | { |
65 | if (S_ISDIR(mode)) | |
66 | return '/'; | |
a7fcc44d | 67 | else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ |
7000f3fa RB |
68 | /* in git, modes are very regular, so we must have 0100755 mode */ |
69 | return '*'; | |
70 | else | |
71 | return ' '; | |
72 | } | |
73 | ||
74 | char git_diff_status_char(git_delta_t status) | |
75 | { | |
76 | char code; | |
77 | ||
78 | switch (status) { | |
79 | case GIT_DELTA_ADDED: code = 'A'; break; | |
80 | case GIT_DELTA_DELETED: code = 'D'; break; | |
81 | case GIT_DELTA_MODIFIED: code = 'M'; break; | |
82 | case GIT_DELTA_RENAMED: code = 'R'; break; | |
83 | case GIT_DELTA_COPIED: code = 'C'; break; | |
84 | case GIT_DELTA_IGNORED: code = 'I'; break; | |
85 | case GIT_DELTA_UNTRACKED: code = '?'; break; | |
86 | default: code = ' '; break; | |
87 | } | |
88 | ||
89 | return code; | |
90 | } | |
91 | ||
92 | static int callback_error(void) | |
93 | { | |
94 | giterr_clear(); | |
95 | return GIT_EUSER; | |
96 | } | |
97 | ||
10672e3e RB |
98 | static int diff_print_one_name_only( |
99 | const git_diff_delta *delta, float progress, void *data) | |
100 | { | |
101 | diff_print_info *pi = data; | |
102 | git_buf *out = pi->buf; | |
103 | ||
104 | GIT_UNUSED(progress); | |
105 | ||
106 | if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && | |
107 | delta->status == GIT_DELTA_UNMODIFIED) | |
108 | return 0; | |
109 | ||
110 | git_buf_clear(out); | |
111 | ||
112 | if (git_buf_puts(out, delta->new_file.path) < 0 || | |
113 | git_buf_putc(out, '\n')) | |
114 | return -1; | |
115 | ||
3b5f7954 RB |
116 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; |
117 | pi->line.content = git_buf_cstr(out); | |
118 | pi->line.content_len = git_buf_len(out); | |
119 | ||
120 | if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) | |
10672e3e RB |
121 | return callback_error(); |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | static int diff_print_one_name_status( | |
7000f3fa RB |
127 | const git_diff_delta *delta, float progress, void *data) |
128 | { | |
129 | diff_print_info *pi = data; | |
a1683f28 | 130 | git_buf *out = pi->buf; |
7000f3fa | 131 | char old_suffix, new_suffix, code = git_diff_status_char(delta->status); |
74ded024 RB |
132 | int (*strcomp)(const char *, const char *) = |
133 | pi->diff ? pi->diff->strcomp : git__strcmp; | |
7000f3fa RB |
134 | |
135 | GIT_UNUSED(progress); | |
136 | ||
10672e3e | 137 | if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') |
7000f3fa RB |
138 | return 0; |
139 | ||
a1683f28 RB |
140 | old_suffix = diff_pick_suffix(delta->old_file.mode); |
141 | new_suffix = diff_pick_suffix(delta->new_file.mode); | |
7000f3fa | 142 | |
a1683f28 | 143 | git_buf_clear(out); |
7000f3fa RB |
144 | |
145 | if (delta->old_file.path != delta->new_file.path && | |
74ded024 | 146 | strcomp(delta->old_file.path,delta->new_file.path) != 0) |
df40f398 | 147 | git_buf_printf(out, "%c\t%s%c %s%c\n", code, |
7000f3fa RB |
148 | delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); |
149 | else if (delta->old_file.mode != delta->new_file.mode && | |
150 | delta->old_file.mode != 0 && delta->new_file.mode != 0) | |
df40f398 RB |
151 | git_buf_printf(out, "%c\t%s%c %s%c\n", code, |
152 | delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); | |
7000f3fa | 153 | else if (old_suffix != ' ') |
a1683f28 | 154 | git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); |
7000f3fa | 155 | else |
a1683f28 | 156 | git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path); |
7000f3fa | 157 | |
a1683f28 | 158 | if (git_buf_oom(out)) |
7000f3fa RB |
159 | return -1; |
160 | ||
3b5f7954 RB |
161 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; |
162 | pi->line.content = git_buf_cstr(out); | |
163 | pi->line.content_len = git_buf_len(out); | |
164 | ||
165 | if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) | |
7000f3fa RB |
166 | return callback_error(); |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
a1683f28 | 171 | static int diff_print_one_raw( |
7000f3fa RB |
172 | const git_diff_delta *delta, float progress, void *data) |
173 | { | |
174 | diff_print_info *pi = data; | |
a1683f28 | 175 | git_buf *out = pi->buf; |
7000f3fa RB |
176 | char code = git_diff_status_char(delta->status); |
177 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
178 | ||
179 | GIT_UNUSED(progress); | |
180 | ||
10672e3e | 181 | if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') |
7000f3fa RB |
182 | return 0; |
183 | ||
a1683f28 | 184 | git_buf_clear(out); |
7000f3fa RB |
185 | |
186 | git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); | |
187 | git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); | |
188 | ||
189 | git_buf_printf( | |
a1683f28 | 190 | out, ":%06o %06o %s... %s... %c", |
7000f3fa RB |
191 | delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); |
192 | ||
193 | if (delta->similarity > 0) | |
a1683f28 | 194 | git_buf_printf(out, "%03u", delta->similarity); |
7000f3fa | 195 | |
e4acc3ba | 196 | if (delta->old_file.path != delta->new_file.path) |
7000f3fa | 197 | git_buf_printf( |
a1683f28 | 198 | out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); |
7000f3fa RB |
199 | else |
200 | git_buf_printf( | |
a1683f28 | 201 | out, "\t%s\n", delta->old_file.path ? |
7000f3fa RB |
202 | delta->old_file.path : delta->new_file.path); |
203 | ||
a1683f28 | 204 | if (git_buf_oom(out)) |
7000f3fa RB |
205 | return -1; |
206 | ||
3b5f7954 RB |
207 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; |
208 | pi->line.content = git_buf_cstr(out); | |
209 | pi->line.content_len = git_buf_len(out); | |
210 | ||
211 | if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) | |
7000f3fa RB |
212 | return callback_error(); |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
197b8966 RB |
217 | static int diff_print_oid_range( |
218 | git_buf *out, const git_diff_delta *delta, int oid_strlen) | |
7000f3fa RB |
219 | { |
220 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
221 | ||
197b8966 RB |
222 | git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid); |
223 | git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid); | |
7000f3fa RB |
224 | |
225 | /* TODO: Match git diff more closely */ | |
226 | if (delta->old_file.mode == delta->new_file.mode) { | |
a1683f28 | 227 | git_buf_printf(out, "index %s..%s %o\n", |
7000f3fa RB |
228 | start_oid, end_oid, delta->old_file.mode); |
229 | } else { | |
230 | if (delta->old_file.mode == 0) { | |
a1683f28 | 231 | git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); |
7000f3fa | 232 | } else if (delta->new_file.mode == 0) { |
a1683f28 | 233 | git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode); |
7000f3fa | 234 | } else { |
a1683f28 RB |
235 | git_buf_printf(out, "old mode %o\n", delta->old_file.mode); |
236 | git_buf_printf(out, "new mode %o\n", delta->new_file.mode); | |
7000f3fa | 237 | } |
a1683f28 | 238 | git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); |
7000f3fa RB |
239 | } |
240 | ||
a1683f28 | 241 | if (git_buf_oom(out)) |
7000f3fa RB |
242 | return -1; |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
eb1c1707 | 247 | static int diff_delta_format_with_paths( |
197b8966 RB |
248 | git_buf *out, |
249 | const git_diff_delta *delta, | |
250 | const char *oldpfx, | |
251 | const char *newpfx, | |
eb1c1707 | 252 | const char *template) |
7000f3fa | 253 | { |
7000f3fa | 254 | const char *oldpath = delta->old_file.path; |
7000f3fa | 255 | const char *newpath = delta->new_file.path; |
7000f3fa | 256 | |
eb1c1707 RB |
257 | if (git_oid_iszero(&delta->old_file.oid)) { |
258 | oldpfx = ""; | |
259 | oldpath = "/dev/null"; | |
260 | } | |
261 | if (git_oid_iszero(&delta->new_file.oid)) { | |
262 | newpfx = ""; | |
263 | newpath = "/dev/null"; | |
264 | } | |
265 | ||
266 | return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath); | |
267 | } | |
268 | ||
269 | int git_diff_delta__format_file_header( | |
270 | git_buf *out, | |
271 | const git_diff_delta *delta, | |
272 | const char *oldpfx, | |
273 | const char *newpfx, | |
274 | int oid_strlen) | |
275 | { | |
7000f3fa RB |
276 | if (!oldpfx) |
277 | oldpfx = DIFF_OLD_PREFIX_DEFAULT; | |
7000f3fa RB |
278 | if (!newpfx) |
279 | newpfx = DIFF_NEW_PREFIX_DEFAULT; | |
197b8966 RB |
280 | if (!oid_strlen) |
281 | oid_strlen = GIT_ABBREV_DEFAULT + 1; | |
7000f3fa | 282 | |
197b8966 RB |
283 | git_buf_clear(out); |
284 | ||
285 | git_buf_printf(out, "diff --git %s%s %s%s\n", | |
eb1c1707 | 286 | oldpfx, delta->old_file.path, newpfx, delta->new_file.path); |
7000f3fa | 287 | |
197b8966 | 288 | if (diff_print_oid_range(out, delta, oid_strlen) < 0) |
7000f3fa RB |
289 | return -1; |
290 | ||
eb1c1707 RB |
291 | if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) |
292 | diff_delta_format_with_paths( | |
293 | out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n"); | |
7000f3fa | 294 | |
197b8966 RB |
295 | return git_buf_oom(out) ? -1 : 0; |
296 | } | |
7000f3fa | 297 | |
197b8966 RB |
298 | static int diff_print_patch_file( |
299 | const git_diff_delta *delta, float progress, void *data) | |
300 | { | |
301 | diff_print_info *pi = data; | |
eb1c1707 RB |
302 | const char *oldpfx = |
303 | pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; | |
304 | const char *newpfx = | |
305 | pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; | |
197b8966 RB |
306 | |
307 | GIT_UNUSED(progress); | |
7000f3fa | 308 | |
197b8966 RB |
309 | if (S_ISDIR(delta->new_file.mode) || |
310 | delta->status == GIT_DELTA_UNMODIFIED || | |
311 | delta->status == GIT_DELTA_IGNORED || | |
312 | (delta->status == GIT_DELTA_UNTRACKED && | |
10672e3e | 313 | (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) |
7000f3fa RB |
314 | return 0; |
315 | ||
197b8966 RB |
316 | if (git_diff_delta__format_file_header( |
317 | pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0) | |
7000f3fa RB |
318 | return -1; |
319 | ||
3b5f7954 RB |
320 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; |
321 | pi->line.content = git_buf_cstr(pi->buf); | |
322 | pi->line.content_len = git_buf_len(pi->buf); | |
323 | ||
324 | if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) | |
eb1c1707 RB |
325 | return callback_error(); |
326 | ||
327 | if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) | |
328 | return 0; | |
329 | ||
330 | git_buf_clear(pi->buf); | |
331 | ||
332 | if (diff_delta_format_with_paths( | |
333 | pi->buf, delta, oldpfx, newpfx, | |
334 | "Binary files %s%s and %s%s differ\n") < 0) | |
335 | return -1; | |
336 | ||
3b5f7954 RB |
337 | pi->line.origin = GIT_DIFF_LINE_BINARY; |
338 | pi->line.content = git_buf_cstr(pi->buf); | |
339 | pi->line.content_len = git_buf_len(pi->buf); | |
340 | pi->line.num_lines = 1; | |
341 | ||
342 | if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) | |
7000f3fa RB |
343 | return callback_error(); |
344 | ||
345 | return 0; | |
346 | } | |
347 | ||
a1683f28 | 348 | static int diff_print_patch_hunk( |
7000f3fa | 349 | const git_diff_delta *d, |
3b5f7954 | 350 | const git_diff_hunk *h, |
7000f3fa RB |
351 | void *data) |
352 | { | |
353 | diff_print_info *pi = data; | |
354 | ||
355 | if (S_ISDIR(d->new_file.mode)) | |
356 | return 0; | |
357 | ||
3b5f7954 RB |
358 | pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; |
359 | pi->line.content = h->header; | |
360 | pi->line.content_len = h->header_len; | |
7000f3fa | 361 | |
3b5f7954 | 362 | if (pi->print_cb(d, h, &pi->line, pi->payload)) |
7000f3fa RB |
363 | return callback_error(); |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
a1683f28 | 368 | static int diff_print_patch_line( |
7000f3fa | 369 | const git_diff_delta *delta, |
3b5f7954 RB |
370 | const git_diff_hunk *hunk, |
371 | const git_diff_line *line, | |
7000f3fa RB |
372 | void *data) |
373 | { | |
374 | diff_print_info *pi = data; | |
375 | ||
376 | if (S_ISDIR(delta->new_file.mode)) | |
377 | return 0; | |
378 | ||
3b5f7954 | 379 | if (pi->print_cb(delta, hunk, line, pi->payload)) |
7000f3fa RB |
380 | return callback_error(); |
381 | ||
382 | return 0; | |
383 | } | |
384 | ||
10672e3e RB |
385 | /* print a git_diff to an output callback */ |
386 | int git_diff_print( | |
3ff1d123 | 387 | git_diff *diff, |
10672e3e | 388 | git_diff_format_t format, |
3ff1d123 | 389 | git_diff_line_cb print_cb, |
7000f3fa RB |
390 | void *payload) |
391 | { | |
392 | int error; | |
393 | git_buf buf = GIT_BUF_INIT; | |
394 | diff_print_info pi; | |
10672e3e RB |
395 | git_diff_file_cb print_file = NULL; |
396 | git_diff_hunk_cb print_hunk = NULL; | |
397 | git_diff_line_cb print_line = NULL; | |
398 | ||
399 | switch (format) { | |
400 | case GIT_DIFF_FORMAT_PATCH: | |
401 | print_file = diff_print_patch_file; | |
402 | print_hunk = diff_print_patch_hunk; | |
403 | print_line = diff_print_patch_line; | |
404 | break; | |
405 | case GIT_DIFF_FORMAT_PATCH_HEADER: | |
406 | print_file = diff_print_patch_file; | |
407 | break; | |
408 | case GIT_DIFF_FORMAT_RAW: | |
409 | print_file = diff_print_one_raw; | |
410 | break; | |
411 | case GIT_DIFF_FORMAT_NAME_ONLY: | |
412 | print_file = diff_print_one_name_only; | |
413 | break; | |
414 | case GIT_DIFF_FORMAT_NAME_STATUS: | |
415 | print_file = diff_print_one_name_status; | |
416 | break; | |
417 | default: | |
418 | giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format); | |
419 | return -1; | |
420 | } | |
7000f3fa | 421 | |
10672e3e RB |
422 | if (!(error = diff_print_info_init( |
423 | &pi, &buf, diff, format, print_cb, payload))) | |
7000f3fa | 424 | error = git_diff_foreach( |
10672e3e | 425 | diff, print_file, print_hunk, print_line, &pi); |
7000f3fa RB |
426 | |
427 | git_buf_free(&buf); | |
428 | ||
429 | return error; | |
430 | } | |
431 | ||
3ff1d123 RB |
432 | /* print a git_patch to an output callback */ |
433 | int git_patch_print( | |
434 | git_patch *patch, | |
435 | git_diff_line_cb print_cb, | |
7000f3fa RB |
436 | void *payload) |
437 | { | |
438 | int error; | |
439 | git_buf temp = GIT_BUF_INIT; | |
440 | diff_print_info pi; | |
7000f3fa RB |
441 | |
442 | assert(patch && print_cb); | |
443 | ||
444 | if (!(error = diff_print_info_init( | |
10672e3e RB |
445 | &pi, &temp, git_patch__diff(patch), |
446 | GIT_DIFF_FORMAT_PATCH, print_cb, payload))) | |
3ff1d123 | 447 | error = git_patch__invoke_callbacks( |
a1683f28 RB |
448 | patch, diff_print_patch_file, diff_print_patch_hunk, |
449 | diff_print_patch_line, &pi); | |
7000f3fa RB |
450 | |
451 | git_buf_free(&temp); | |
452 | ||
453 | return error; | |
454 | } | |
455 | ||
a1683f28 RB |
456 | static int diff_print_to_buffer_cb( |
457 | const git_diff_delta *delta, | |
3b5f7954 RB |
458 | const git_diff_hunk *hunk, |
459 | const git_diff_line *line, | |
a1683f28 RB |
460 | void *payload) |
461 | { | |
462 | git_buf *output = payload; | |
3b5f7954 RB |
463 | GIT_UNUSED(delta); GIT_UNUSED(hunk); |
464 | ||
465 | if (line->origin == GIT_DIFF_LINE_ADDITION || | |
466 | line->origin == GIT_DIFF_LINE_DELETION || | |
467 | line->origin == GIT_DIFF_LINE_CONTEXT) | |
468 | git_buf_putc(output, line->origin); | |
469 | ||
470 | return git_buf_put(output, line->content, line->content_len); | |
a1683f28 RB |
471 | } |
472 | ||
3ff1d123 RB |
473 | /* print a git_patch to a string buffer */ |
474 | int git_patch_to_str( | |
7000f3fa | 475 | char **string, |
3ff1d123 | 476 | git_patch *patch) |
7000f3fa RB |
477 | { |
478 | int error; | |
479 | git_buf output = GIT_BUF_INIT; | |
480 | ||
3ff1d123 | 481 | error = git_patch_print(patch, diff_print_to_buffer_cb, &output); |
7000f3fa RB |
482 | |
483 | /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, | |
484 | * meaning a memory allocation failure, so just map to -1... | |
485 | */ | |
486 | if (error == GIT_EUSER) | |
487 | error = -1; | |
488 | ||
489 | *string = git_buf_detach(&output); | |
490 | ||
491 | return error; | |
492 | } |