source src/diff_xdiff.c
Line | Flow | Count | Block(s) | Source |
---|---|---|---|---|
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_xdiff.h" | ||
9 | - | #include "util.h" | ||
10 | - | |||
11 | - | #include "git2/errors.h" | ||
12 | - | #include "diff.h" | ||
13 | - | #include "diff_driver.h" | ||
14 | - | #include "patch_generate.h" | ||
15 | - | |||
16 | 3234 | 2 | static int git_xdiff_scan_int(const char **str, int *value) | |
17 | - | { | ||
18 | 3234 | 2 | const char *scan = *str; | |
19 | 3234 | 2 | int v = 0, digits = 0; | |
20 | - | /* find next digit */ | ||
21 | 10248 | 2-6 | for (scan = *str; *scan && !git__isdigit(*scan); scan++); | |
22 | - | /* parse next number */ | ||
23 | 6970 | 7-10 | for (; git__isdigit(*scan); scan++, digits++) | |
24 | 3736 | 8 | v = (v * 10) + (*scan - '0'); | |
25 | 3234 | 11 | *str = scan; | |
26 | 3234 | 11 | *value = v; | |
27 | 3234 | 11 | return (digits > 0) ? 0 : -1; | |
28 | - | } | ||
29 | - | |||
30 | 945 | 2 | static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) | |
31 | - | { | ||
32 | - | /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ | ||
33 | 945 | 2 | if (*header != '@') | |
34 | ##### | 3 | goto fail; | |
35 | 945 | 4,5 | if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) | |
36 | ##### | 6 | goto fail; | |
37 | 945 | 7 | if (*header == ',') { | |
38 | 558 | 8,9 | if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) | |
39 | ##### | 10 | goto fail; | |
40 | - | } else | ||
41 | 387 | 11 | hunk->old_lines = 1; | |
42 | 945 | 12,13 | if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) | |
43 | ##### | 14 | goto fail; | |
44 | 945 | 15 | if (*header == ',') { | |
45 | 786 | 16,17 | if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) | |
46 | ##### | 18 | goto fail; | |
47 | - | } else | ||
48 | 159 | 19 | hunk->new_lines = 1; | |
49 | 945 | 20,21 | if (hunk->old_start < 0 || hunk->new_start < 0) | |
50 | - | goto fail; | ||
51 | - | |||
52 | 945 | 22 | return 0; | |
53 | - | |||
54 | - | fail: | ||
55 | ##### | 23 | git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff"); | |
56 | ##### | 24 | return -1; | |
57 | - | } | ||
58 | - | |||
59 | - | typedef struct { | ||
60 | - | git_xdiff_output *xo; | ||
61 | - | git_patch_generated *patch; | ||
62 | - | git_diff_hunk hunk; | ||
63 | - | int old_lineno, new_lineno; | ||
64 | - | mmfile_t xd_old_data, xd_new_data; | ||
65 | - | } git_xdiff_info; | ||
66 | - | |||
67 | 8761 | 2 | static int diff_update_lines( | |
68 | - | git_xdiff_info *info, | ||
69 | - | git_diff_line *line, | ||
70 | - | const char *content, | ||
71 | - | size_t content_len) | ||
72 | - | { | ||
73 | 8761 | 2 | const char *scan = content, *scan_end = content + content_len; | |
74 | - | |||
75 | 297984 | 2,5,6 | for (line->num_lines = 0; scan < scan_end; ++scan) | |
76 | 289223 | 3 | if (*scan == '\n') | |
77 | 8761 | 4 | ++line->num_lines; | |
78 | - | |||
79 | 8761 | 7 | line->content = content; | |
80 | 8761 | 7 | line->content_len = content_len; | |
81 | - | |||
82 | - | /* expect " "/"-"/"+", then data */ | ||
83 | 8761 | 7 | switch (line->origin) { | |
84 | - | case GIT_DIFF_LINE_ADDITION: | ||
85 | - | case GIT_DIFF_LINE_DEL_EOFNL: | ||
86 | 3674 | 8 | line->old_lineno = -1; | |
87 | 3674 | 8 | line->new_lineno = info->new_lineno; | |
88 | 3674 | 8 | info->new_lineno += (int)line->num_lines; | |
89 | 3674 | 8 | break; | |
90 | - | case GIT_DIFF_LINE_DELETION: | ||
91 | - | case GIT_DIFF_LINE_ADD_EOFNL: | ||
92 | 3538 | 9 | line->old_lineno = info->old_lineno; | |
93 | 3538 | 9 | line->new_lineno = -1; | |
94 | 3538 | 9 | info->old_lineno += (int)line->num_lines; | |
95 | 3538 | 9 | break; | |
96 | - | case GIT_DIFF_LINE_CONTEXT: | ||
97 | - | case GIT_DIFF_LINE_CONTEXT_EOFNL: | ||
98 | 1549 | 10 | line->old_lineno = info->old_lineno; | |
99 | 1549 | 10 | line->new_lineno = info->new_lineno; | |
100 | 1549 | 10 | info->old_lineno += (int)line->num_lines; | |
101 | 1549 | 10 | info->new_lineno += (int)line->num_lines; | |
102 | 1549 | 10 | break; | |
103 | - | default: | ||
104 | ##### | 11 | git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x", | |
105 | ##### | 11 | (unsigned int)line->origin); | |
106 | ##### | 12 | return -1; | |
107 | - | } | ||
108 | - | |||
109 | 8761 | 13 | return 0; | |
110 | - | } | ||
111 | - | |||
112 | 9641 | 2 | static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) | |
113 | - | { | ||
114 | 9641 | 2 | git_xdiff_info *info = priv; | |
115 | 9641 | 2 | git_patch_generated *patch = info->patch; | |
116 | 9641 | 2 | const git_diff_delta *delta = patch->base.delta; | |
117 | 9641 | 2 | git_patch_generated_output *output = &info->xo->output; | |
118 | - | git_diff_line line; | ||
119 | - | size_t buffer_len; | ||
120 | - | |||
121 | 9641 | 2 | if (len == 1) { | |
122 | 945 | 3 | output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); | |
123 | 945 | 4 | if (output->error < 0) | |
124 | ##### | 5 | return output->error; | |
125 | - | |||
126 | 945 | 6 | info->hunk.header_len = bufs[0].size; | |
127 | 945 | 6 | if (info->hunk.header_len >= sizeof(info->hunk.header)) | |
128 | ##### | 7 | info->hunk.header_len = sizeof(info->hunk.header) - 1; | |
129 | - | |||
130 | - | /* Sanitize the hunk header in case there is invalid Unicode */ | ||
131 | 945 | 8 | buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, info->hunk.header_len); | |
132 | - | /* Sanitizing the hunk header may delete the newline, so add it back again if there is room */ | ||
133 | 945 | 9 | if (buffer_len < info->hunk.header_len) { | |
134 | 1 | 10 | bufs[0].ptr[buffer_len] = '\n'; | |
135 | 1 | 10 | buffer_len += 1; | |
136 | 1 | 10 | info->hunk.header_len = buffer_len; | |
137 | - | } | ||
138 | - | |||
139 | 945 | 11 | memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); | |
140 | 945 | 11 | info->hunk.header[info->hunk.header_len] = '\0'; | |
141 | - | |||
142 | 945 | 11,13 | if (output->hunk_cb != NULL && | |
143 | 945 | 12,12 | (output->error = output->hunk_cb( | |
144 | 945 | 12 | delta, &info->hunk, output->payload))) | |
145 | ##### | 14 | return output->error; | |
146 | - | |||
147 | 945 | 15 | info->old_lineno = info->hunk.old_start; | |
148 | 945 | 15 | info->new_lineno = info->hunk.new_start; | |
149 | - | } | ||
150 | - | |||
151 | 9641 | 16,17 | if (len == 2 || len == 3) { | |
152 | - | /* expect " "/"-"/"+", then data */ | ||
153 | 8696 | 18-23 | line.origin = | |
154 | 8696 | 18 | (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : | |
155 | 5053 | 19 | (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : | |
156 | - | GIT_DIFF_LINE_CONTEXT; | ||
157 | - | |||
158 | 8696 | 24 | if (line.origin == GIT_DIFF_LINE_ADDITION) | |
159 | 3643 | 25 | line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; | |
160 | 5053 | 26 | else if (line.origin == GIT_DIFF_LINE_DELETION) | |
161 | 3510 | 27 | line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; | |
162 | - | else | ||
163 | 1543 | 28 | line.content_offset = -1; | |
164 | - | |||
165 | 8696 | 29,29 | output->error = diff_update_lines( | |
166 | 8696 | 29,29 | info, &line, bufs[1].ptr, bufs[1].size); | |
167 | - | |||
168 | 8696 | 30,31 | if (!output->error && output->data_cb != NULL) | |
169 | 8696 | 32,32,33 | output->error = output->data_cb( | |
170 | 8696 | 32 | delta, &info->hunk, &line, output->payload); | |
171 | - | } | ||
172 | - | |||
173 | 9641 | 34,35 | if (len == 3 && !output->error) { | |
174 | - | /* If we have a '+' and a third buf, then we have added a line | ||
175 | - | * without a newline and the old code had one, so DEL_EOFNL. | ||
176 | - | * If we have a '-' and a third buf, then we have removed a line | ||
177 | - | * with out a newline but added a blank line, so ADD_EOFNL. | ||
178 | - | */ | ||
179 | 65 | 36-41 | line.origin = | |
180 | 65 | 36 | (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : | |
181 | 34 | 37 | (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : | |
182 | - | GIT_DIFF_LINE_CONTEXT_EOFNL; | ||
183 | - | |||
184 | 65 | 42 | line.content_offset = -1; | |
185 | - | |||
186 | 65 | 42,42 | output->error = diff_update_lines( | |
187 | 65 | 42,42 | info, &line, bufs[2].ptr, bufs[2].size); | |
188 | - | |||
189 | 65 | 43,44 | if (!output->error && output->data_cb != NULL) | |
190 | 65 | 45,45,46 | output->error = output->data_cb( | |
191 | 65 | 45 | delta, &info->hunk, &line, output->payload); | |
192 | - | } | ||
193 | - | |||
194 | 9641 | 47 | return output->error; | |
195 | - | } | ||
196 | - | |||
197 | 829 | 2 | static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) | |
198 | - | { | ||
199 | 829 | 2 | git_xdiff_output *xo = (git_xdiff_output *)output; | |
200 | - | git_xdiff_info info; | ||
201 | - | git_diff_find_context_payload findctxt; | ||
202 | - | |||
203 | 829 | 2 | memset(&info, 0, sizeof(info)); | |
204 | 829 | 2 | info.patch = patch; | |
205 | 829 | 2 | info.xo = xo; | |
206 | - | |||
207 | 829 | 2 | xo->callback.priv = &info; | |
208 | - | |||
209 | 829 | 2,3 | git_diff_find_context_init( | |
210 | 829 | 3 | &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); | |
211 | 829 | 4 | xo->config.find_func_priv = &findctxt; | |
212 | - | |||
213 | 829 | 4 | if (xo->config.find_func != NULL) | |
214 | 829 | 5 | xo->config.flags |= XDL_EMIT_FUNCNAMES; | |
215 | - | else | ||
216 | ##### | 6 | xo->config.flags &= ~XDL_EMIT_FUNCNAMES; | |
217 | - | |||
218 | - | /* TODO: check ofile.opts_flags to see if driver-specific per-file | ||
219 | - | * updates are needed to xo->params.flags | ||
220 | - | */ | ||
221 | - | |||
222 | 829 | 7 | git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); | |
223 | 829 | 8 | git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch); | |
224 | - | |||
225 | 829 | 9,10 | if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || | |
226 | 829 | 10 | info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { | |
227 | ##### | 11 | git_error_set(GIT_ERROR_INVALID, "files too large for diff"); | |
228 | ##### | 12 | return -1; | |
229 | - | } | ||
230 | - | |||
231 | 829 | 13,13 | xdl_diff(&info.xd_old_data, &info.xd_new_data, | |
232 | 829 | 13 | &xo->params, &xo->config, &xo->callback); | |
233 | - | |||
234 | 829 | 14 | git_diff_find_context_clear(&findctxt); | |
235 | - | |||
236 | 829 | 15 | return xo->output.error; | |
237 | - | } | ||
238 | - | |||
239 | 1266 | 2 | void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) | |
240 | - | { | ||
241 | 1266 | 2-4 | uint32_t flags = opts ? opts->flags : 0; | |
242 | - | |||
243 | 1266 | 5 | xo->output.diff_cb = git_xdiff; | |
244 | - | |||
245 | 1266 | 5-7 | xo->config.ctxlen = opts ? opts->context_lines : 3; | |
246 | 1266 | 8-10 | xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; | |
247 | - | |||
248 | 1266 | 11 | if (flags & GIT_DIFF_IGNORE_WHITESPACE) | |
249 | 2 | 12 | xo->params.flags |= XDF_WHITESPACE_FLAGS; | |
250 | 1266 | 13 | if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) | |
251 | 1 | 14 | xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; | |
252 | 1266 | 15 | if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) | |
253 | 1 | 16 | xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; | |
254 | 1266 | 17 | if (flags & GIT_DIFF_INDENT_HEURISTIC) | |
255 | ##### | 18 | xo->params.flags |= XDF_INDENT_HEURISTIC; | |
256 | - | |||
257 | 1266 | 19 | if (flags & GIT_DIFF_PATIENCE) | |
258 | 1 | 20 | xo->params.flags |= XDF_PATIENCE_DIFF; | |
259 | 1266 | 21 | if (flags & GIT_DIFF_MINIMAL) | |
260 | ##### | 22 | xo->params.flags |= XDF_NEED_MINIMAL; | |
261 | - | |||
262 | 1266 | 23 | xo->callback.outf = git_xdiff_cb; | |
263 | 1266 | 23 | } |