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