File Coverage

deps/libgit2/src/diff_xdiff.c
Criterion Covered Total %
statement 119 144 82.6
branch 60 90 66.6
condition n/a
subroutine n/a
pod n/a
total 179 234 76.5


line stmt bran cond sub pod time code
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 109           static int git_xdiff_scan_int(const char **str, int *value)
17             {
18 109           const char *scan = *str;
19 109           int v = 0, digits = 0;
20             /* find next digit */
21 370 50         for (scan = *str; *scan && !git__isdigit(*scan); scan++);
    100          
22             /* parse next number */
23 218 100         for (; git__isdigit(*scan); scan++, digits++)
24 109           v = (v * 10) + (*scan - '0');
25 109           *str = scan;
26 109           *value = v;
27 109 50         return (digits > 0) ? 0 : -1;
28             }
29              
30 38           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 38 50         if (*header != '@')
34 0           goto fail;
35 38 50         if (git_xdiff_scan_int(&header, &hunk->old_start) < 0)
36 0           goto fail;
37 38 100         if (*header == ',') {
38 28 50         if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0)
39 0           goto fail;
40             } else
41 10           hunk->old_lines = 1;
42 38 50         if (git_xdiff_scan_int(&header, &hunk->new_start) < 0)
43 0           goto fail;
44 38 100         if (*header == ',') {
45 5 50         if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0)
46 0           goto fail;
47             } else
48 33           hunk->new_lines = 1;
49 38 50         if (hunk->old_start < 0 || hunk->new_start < 0)
    50          
50             goto fail;
51              
52 38           return 0;
53              
54             fail:
55 0           git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff");
56 0           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 81           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 81           const char *scan = content, *scan_end = content + content_len;
74              
75 1738 100         for (line->num_lines = 0; scan < scan_end; ++scan)
76 1657 100         if (*scan == '\n')
77 81           ++line->num_lines;
78              
79 81           line->content = content;
80 81           line->content_len = content_len;
81              
82             /* expect " "/"-"/"+", then data */
83 81           switch (line->origin) {
84             case GIT_DIFF_LINE_ADDITION:
85             case GIT_DIFF_LINE_DEL_EOFNL:
86 55           line->old_lineno = -1;
87 55           line->new_lineno = info->new_lineno;
88 55           info->new_lineno += (int)line->num_lines;
89 55           break;
90             case GIT_DIFF_LINE_DELETION:
91             case GIT_DIFF_LINE_ADD_EOFNL:
92 20           line->old_lineno = info->old_lineno;
93 20           line->new_lineno = -1;
94 20           info->old_lineno += (int)line->num_lines;
95 20           break;
96             case GIT_DIFF_LINE_CONTEXT:
97             case GIT_DIFF_LINE_CONTEXT_EOFNL:
98 6           line->old_lineno = info->old_lineno;
99 6           line->new_lineno = info->new_lineno;
100 6           info->old_lineno += (int)line->num_lines;
101 6           info->new_lineno += (int)line->num_lines;
102 6           break;
103             default:
104 0           git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x",
105 0           (unsigned int)line->origin);
106 0           return -1;
107             }
108              
109 81           return 0;
110             }
111              
112 95           static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
113             {
114 95           git_xdiff_info *info = priv;
115 95           git_patch_generated *patch = info->patch;
116 95           const git_diff_delta *delta = patch->base.delta;
117 95           git_patch_generated_output *output = &info->xo->output;
118             git_diff_line line;
119             size_t buffer_len;
120              
121 95 100         if (len == 1) {
122 38           output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr);
123 38 50         if (output->error < 0)
124 0           return output->error;
125              
126 38           info->hunk.header_len = bufs[0].size;
127 38 50         if (info->hunk.header_len >= sizeof(info->hunk.header))
128 0           info->hunk.header_len = sizeof(info->hunk.header) - 1;
129              
130             /* Sanitize the hunk header in case there is invalid Unicode */
131 38           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 38 50         if (buffer_len < info->hunk.header_len) {
134 0           bufs[0].ptr[buffer_len] = '\n';
135 0           buffer_len += 1;
136 0           info->hunk.header_len = buffer_len;
137             }
138              
139 38           memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len);
140 38           info->hunk.header[info->hunk.header_len] = '\0';
141              
142 76           if (output->hunk_cb != NULL &&
143 38           (output->error = output->hunk_cb(
144 38           delta, &info->hunk, output->payload)))
145 0           return output->error;
146              
147 38           info->old_lineno = info->hunk.old_start;
148 38           info->new_lineno = info->hunk.new_start;
149             }
150              
151 95 100         if (len == 2 || len == 3) {
    100          
152             /* expect " "/"-"/"+", then data */
153 57 100         line.origin =
    100          
154 57           (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
155 20           (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
156             GIT_DIFF_LINE_CONTEXT;
157              
158 57 100         if (line.origin == GIT_DIFF_LINE_ADDITION)
159 37           line.content_offset = bufs[1].ptr - info->xd_new_data.ptr;
160 20 100         else if (line.origin == GIT_DIFF_LINE_DELETION)
161 14           line.content_offset = bufs[1].ptr - info->xd_old_data.ptr;
162             else
163 6           line.content_offset = -1;
164              
165 57           output->error = diff_update_lines(
166 114           info, &line, bufs[1].ptr, bufs[1].size);
167              
168 57 50         if (!output->error && output->data_cb != NULL)
    50          
169 57           output->error = output->data_cb(
170 57           delta, &info->hunk, &line, output->payload);
171             }
172              
173 95 100         if (len == 3 && !output->error) {
    50          
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 24 100         line.origin =
    50          
180 24           (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
181 6           (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
182             GIT_DIFF_LINE_CONTEXT_EOFNL;
183              
184 24           line.content_offset = -1;
185              
186 24           output->error = diff_update_lines(
187 48           info, &line, bufs[2].ptr, bufs[2].size);
188              
189 24 50         if (!output->error && output->data_cb != NULL)
    50          
190 24           output->error = output->data_cb(
191 24           delta, &info->hunk, &line, output->payload);
192             }
193              
194 95           return output->error;
195             }
196              
197 38           static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch)
198             {
199 38           git_xdiff_output *xo = (git_xdiff_output *)output;
200             git_xdiff_info info;
201             git_diff_find_context_payload findctxt;
202              
203 38           memset(&info, 0, sizeof(info));
204 38           info.patch = patch;
205 38           info.xo = xo;
206              
207 38           xo->callback.priv = &info;
208              
209 38           git_diff_find_context_init(
210 38           &xo->config.find_func, &findctxt, git_patch_generated_driver(patch));
211 38           xo->config.find_func_priv = &findctxt;
212              
213 38 50         if (xo->config.find_func != NULL)
214 38           xo->config.flags |= XDL_EMIT_FUNCNAMES;
215             else
216 0           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 38           git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
223 38           git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
224              
225 38 50         if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
    50          
226 38           info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
227 0           git_error_set(GIT_ERROR_INVALID, "files too large for diff");
228 0           return -1;
229             }
230              
231 38           xdl_diff(&info.xd_old_data, &info.xd_new_data,
232 38           &xo->params, &xo->config, &xo->callback);
233              
234 38           git_diff_find_context_clear(&findctxt);
235              
236 38           return xo->output.error;
237             }
238              
239 50           void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts)
240             {
241 50 50         uint32_t flags = opts ? opts->flags : 0;
242              
243 50           xo->output.diff_cb = git_xdiff;
244              
245 50 50         xo->config.ctxlen = opts ? opts->context_lines : 3;
246 50 50         xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0;
247              
248 50 50         if (flags & GIT_DIFF_IGNORE_WHITESPACE)
249 0           xo->params.flags |= XDF_WHITESPACE_FLAGS;
250 50 50         if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
251 0           xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
252 50 50         if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
253 0           xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
254 50 50         if (flags & GIT_DIFF_INDENT_HEURISTIC)
255 0           xo->params.flags |= XDF_INDENT_HEURISTIC;
256              
257 50 50         if (flags & GIT_DIFF_PATIENCE)
258 0           xo->params.flags |= XDF_PATIENCE_DIFF;
259 50 50         if (flags & GIT_DIFF_MINIMAL)
260 0           xo->params.flags |= XDF_NEED_MINIMAL;
261              
262 50           xo->callback.outf = git_xdiff_cb;
263 50           }