]> git.proxmox.com Git - libgit2.git/blame - src/diff_xdiff.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / diff_xdiff.c
CommitLineData
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
15static 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 29static 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
53fail:
ac3d33df 54 git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff");
96869a4e 55 return -1;
114f5a6c
RB
56}
57
58typedef 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
66static 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
111static 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 196static 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
238void 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}