strbuf: allow zero-length lines
[git.git] / diff-helper.c
blob3ef57060d62054365895ff592ef4c3bd9d54fe17
1 /*
2 * Copyright (C) 2005 Junio C Hamano
3 */
4 #include <limits.h>
5 #include "cache.h"
6 #include "strbuf.h"
7 #include "diff.h"
9 static int matches_pathspec(const char *name, const char **spec, int cnt)
11 int i;
12 int namelen = strlen(name);
13 for (i = 0; i < cnt; i++) {
14 int speclen = strlen(spec[i]);
15 if (! strncmp(spec[i], name, speclen) &&
16 speclen <= namelen &&
17 (name[speclen] == 0 ||
18 name[speclen] == '/'))
19 return 1;
21 return 0;
24 static int detect_rename = 0;
27 * We do not detect circular renames. Just hold created and deleted
28 * entries and later attempt to match them up. If they do not match,
29 * then spit them out as deletes or creates as original.
32 static struct diff_spec_hold {
33 struct diff_spec_hold *next;
34 struct diff_spec_hold *matched;
35 struct diff_spec old, new;
36 char path[1];
37 } *createdfile, *deletedfile;
39 static void hold_spec(const char *path,
40 struct diff_spec *old, struct diff_spec *new)
42 struct diff_spec_hold **list, *elem;
43 list = (! old->file_valid) ? &createdfile : &deletedfile;
44 elem = xmalloc(sizeof(*elem) + strlen(path));
45 strcpy(elem->path, path);
46 elem->next = *list;
47 *list = elem;
48 elem->old = *old;
49 elem->new = *new;
50 elem->matched = 0;
53 #define MINIMUM_SCORE 7000
54 int estimate_similarity(struct diff_spec *one, struct diff_spec *two)
56 /* Return how similar they are, representing the score as an
57 * integer between 0 and 10000.
59 * This version is very dumb and detects exact matches only.
60 * Wnen Nico's delta stuff gets in, I'll use the delta
61 * algorithm to estimate the similarity score in core.
64 if (one->sha1_valid && two->sha1_valid &&
65 !memcmp(one->blob_sha1, two->blob_sha1, 20))
66 return 10000;
67 return 0;
70 static void flush_renames(const char **spec, int cnt, int reverse)
72 struct diff_spec_hold *rename_src, *rename_dst, *elem;
73 struct diff_spec_hold *leftover = NULL;
74 int score, best_score;
76 while (createdfile) {
77 rename_dst = createdfile;
78 createdfile = rename_dst->next;
79 best_score = MINIMUM_SCORE;
80 rename_src = NULL;
81 for (elem = deletedfile;
82 elem;
83 elem = elem->next) {
84 if (elem->matched)
85 continue;
86 score = estimate_similarity(&elem->old,
87 &rename_dst->new);
88 if (best_score < score) {
89 rename_src = elem;
90 best_score = score;
93 if (rename_src) {
94 rename_src->matched = rename_dst;
95 rename_dst->matched = rename_src;
97 if (!cnt ||
98 matches_pathspec(rename_src->path, spec, cnt) ||
99 matches_pathspec(rename_dst->path, spec, cnt)) {
100 if (reverse)
101 run_external_diff(rename_dst->path,
102 rename_src->path,
103 &rename_dst->new,
104 &rename_src->old);
105 else
106 run_external_diff(rename_src->path,
107 rename_dst->path,
108 &rename_src->old,
109 &rename_dst->new);
112 else {
113 rename_dst->next = leftover;
114 leftover = rename_dst;
118 /* unmatched deletes */
119 for (elem = deletedfile; elem; elem = elem->next) {
120 if (elem->matched)
121 continue;
122 if (!cnt ||
123 matches_pathspec(elem->path, spec, cnt)) {
124 if (reverse)
125 run_external_diff(elem->path, NULL,
126 &elem->new, &elem->old);
127 else
128 run_external_diff(elem->path, NULL,
129 &elem->old, &elem->new);
133 /* unmatched creates */
134 for (elem = leftover; elem; elem = elem->next) {
135 if (!cnt ||
136 matches_pathspec(elem->path, spec, cnt)) {
137 if (reverse)
138 run_external_diff(elem->path, NULL,
139 &elem->new, &elem->old);
140 else
141 run_external_diff(elem->path, NULL,
142 &elem->old, &elem->new);
147 static int parse_oneside_change(const char *cp, struct diff_spec *one,
148 char *path)
150 int ch;
152 one->file_valid = one->sha1_valid = 1;
153 one->mode = 0;
154 while ((ch = *cp) && '0' <= ch && ch <= '7') {
155 one->mode = (one->mode << 3) | (ch - '0');
156 cp++;
159 if (strncmp(cp, "\tblob\t", 6))
160 return -1;
161 cp += 6;
162 if (get_sha1_hex(cp, one->blob_sha1))
163 return -1;
164 cp += 40;
165 if (*cp++ != '\t')
166 return -1;
167 strcpy(path, cp);
168 return 0;
171 static int parse_diff_raw_output(const char *buf,
172 const char **spec, int cnt, int reverse)
174 struct diff_spec old, new;
175 char path[PATH_MAX];
176 const char *cp = buf;
177 int ch;
179 switch (*cp++) {
180 case 'U':
181 if (!cnt || matches_pathspec(cp + 1, spec, cnt))
182 diff_unmerge(cp + 1);
183 return 0;
184 case '+':
185 old.file_valid = 0;
186 parse_oneside_change(cp, &new, path);
187 break;
188 case '-':
189 new.file_valid = 0;
190 parse_oneside_change(cp, &old, path);
191 break;
192 case '*':
193 old.file_valid = old.sha1_valid =
194 new.file_valid = new.sha1_valid = 1;
195 old.mode = new.mode = 0;
196 while ((ch = *cp) && ('0' <= ch && ch <= '7')) {
197 old.mode = (old.mode << 3) | (ch - '0');
198 cp++;
200 if (strncmp(cp, "->", 2))
201 return -1;
202 cp += 2;
203 while ((ch = *cp) && ('0' <= ch && ch <= '7')) {
204 new.mode = (new.mode << 3) | (ch - '0');
205 cp++;
207 if (strncmp(cp, "\tblob\t", 6))
208 return -1;
209 cp += 6;
210 if (get_sha1_hex(cp, old.blob_sha1))
211 return -1;
212 cp += 40;
213 if (strncmp(cp, "->", 2))
214 return -1;
215 cp += 2;
216 if (get_sha1_hex(cp, new.blob_sha1))
217 return -1;
218 cp += 40;
219 if (*cp++ != '\t')
220 return -1;
221 strcpy(path, cp);
222 break;
223 default:
224 return -1;
227 if (detect_rename && old.file_valid != new.file_valid) {
228 /* hold these */
229 hold_spec(path, &old, &new);
230 return 0;
233 if (!cnt || matches_pathspec(path, spec, cnt)) {
234 if (reverse)
235 run_external_diff(path, NULL, &new, &old);
236 else
237 run_external_diff(path, NULL, &old, &new);
239 return 0;
242 static const char *diff_helper_usage =
243 "git-diff-helper [-r] [-R] [-z] paths...";
245 int main(int ac, const char **av) {
246 struct strbuf sb;
247 int reverse = 0;
248 int line_termination = '\n';
250 strbuf_init(&sb);
252 while (1 < ac && av[1][0] == '-') {
253 if (av[1][1] == 'R')
254 reverse = 1;
255 else if (av[1][1] == 'z')
256 line_termination = 0;
257 else if (av[1][1] == 'r')
258 detect_rename = 1;
259 else
260 usage(diff_helper_usage);
261 ac--; av++;
263 /* the remaining parameters are paths patterns */
265 while (1) {
266 int status;
267 read_line(&sb, stdin, line_termination);
268 if (sb.eof)
269 break;
270 status = parse_diff_raw_output(sb.buf, av+1, ac-1, reverse);
271 if (status)
272 fprintf(stderr, "cannot parse %s\n", sb.buf);
275 if (detect_rename)
276 flush_renames(av+1, ac-1, reverse);
277 return 0;