2 * Helper functions for tree diff generation
9 static char *malloc_base(const char *base
, int baselen
, const char *path
, int pathlen
)
11 char *newbase
= xmalloc(baselen
+ pathlen
+ 2);
12 memcpy(newbase
, base
, baselen
);
13 memcpy(newbase
+ baselen
, path
, pathlen
);
14 memcpy(newbase
+ baselen
+ pathlen
, "/", 2);
18 static void show_entry(struct diff_options
*opt
, const char *prefix
, struct tree_desc
*desc
,
19 const char *base
, int baselen
);
21 static int compare_tree_entry(struct tree_desc
*t1
, struct tree_desc
*t2
, const char *base
, int baselen
, struct diff_options
*opt
)
23 unsigned mode1
, mode2
;
24 const char *path1
, *path2
;
25 const unsigned char *sha1
, *sha2
;
26 int cmp
, pathlen1
, pathlen2
;
28 sha1
= tree_entry_extract(t1
, &path1
, &mode1
);
29 sha2
= tree_entry_extract(t2
, &path2
, &mode2
);
31 pathlen1
= tree_entry_len(path1
, sha1
);
32 pathlen2
= tree_entry_len(path2
, sha2
);
33 cmp
= base_name_compare(path1
, pathlen1
, mode1
, path2
, pathlen2
, mode2
);
35 show_entry(opt
, "-", t1
, base
, baselen
);
39 show_entry(opt
, "+", t2
, base
, baselen
);
42 if (!DIFF_OPT_TST(opt
, FIND_COPIES_HARDER
) && !hashcmp(sha1
, sha2
) && mode1
== mode2
)
46 * If the filemode has changed to/from a directory from/to a regular
47 * file, we need to consider it a remove and an add.
49 if (S_ISDIR(mode1
) != S_ISDIR(mode2
)) {
50 show_entry(opt
, "-", t1
, base
, baselen
);
51 show_entry(opt
, "+", t2
, base
, baselen
);
55 if (DIFF_OPT_TST(opt
, RECURSIVE
) && S_ISDIR(mode1
)) {
57 char *newbase
= malloc_base(base
, baselen
, path1
, pathlen1
);
58 if (DIFF_OPT_TST(opt
, TREE_IN_RECURSIVE
))
59 opt
->change(opt
, mode1
, mode2
,
60 sha1
, sha2
, base
, path1
);
61 retval
= diff_tree_sha1(sha1
, sha2
, newbase
, opt
);
66 opt
->change(opt
, mode1
, mode2
, sha1
, sha2
, base
, path1
);
71 * Is a tree entry interesting given the pathspec we have?
74 * - 2 for "yes, and all subsequent entries will be"
77 * - negative for "no, and no subsequent entries will be either"
79 static int tree_entry_interesting(struct tree_desc
*desc
, const char *base
, int baselen
, struct diff_options
*opt
)
82 const unsigned char *sha1
;
86 int never_interesting
= -1;
91 sha1
= tree_entry_extract(desc
, &path
, &mode
);
93 pathlen
= tree_entry_len(path
, sha1
);
95 for (i
= 0; i
< opt
->nr_paths
; i
++) {
96 const char *match
= opt
->paths
[i
];
97 int matchlen
= opt
->pathlens
[i
];
98 int m
= -1; /* signals that we haven't called strncmp() */
100 if (baselen
>= matchlen
) {
101 /* If it doesn't match, move along... */
102 if (strncmp(base
, match
, matchlen
))
106 * The base is a subdirectory of a path which
107 * was specified, so all of them are interesting.
112 /* Does the base match? */
113 if (strncmp(base
, match
, baselen
))
119 if (never_interesting
) {
121 * We have not seen any match that sorts later
122 * than the current path.
126 * Does match sort strictly earlier than path
127 * with their common parts?
129 m
= strncmp(match
, path
,
130 (matchlen
< pathlen
) ? matchlen
: pathlen
);
135 * If we come here even once, that means there is at
136 * least one pathspec that would sort equal to or
137 * later than the path we are currently looking at.
138 * In other words, if we have never reached this point
139 * after iterating all pathspecs, it means all
140 * pathspecs are either outside of base, or inside the
141 * base but sorts strictly earlier than the current
142 * one. In either case, they will never match the
143 * subsequent entries. In such a case, we initialized
144 * the variable to -1 and that is what will be
145 * returned, allowing the caller to terminate early.
147 never_interesting
= 0;
150 if (pathlen
> matchlen
)
153 if (matchlen
> pathlen
) {
154 if (match
[pathlen
] != '/')
162 * we cheated and did not do strncmp(), so we do
165 m
= strncmp(match
, path
, pathlen
);
168 * If common part matched earlier then it is a hit,
169 * because we rejected the case where path is not a
170 * leading directory and is shorter than match.
175 return never_interesting
; /* No matches */
178 /* A whole sub-tree went away or appeared */
179 static void show_tree(struct diff_options
*opt
, const char *prefix
, struct tree_desc
*desc
, const char *base
, int baselen
)
181 int all_interesting
= 0;
188 show
= tree_entry_interesting(desc
, base
, baselen
,
196 show_entry(opt
, prefix
, desc
, base
, baselen
);
197 update_tree_entry(desc
);
201 /* A file entry went away or appeared */
202 static void show_entry(struct diff_options
*opt
, const char *prefix
, struct tree_desc
*desc
,
203 const char *base
, int baselen
)
207 const unsigned char *sha1
= tree_entry_extract(desc
, &path
, &mode
);
209 if (DIFF_OPT_TST(opt
, RECURSIVE
) && S_ISDIR(mode
)) {
210 enum object_type type
;
211 int pathlen
= tree_entry_len(path
, sha1
);
212 char *newbase
= malloc_base(base
, baselen
, path
, pathlen
);
213 struct tree_desc inner
;
217 tree
= read_sha1_file(sha1
, &type
, &size
);
218 if (!tree
|| type
!= OBJ_TREE
)
219 die("corrupt tree sha %s", sha1_to_hex(sha1
));
221 init_tree_desc(&inner
, tree
, size
);
222 show_tree(opt
, prefix
, &inner
, newbase
, baselen
+ 1 + pathlen
);
227 opt
->add_remove(opt
, prefix
[0], mode
, sha1
, base
, path
);
231 static void skip_uninteresting(struct tree_desc
*t
, const char *base
, int baselen
, struct diff_options
*opt
)
233 int all_interesting
= 0;
240 show
= tree_entry_interesting(t
, base
, baselen
, opt
);
245 update_tree_entry(t
);
255 int diff_tree(struct tree_desc
*t1
, struct tree_desc
*t2
, const char *base
, struct diff_options
*opt
)
257 int baselen
= strlen(base
);
260 if (DIFF_OPT_TST(opt
, QUIET
) && DIFF_OPT_TST(opt
, HAS_CHANGES
))
263 skip_uninteresting(t1
, base
, baselen
, opt
);
264 skip_uninteresting(t2
, base
, baselen
, opt
);
269 show_entry(opt
, "+", t2
, base
, baselen
);
270 update_tree_entry(t2
);
274 show_entry(opt
, "-", t1
, base
, baselen
);
275 update_tree_entry(t1
);
278 switch (compare_tree_entry(t1
, t2
, base
, baselen
, opt
)) {
280 update_tree_entry(t1
);
283 update_tree_entry(t1
);
286 update_tree_entry(t2
);
289 die("git-diff-tree: internal error");
295 * Does it look like the resulting diff might be due to a rename?
297 * - not a valid previous file
299 static inline int diff_might_be_rename(void)
301 return diff_queued_diff
.nr
== 1 &&
302 !DIFF_FILE_VALID(diff_queued_diff
.queue
[0]->one
);
305 static void try_to_follow_renames(struct tree_desc
*t1
, struct tree_desc
*t2
, const char *base
, struct diff_options
*opt
)
307 struct diff_options diff_opts
;
308 struct diff_queue_struct
*q
= &diff_queued_diff
;
309 struct diff_filepair
*choice
;
310 const char *paths
[1];
313 /* Remove the file creation entry from the diff queue, and remember it */
314 choice
= q
->queue
[0];
317 diff_setup(&diff_opts
);
318 DIFF_OPT_SET(&diff_opts
, RECURSIVE
);
319 diff_opts
.detect_rename
= DIFF_DETECT_RENAME
;
320 diff_opts
.output_format
= DIFF_FORMAT_NO_OUTPUT
;
321 diff_opts
.single_follow
= opt
->paths
[0];
322 diff_opts
.break_opt
= opt
->break_opt
;
324 diff_tree_setup_paths(paths
, &diff_opts
);
325 if (diff_setup_done(&diff_opts
) < 0)
326 die("unable to set up diff options to follow renames");
327 diff_tree(t1
, t2
, base
, &diff_opts
);
328 diffcore_std(&diff_opts
);
330 /* Go through the new set of filepairing, and see if we find a more interesting one */
331 for (i
= 0; i
< q
->nr
; i
++) {
332 struct diff_filepair
*p
= q
->queue
[i
];
335 * Found a source? Not only do we use that for the new
336 * diff_queued_diff, we will also use that as the path in
339 if ((p
->status
== 'R' || p
->status
== 'C') && !strcmp(p
->two
->path
, opt
->paths
[0])) {
340 /* Switch the file-pairs around */
341 q
->queue
[i
] = choice
;
344 /* Update the path we use from now on.. */
345 opt
->paths
[0] = xstrdup(p
->one
->path
);
346 diff_tree_setup_paths(opt
->paths
, opt
);
352 * Then, discard all the non-relevane file pairs...
354 for (i
= 0; i
< q
->nr
; i
++) {
355 struct diff_filepair
*p
= q
->queue
[i
];
356 diff_free_filepair(p
);
360 * .. and re-instate the one we want (which might be either the
361 * original one, or the rename/copy we found)
363 q
->queue
[0] = choice
;
367 int diff_tree_sha1(const unsigned char *old
, const unsigned char *new, const char *base
, struct diff_options
*opt
)
370 struct tree_desc t1
, t2
;
371 unsigned long size1
, size2
;
374 tree1
= read_object_with_reference(old
, tree_type
, &size1
, NULL
);
376 die("unable to read source tree (%s)", sha1_to_hex(old
));
377 tree2
= read_object_with_reference(new, tree_type
, &size2
, NULL
);
379 die("unable to read destination tree (%s)", sha1_to_hex(new));
380 init_tree_desc(&t1
, tree1
, size1
);
381 init_tree_desc(&t2
, tree2
, size2
);
382 retval
= diff_tree(&t1
, &t2
, base
, opt
);
383 if (DIFF_OPT_TST(opt
, FOLLOW_RENAMES
) && diff_might_be_rename()) {
384 init_tree_desc(&t1
, tree1
, size1
);
385 init_tree_desc(&t2
, tree2
, size2
);
386 try_to_follow_renames(&t1
, &t2
, base
, opt
);
393 int diff_root_tree_sha1(const unsigned char *new, const char *base
, struct diff_options
*opt
)
398 struct tree_desc empty
, real
;
400 tree
= read_object_with_reference(new, tree_type
, &size
, NULL
);
402 die("unable to read root tree (%s)", sha1_to_hex(new));
403 init_tree_desc(&real
, tree
, size
);
405 init_tree_desc(&empty
, "", 0);
406 retval
= diff_tree(&empty
, &real
, base
, opt
);
411 static int count_paths(const char **paths
)
419 void diff_tree_release_paths(struct diff_options
*opt
)
424 void diff_tree_setup_paths(const char **p
, struct diff_options
*opt
)
427 opt
->pathlens
= NULL
;
434 opt
->nr_paths
= count_paths(p
);
435 if (opt
->nr_paths
== 0) {
436 opt
->pathlens
= NULL
;
439 opt
->pathlens
= xmalloc(opt
->nr_paths
* sizeof(int));
440 for (i
=0; i
< opt
->nr_paths
; i
++)
441 opt
->pathlens
[i
] = strlen(p
[i
]);