2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "diff_xdiff.h"
11 #include "git2/errors.h"
13 #include "diff_driver.h"
14 #include "patch_generate.h"
16 static int git_xdiff_scan_int(const char **str
, int *value
)
18 const char *scan
= *str
;
19 int v
= 0, digits
= 0;
21 for (scan
= *str
; *scan
&& !git__isdigit(*scan
); scan
++);
22 /* parse next number */
23 for (; git__isdigit(*scan
); scan
++, digits
++)
24 v
= (v
* 10) + (*scan
- '0');
27 return (digits
> 0) ? 0 : -1;
30 static int git_xdiff_parse_hunk(git_diff_hunk
*hunk
, const char *header
)
32 /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
35 if (git_xdiff_scan_int(&header
, &hunk
->old_start
) < 0)
38 if (git_xdiff_scan_int(&header
, &hunk
->old_lines
) < 0)
42 if (git_xdiff_scan_int(&header
, &hunk
->new_start
) < 0)
45 if (git_xdiff_scan_int(&header
, &hunk
->new_lines
) < 0)
49 if (hunk
->old_start
< 0 || hunk
->new_start
< 0)
55 git_error_set(GIT_ERROR_INVALID
, "malformed hunk header from xdiff");
61 git_patch_generated
*patch
;
63 int old_lineno
, new_lineno
;
64 mmfile_t xd_old_data
, xd_new_data
;
67 static int diff_update_lines(
73 const char *scan
= content
, *scan_end
= content
+ content_len
;
75 for (line
->num_lines
= 0; scan
< scan_end
; ++scan
)
79 line
->content
= content
;
80 line
->content_len
= content_len
;
82 /* expect " "/"-"/"+", then data */
83 switch (line
->origin
) {
84 case GIT_DIFF_LINE_ADDITION
:
85 case GIT_DIFF_LINE_DEL_EOFNL
:
86 line
->old_lineno
= -1;
87 line
->new_lineno
= info
->new_lineno
;
88 info
->new_lineno
+= (int)line
->num_lines
;
90 case GIT_DIFF_LINE_DELETION
:
91 case GIT_DIFF_LINE_ADD_EOFNL
:
92 line
->old_lineno
= info
->old_lineno
;
93 line
->new_lineno
= -1;
94 info
->old_lineno
+= (int)line
->num_lines
;
96 case GIT_DIFF_LINE_CONTEXT
:
97 case GIT_DIFF_LINE_CONTEXT_EOFNL
:
98 line
->old_lineno
= info
->old_lineno
;
99 line
->new_lineno
= info
->new_lineno
;
100 info
->old_lineno
+= (int)line
->num_lines
;
101 info
->new_lineno
+= (int)line
->num_lines
;
104 git_error_set(GIT_ERROR_INVALID
, "unknown diff line origin %02x",
105 (unsigned int)line
->origin
);
112 static int git_xdiff_cb(void *priv
, mmbuffer_t
*bufs
, int len
)
114 git_xdiff_info
*info
= priv
;
115 git_patch_generated
*patch
= info
->patch
;
116 const git_diff_delta
*delta
= patch
->base
.delta
;
117 git_patch_generated_output
*output
= &info
->xo
->output
;
122 output
->error
= git_xdiff_parse_hunk(&info
->hunk
, bufs
[0].ptr
);
123 if (output
->error
< 0)
124 return output
->error
;
126 info
->hunk
.header_len
= bufs
[0].size
;
127 if (info
->hunk
.header_len
>= sizeof(info
->hunk
.header
))
128 info
->hunk
.header_len
= sizeof(info
->hunk
.header
) - 1;
130 /* Sanitize the hunk header in case there is invalid Unicode */
131 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 if (buffer_len
< info
->hunk
.header_len
) {
134 bufs
[0].ptr
[buffer_len
] = '\n';
136 info
->hunk
.header_len
= buffer_len
;
139 memcpy(info
->hunk
.header
, bufs
[0].ptr
, info
->hunk
.header_len
);
140 info
->hunk
.header
[info
->hunk
.header_len
] = '\0';
142 if (output
->hunk_cb
!= NULL
&&
143 (output
->error
= output
->hunk_cb(
144 delta
, &info
->hunk
, output
->payload
)))
145 return output
->error
;
147 info
->old_lineno
= info
->hunk
.old_start
;
148 info
->new_lineno
= info
->hunk
.new_start
;
151 if (len
== 2 || len
== 3) {
152 /* expect " "/"-"/"+", then data */
154 (*bufs
[0].ptr
== '+') ? GIT_DIFF_LINE_ADDITION
:
155 (*bufs
[0].ptr
== '-') ? GIT_DIFF_LINE_DELETION
:
156 GIT_DIFF_LINE_CONTEXT
;
158 if (line
.origin
== GIT_DIFF_LINE_ADDITION
)
159 line
.content_offset
= bufs
[1].ptr
- info
->xd_new_data
.ptr
;
160 else if (line
.origin
== GIT_DIFF_LINE_DELETION
)
161 line
.content_offset
= bufs
[1].ptr
- info
->xd_old_data
.ptr
;
163 line
.content_offset
= -1;
165 output
->error
= diff_update_lines(
166 info
, &line
, bufs
[1].ptr
, bufs
[1].size
);
168 if (!output
->error
&& output
->data_cb
!= NULL
)
169 output
->error
= output
->data_cb(
170 delta
, &info
->hunk
, &line
, output
->payload
);
173 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.
180 (*bufs
[0].ptr
== '+') ? GIT_DIFF_LINE_DEL_EOFNL
:
181 (*bufs
[0].ptr
== '-') ? GIT_DIFF_LINE_ADD_EOFNL
:
182 GIT_DIFF_LINE_CONTEXT_EOFNL
;
184 line
.content_offset
= -1;
186 output
->error
= diff_update_lines(
187 info
, &line
, bufs
[2].ptr
, bufs
[2].size
);
189 if (!output
->error
&& output
->data_cb
!= NULL
)
190 output
->error
= output
->data_cb(
191 delta
, &info
->hunk
, &line
, output
->payload
);
194 return output
->error
;
197 static int git_xdiff(git_patch_generated_output
*output
, git_patch_generated
*patch
)
199 git_xdiff_output
*xo
= (git_xdiff_output
*)output
;
201 git_diff_find_context_payload findctxt
;
203 memset(&info
, 0, sizeof(info
));
207 xo
->callback
.priv
= &info
;
209 git_diff_find_context_init(
210 &xo
->config
.find_func
, &findctxt
, git_patch_generated_driver(patch
));
211 xo
->config
.find_func_priv
= &findctxt
;
213 if (xo
->config
.find_func
!= NULL
)
214 xo
->config
.flags
|= XDL_EMIT_FUNCNAMES
;
216 xo
->config
.flags
&= ~XDL_EMIT_FUNCNAMES
;
218 /* TODO: check ofile.opts_flags to see if driver-specific per-file
219 * updates are needed to xo->params.flags
222 git_patch_generated_old_data(&info
.xd_old_data
.ptr
, &info
.xd_old_data
.size
, patch
);
223 git_patch_generated_new_data(&info
.xd_new_data
.ptr
, &info
.xd_new_data
.size
, patch
);
225 if (info
.xd_old_data
.size
> GIT_XDIFF_MAX_SIZE
||
226 info
.xd_new_data
.size
> GIT_XDIFF_MAX_SIZE
) {
227 git_error_set(GIT_ERROR_INVALID
, "files too large for diff");
231 xdl_diff(&info
.xd_old_data
, &info
.xd_new_data
,
232 &xo
->params
, &xo
->config
, &xo
->callback
);
234 git_diff_find_context_clear(&findctxt
);
236 return xo
->output
.error
;
239 void git_xdiff_init(git_xdiff_output
*xo
, const git_diff_options
*opts
)
241 uint32_t flags
= opts
? opts
->flags
: 0;
243 xo
->output
.diff_cb
= git_xdiff
;
245 xo
->config
.ctxlen
= opts
? opts
->context_lines
: 3;
246 xo
->config
.interhunkctxlen
= opts
? opts
->interhunk_lines
: 0;
248 if (flags
& GIT_DIFF_IGNORE_WHITESPACE
)
249 xo
->params
.flags
|= XDF_WHITESPACE_FLAGS
;
250 if (flags
& GIT_DIFF_IGNORE_WHITESPACE_CHANGE
)
251 xo
->params
.flags
|= XDF_IGNORE_WHITESPACE_CHANGE
;
252 if (flags
& GIT_DIFF_IGNORE_WHITESPACE_EOL
)
253 xo
->params
.flags
|= XDF_IGNORE_WHITESPACE_AT_EOL
;
254 if (flags
& GIT_DIFF_INDENT_HEURISTIC
)
255 xo
->params
.flags
|= XDF_INDENT_HEURISTIC
;
257 if (flags
& GIT_DIFF_PATIENCE
)
258 xo
->params
.flags
|= XDF_PATIENCE_DIFF
;
259 if (flags
& GIT_DIFF_MINIMAL
)
260 xo
->params
.flags
|= XDF_NEED_MINIMAL
;
262 xo
->callback
.outf
= git_xdiff_cb
;