Merge branch 'lt/follow'
authorJunio C Hamano <gitster@pobox.com>
Sun, 24 Jun 2007 09:08:31 +0000 (02:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 24 Jun 2007 09:08:31 +0000 (02:08 -0700)
* lt/follow:
Fix up "git log --follow" a bit..
Finally implement "git log --follow"

builtin-log.c
diff.c
diff.h
revision.c
tree-diff.c
index b9035ab7997612a91cd032e3cb1bcd5296585768..073a2a16a3fafd66d13b1ed106a0016617ec63ad 100644 (file)
@@ -58,6 +58,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        argc = setup_revisions(argc, argv, rev, "HEAD");
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
+       if (rev->diffopt.follow_renames) {
+               rev->always_show_header = 0;
+               if (rev->diffopt.nr_paths != 1)
+                       usage("git logs can only follow renames on one pathname at a time");
+       }
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "--decorate")) {
diff --git a/diff.c b/diff.c
index 4aa9bbc0116ac9081dee7aa8aa85507c401b5e53..9938969fa50e8af2a4eabe96ae122111ae42fe75 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2210,6 +2210,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--find-copies-harder"))
                options->find_copies_harder = 1;
+       else if (!strcmp(arg, "--follow"))
+               options->follow_renames = 1;
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (!prefixcmp(arg, "--abbrev=")) {
diff --git a/diff.h b/diff.h
index a7ee6d8c87887b111ef84b95266e1ff43496e7c3..9fd6d447d4c62e158c941a736e547c7e516cdd93 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -55,6 +55,7 @@ struct diff_options {
                 full_index:1,
                 silent_on_remove:1,
                 find_copies_harder:1,
+                follow_renames:1,
                 color_diff:1,
                 color_diff_words:1,
                 has_changes:1,
index 1f4590b89649a9d1397af2f35af142cc6ab36847..7834bb108e27a819a4a619a85123443f254d421d 100644 (file)
@@ -1230,7 +1230,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
        if (revs->prune_data) {
                diff_tree_setup_paths(revs->prune_data, &revs->pruning);
-               revs->prune_fn = try_to_simplify_commit;
+               /* Can't prune commits with rename following: the paths change.. */
+               if (!revs->diffopt.follow_renames)
+                       revs->prune_fn = try_to_simplify_commit;
                if (!revs->full_diff)
                        diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
        }
index 852498eb49fe0cba5e1cd21661288e8321a4b20e..26bdbdd2bfea5eab99b9f1c38936efe56f1ab45a 100644 (file)
@@ -3,6 +3,7 @@
  */
 #include "cache.h"
 #include "diff.h"
+#include "diffcore.h"
 #include "tree.h"
 
 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
        return 0;
 }
 
+/*
+ * Does it look like the resulting diff might be due to a rename?
+ *  - single entry
+ *  - not a valid previous file
+ */
+static inline int diff_might_be_rename(void)
+{
+       return diff_queued_diff.nr == 1 &&
+               !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
+}
+
+static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       struct diff_options diff_opts;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_filepair *choice;
+       const char *paths[1];
+       int i;
+
+       /* Remove the file creation entry from the diff queue, and remember it */
+       choice = q->queue[0];
+       q->nr = 0;
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_opts.single_follow = opt->paths[0];
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("unable to set up diff options to follow renames");
+       diff_tree(t1, t2, base, &diff_opts);
+       diffcore_std(&diff_opts);
+
+       /* Go through the new set of filepairing, and see if we find a more interesting one */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+
+               /*
+                * Found a source? Not only do we use that for the new
+                * diff_queued_diff, we will also use that as the path in
+                * the future!
+                */
+               if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+                       /* Switch the file-pairs around */
+                       q->queue[i] = choice;
+                       choice = p;
+
+                       /* Update the path we use from now on.. */
+                       opt->paths[0] = xstrdup(p->one->path);
+                       diff_tree_setup_paths(opt->paths, opt);
+                       break;
+               }
+       }
+
+       /*
+        * Then, discard all the non-relevane file pairs...
+        */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_free_filepair(p);
+       }
+
+       /*
+        * .. and re-instate the one we want (which might be either the
+        * original one, or the rename/copy we found)
+        */
+       q->queue[0] = choice;
+       q->nr = 1;
+}
+
 int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
 {
        void *tree1, *tree2;
@@ -306,6 +379,11 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
        init_tree_desc(&t1, tree1, size1);
        init_tree_desc(&t2, tree2, size2);
        retval = diff_tree(&t1, &t2, base, opt);
+       if (opt->follow_renames && diff_might_be_rename()) {
+               init_tree_desc(&t1, tree1, size1);
+               init_tree_desc(&t2, tree2, size2);
+               try_to_follow_renames(&t1, &t2, base, opt);
+       }
        free(tree1);
        free(tree2);
        return retval;