Skip to content

Commit 1066c2c

Browse files
committed
Merge branch 'lt/follow'
* lt/follow: Fix up "git log --follow" a bit.. Finally implement "git log --follow"
2 parents fc746df + 9f38e1e commit 1066c2c

File tree

5 files changed

+89
-1
lines changed

5 files changed

+89
-1
lines changed

builtin-log.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
5858
argc = setup_revisions(argc, argv, rev, "HEAD");
5959
if (rev->diffopt.pickaxe || rev->diffopt.filter)
6060
rev->always_show_header = 0;
61+
if (rev->diffopt.follow_renames) {
62+
rev->always_show_header = 0;
63+
if (rev->diffopt.nr_paths != 1)
64+
usage("git logs can only follow renames on one pathname at a time");
65+
}
6166
for (i = 1; i < argc; i++) {
6267
const char *arg = argv[i];
6368
if (!strcmp(arg, "--decorate")) {

diff.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
22102210
}
22112211
else if (!strcmp(arg, "--find-copies-harder"))
22122212
options->find_copies_harder = 1;
2213+
else if (!strcmp(arg, "--follow"))
2214+
options->follow_renames = 1;
22132215
else if (!strcmp(arg, "--abbrev"))
22142216
options->abbrev = DEFAULT_ABBREV;
22152217
else if (!prefixcmp(arg, "--abbrev=")) {

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct diff_options {
5555
full_index:1,
5656
silent_on_remove:1,
5757
find_copies_harder:1,
58+
follow_renames:1,
5859
color_diff:1,
5960
color_diff_words:1,
6061
has_changes:1,

revision.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
12301230

12311231
if (revs->prune_data) {
12321232
diff_tree_setup_paths(revs->prune_data, &revs->pruning);
1233-
revs->prune_fn = try_to_simplify_commit;
1233+
/* Can't prune commits with rename following: the paths change.. */
1234+
if (!revs->diffopt.follow_renames)
1235+
revs->prune_fn = try_to_simplify_commit;
12341236
if (!revs->full_diff)
12351237
diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
12361238
}

tree-diff.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
#include "cache.h"
55
#include "diff.h"
6+
#include "diffcore.h"
67
#include "tree.h"
78

89
static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
@@ -290,6 +291,78 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
290291
return 0;
291292
}
292293

294+
/*
295+
* Does it look like the resulting diff might be due to a rename?
296+
* - single entry
297+
* - not a valid previous file
298+
*/
299+
static inline int diff_might_be_rename(void)
300+
{
301+
return diff_queued_diff.nr == 1 &&
302+
!DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
303+
}
304+
305+
static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
306+
{
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];
311+
int i;
312+
313+
/* Remove the file creation entry from the diff queue, and remember it */
314+
choice = q->queue[0];
315+
q->nr = 0;
316+
317+
diff_setup(&diff_opts);
318+
diff_opts.recursive = 1;
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+
paths[0] = NULL;
323+
diff_tree_setup_paths(paths, &diff_opts);
324+
if (diff_setup_done(&diff_opts) < 0)
325+
die("unable to set up diff options to follow renames");
326+
diff_tree(t1, t2, base, &diff_opts);
327+
diffcore_std(&diff_opts);
328+
329+
/* Go through the new set of filepairing, and see if we find a more interesting one */
330+
for (i = 0; i < q->nr; i++) {
331+
struct diff_filepair *p = q->queue[i];
332+
333+
/*
334+
* Found a source? Not only do we use that for the new
335+
* diff_queued_diff, we will also use that as the path in
336+
* the future!
337+
*/
338+
if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
339+
/* Switch the file-pairs around */
340+
q->queue[i] = choice;
341+
choice = p;
342+
343+
/* Update the path we use from now on.. */
344+
opt->paths[0] = xstrdup(p->one->path);
345+
diff_tree_setup_paths(opt->paths, opt);
346+
break;
347+
}
348+
}
349+
350+
/*
351+
* Then, discard all the non-relevane file pairs...
352+
*/
353+
for (i = 0; i < q->nr; i++) {
354+
struct diff_filepair *p = q->queue[i];
355+
diff_free_filepair(p);
356+
}
357+
358+
/*
359+
* .. and re-instate the one we want (which might be either the
360+
* original one, or the rename/copy we found)
361+
*/
362+
q->queue[0] = choice;
363+
q->nr = 1;
364+
}
365+
293366
int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
294367
{
295368
void *tree1, *tree2;
@@ -306,6 +379,11 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
306379
init_tree_desc(&t1, tree1, size1);
307380
init_tree_desc(&t2, tree2, size2);
308381
retval = diff_tree(&t1, &t2, base, opt);
382+
if (opt->follow_renames && diff_might_be_rename()) {
383+
init_tree_desc(&t1, tree1, size1);
384+
init_tree_desc(&t2, tree2, size2);
385+
try_to_follow_renames(&t1, &t2, base, opt);
386+
}
309387
free(tree1);
310388
free(tree2);
311389
return retval;

0 commit comments

Comments
 (0)