]>
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 RB |
9 | #include "diff_patch.h" |
10 | #include "buffer.h" | |
7000f3fa RB |
11 | |
12 | typedef struct { | |
13 | git_diff_list *diff; | |
14 | git_diff_data_cb print_cb; | |
15 | void *payload; | |
16 | git_buf *buf; | |
17 | int oid_strlen; | |
18 | } diff_print_info; | |
19 | ||
20 | static int diff_print_info_init( | |
21 | diff_print_info *pi, | |
22 | git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload) | |
23 | { | |
24 | assert(diff && diff->repo); | |
25 | ||
26 | pi->diff = diff; | |
27 | pi->print_cb = cb; | |
28 | pi->payload = payload; | |
29 | pi->buf = out; | |
30 | ||
31 | if (git_repository__cvar(&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) | |
32 | return -1; | |
33 | ||
34 | pi->oid_strlen += 1; /* for NUL byte */ | |
35 | ||
36 | if (pi->oid_strlen < 2) | |
37 | pi->oid_strlen = 2; | |
38 | else if (pi->oid_strlen > GIT_OID_HEXSZ + 1) | |
39 | pi->oid_strlen = GIT_OID_HEXSZ + 1; | |
40 | ||
41 | return 0; | |
42 | } | |
43 | ||
44 | static char pick_suffix(int mode) | |
45 | { | |
46 | if (S_ISDIR(mode)) | |
47 | return '/'; | |
48 | else if (mode & 0100) //-V536 | |
49 | /* in git, modes are very regular, so we must have 0100755 mode */ | |
50 | return '*'; | |
51 | else | |
52 | return ' '; | |
53 | } | |
54 | ||
55 | char git_diff_status_char(git_delta_t status) | |
56 | { | |
57 | char code; | |
58 | ||
59 | switch (status) { | |
60 | case GIT_DELTA_ADDED: code = 'A'; break; | |
61 | case GIT_DELTA_DELETED: code = 'D'; break; | |
62 | case GIT_DELTA_MODIFIED: code = 'M'; break; | |
63 | case GIT_DELTA_RENAMED: code = 'R'; break; | |
64 | case GIT_DELTA_COPIED: code = 'C'; break; | |
65 | case GIT_DELTA_IGNORED: code = 'I'; break; | |
66 | case GIT_DELTA_UNTRACKED: code = '?'; break; | |
67 | default: code = ' '; break; | |
68 | } | |
69 | ||
70 | return code; | |
71 | } | |
72 | ||
73 | static int callback_error(void) | |
74 | { | |
75 | giterr_clear(); | |
76 | return GIT_EUSER; | |
77 | } | |
78 | ||
79 | static int print_compact( | |
80 | const git_diff_delta *delta, float progress, void *data) | |
81 | { | |
82 | diff_print_info *pi = data; | |
83 | char old_suffix, new_suffix, code = git_diff_status_char(delta->status); | |
84 | ||
85 | GIT_UNUSED(progress); | |
86 | ||
87 | if (code == ' ') | |
88 | return 0; | |
89 | ||
90 | old_suffix = pick_suffix(delta->old_file.mode); | |
91 | new_suffix = pick_suffix(delta->new_file.mode); | |
92 | ||
93 | git_buf_clear(pi->buf); | |
94 | ||
95 | if (delta->old_file.path != delta->new_file.path && | |
96 | pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) | |
97 | git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, | |
98 | delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); | |
99 | else if (delta->old_file.mode != delta->new_file.mode && | |
100 | delta->old_file.mode != 0 && delta->new_file.mode != 0) | |
101 | git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, | |
102 | delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); | |
103 | else if (old_suffix != ' ') | |
104 | git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); | |
105 | else | |
106 | git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); | |
107 | ||
108 | if (git_buf_oom(pi->buf)) | |
109 | return -1; | |
110 | ||
111 | if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, | |
112 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
113 | return callback_error(); | |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
118 | int git_diff_print_compact( | |
119 | git_diff_list *diff, | |
120 | git_diff_data_cb print_cb, | |
121 | void *payload) | |
122 | { | |
123 | int error; | |
124 | git_buf buf = GIT_BUF_INIT; | |
125 | diff_print_info pi; | |
126 | ||
127 | if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) | |
128 | error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi); | |
129 | ||
130 | git_buf_free(&buf); | |
131 | ||
132 | return error; | |
133 | } | |
134 | ||
135 | static int print_raw( | |
136 | const git_diff_delta *delta, float progress, void *data) | |
137 | { | |
138 | diff_print_info *pi = data; | |
139 | char code = git_diff_status_char(delta->status); | |
140 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
141 | ||
142 | GIT_UNUSED(progress); | |
143 | ||
144 | if (code == ' ') | |
145 | return 0; | |
146 | ||
147 | git_buf_clear(pi->buf); | |
148 | ||
149 | git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); | |
150 | git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); | |
151 | ||
152 | git_buf_printf( | |
153 | pi->buf, ":%06o %06o %s... %s... %c", | |
154 | delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); | |
155 | ||
156 | if (delta->similarity > 0) | |
157 | git_buf_printf(pi->buf, "%03u", delta->similarity); | |
158 | ||
159 | if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) | |
160 | git_buf_printf( | |
161 | pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path); | |
162 | else | |
163 | git_buf_printf( | |
164 | pi->buf, "\t%s\n", delta->old_file.path ? | |
165 | delta->old_file.path : delta->new_file.path); | |
166 | ||
167 | if (git_buf_oom(pi->buf)) | |
168 | return -1; | |
169 | ||
170 | if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, | |
171 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
172 | return callback_error(); | |
173 | ||
174 | return 0; | |
175 | } | |
176 | ||
177 | int git_diff_print_raw( | |
178 | git_diff_list *diff, | |
179 | git_diff_data_cb print_cb, | |
180 | void *payload) | |
181 | { | |
182 | int error; | |
183 | git_buf buf = GIT_BUF_INIT; | |
184 | diff_print_info pi; | |
185 | ||
186 | if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) | |
187 | error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi); | |
188 | ||
189 | git_buf_free(&buf); | |
190 | ||
191 | return error; | |
192 | } | |
193 | ||
194 | static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) | |
195 | { | |
196 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
197 | ||
198 | git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); | |
199 | git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); | |
200 | ||
201 | /* TODO: Match git diff more closely */ | |
202 | if (delta->old_file.mode == delta->new_file.mode) { | |
203 | git_buf_printf(pi->buf, "index %s..%s %o\n", | |
204 | start_oid, end_oid, delta->old_file.mode); | |
205 | } else { | |
206 | if (delta->old_file.mode == 0) { | |
207 | git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); | |
208 | } else if (delta->new_file.mode == 0) { | |
209 | git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); | |
210 | } else { | |
211 | git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); | |
212 | git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); | |
213 | } | |
214 | git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); | |
215 | } | |
216 | ||
217 | if (git_buf_oom(pi->buf)) | |
218 | return -1; | |
219 | ||
220 | return 0; | |
221 | } | |
222 | ||
223 | static int print_patch_file( | |
224 | const git_diff_delta *delta, float progress, void *data) | |
225 | { | |
226 | diff_print_info *pi = data; | |
227 | const char *oldpfx = pi->diff->opts.old_prefix; | |
228 | const char *oldpath = delta->old_file.path; | |
229 | const char *newpfx = pi->diff->opts.new_prefix; | |
230 | const char *newpath = delta->new_file.path; | |
231 | ||
232 | GIT_UNUSED(progress); | |
233 | ||
234 | if (S_ISDIR(delta->new_file.mode) || | |
235 | delta->status == GIT_DELTA_UNMODIFIED || | |
236 | delta->status == GIT_DELTA_IGNORED || | |
237 | (delta->status == GIT_DELTA_UNTRACKED && | |
238 | (pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) | |
239 | return 0; | |
240 | ||
241 | if (!oldpfx) | |
242 | oldpfx = DIFF_OLD_PREFIX_DEFAULT; | |
243 | ||
244 | if (!newpfx) | |
245 | newpfx = DIFF_NEW_PREFIX_DEFAULT; | |
246 | ||
247 | git_buf_clear(pi->buf); | |
248 | git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); | |
249 | ||
250 | if (print_oid_range(pi, delta) < 0) | |
251 | return -1; | |
252 | ||
253 | if (git_oid_iszero(&delta->old_file.oid)) { | |
254 | oldpfx = ""; | |
255 | oldpath = "/dev/null"; | |
256 | } | |
257 | if (git_oid_iszero(&delta->new_file.oid)) { | |
258 | newpfx = ""; | |
259 | newpath = "/dev/null"; | |
260 | } | |
261 | ||
262 | if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) { | |
263 | git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); | |
264 | git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); | |
265 | } | |
266 | ||
267 | if (git_buf_oom(pi->buf)) | |
268 | return -1; | |
269 | ||
270 | if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, | |
271 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
272 | return callback_error(); | |
273 | ||
274 | if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) | |
275 | return 0; | |
276 | ||
277 | git_buf_clear(pi->buf); | |
278 | git_buf_printf( | |
279 | pi->buf, "Binary files %s%s and %s%s differ\n", | |
280 | oldpfx, oldpath, newpfx, newpath); | |
281 | if (git_buf_oom(pi->buf)) | |
282 | return -1; | |
283 | ||
284 | if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, | |
285 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
286 | return callback_error(); | |
287 | ||
288 | return 0; | |
289 | } | |
290 | ||
291 | static int print_patch_hunk( | |
292 | const git_diff_delta *d, | |
293 | const git_diff_range *r, | |
294 | const char *header, | |
295 | size_t header_len, | |
296 | void *data) | |
297 | { | |
298 | diff_print_info *pi = data; | |
299 | ||
300 | if (S_ISDIR(d->new_file.mode)) | |
301 | return 0; | |
302 | ||
303 | git_buf_clear(pi->buf); | |
304 | if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) | |
305 | return -1; | |
306 | ||
307 | if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR, | |
308 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
309 | return callback_error(); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | static int print_patch_line( | |
315 | const git_diff_delta *delta, | |
316 | const git_diff_range *range, | |
317 | char line_origin, /* GIT_DIFF_LINE value from above */ | |
318 | const char *content, | |
319 | size_t content_len, | |
320 | void *data) | |
321 | { | |
322 | diff_print_info *pi = data; | |
323 | ||
324 | if (S_ISDIR(delta->new_file.mode)) | |
325 | return 0; | |
326 | ||
327 | git_buf_clear(pi->buf); | |
328 | ||
329 | if (line_origin == GIT_DIFF_LINE_ADDITION || | |
330 | line_origin == GIT_DIFF_LINE_DELETION || | |
331 | line_origin == GIT_DIFF_LINE_CONTEXT) | |
332 | git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content); | |
333 | else if (content_len > 0) | |
334 | git_buf_printf(pi->buf, "%.*s", (int)content_len, content); | |
335 | ||
336 | if (git_buf_oom(pi->buf)) | |
337 | return -1; | |
338 | ||
339 | if (pi->print_cb(delta, range, line_origin, | |
340 | git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) | |
341 | return callback_error(); | |
342 | ||
343 | return 0; | |
344 | } | |
345 | ||
346 | int git_diff_print_patch( | |
347 | git_diff_list *diff, | |
348 | git_diff_data_cb print_cb, | |
349 | void *payload) | |
350 | { | |
351 | int error; | |
352 | git_buf buf = GIT_BUF_INIT; | |
353 | diff_print_info pi; | |
354 | ||
355 | if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) | |
356 | error = git_diff_foreach( | |
357 | diff, print_patch_file, print_patch_hunk, print_patch_line, &pi); | |
358 | ||
359 | git_buf_free(&buf); | |
360 | ||
361 | return error; | |
362 | } | |
363 | ||
364 | ||
365 | static int print_to_buffer_cb( | |
366 | const git_diff_delta *delta, | |
367 | const git_diff_range *range, | |
368 | char line_origin, | |
369 | const char *content, | |
370 | size_t content_len, | |
371 | void *payload) | |
372 | { | |
373 | git_buf *output = payload; | |
374 | GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); | |
375 | return git_buf_put(output, content, content_len); | |
376 | } | |
377 | ||
378 | int git_diff_patch_print( | |
379 | git_diff_patch *patch, | |
380 | git_diff_data_cb print_cb, | |
381 | void *payload) | |
382 | { | |
383 | int error; | |
384 | git_buf temp = GIT_BUF_INIT; | |
385 | diff_print_info pi; | |
7000f3fa RB |
386 | |
387 | assert(patch && print_cb); | |
388 | ||
389 | if (!(error = diff_print_info_init( | |
360f42f4 RB |
390 | &pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) |
391 | error = git_diff_patch__invoke_callbacks( | |
392 | patch, print_patch_file, print_patch_hunk, print_patch_line, &pi); | |
7000f3fa RB |
393 | |
394 | git_buf_free(&temp); | |
395 | ||
396 | return error; | |
397 | } | |
398 | ||
399 | int git_diff_patch_to_str( | |
400 | char **string, | |
401 | git_diff_patch *patch) | |
402 | { | |
403 | int error; | |
404 | git_buf output = GIT_BUF_INIT; | |
405 | ||
406 | error = git_diff_patch_print(patch, print_to_buffer_cb, &output); | |
407 | ||
408 | /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, | |
409 | * meaning a memory allocation failure, so just map to -1... | |
410 | */ | |
411 | if (error == GIT_EUSER) | |
412 | error = -1; | |
413 | ||
414 | *string = git_buf_detach(&output); | |
415 | ||
416 | return error; | |
417 | } |