2005-10-21 12:05:05 +08:00
|
|
|
/*
|
|
|
|
* Helper functions for tree diff generation
|
|
|
|
*/
|
|
|
|
#include "cache.h"
|
|
|
|
#include "diff.h"
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
#include "diffcore.h"
|
2006-04-02 20:44:09 +08:00
|
|
|
#include "tree.h"
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-18 11:06:24 +08:00
|
|
|
static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
|
|
|
char *newbase = xmalloc(baselen + pathlen + 2);
|
|
|
|
memcpy(newbase, base, baselen);
|
|
|
|
memcpy(newbase + baselen, path, pathlen);
|
|
|
|
memcpy(newbase + baselen + pathlen, "/", 2);
|
|
|
|
return newbase;
|
|
|
|
}
|
|
|
|
|
2008-07-16 22:54:02 +08:00
|
|
|
static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
|
|
|
|
{
|
|
|
|
char *fullname = xmalloc(baselen + pathlen + 1);
|
|
|
|
memcpy(fullname, base, baselen);
|
|
|
|
memcpy(fullname + baselen, path, pathlen);
|
|
|
|
fullname[baselen + pathlen] = 0;
|
|
|
|
return fullname;
|
|
|
|
}
|
|
|
|
|
2006-08-15 04:39:27 +08:00
|
|
|
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
|
2007-03-18 11:06:24 +08:00
|
|
|
const char *base, int baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-18 11:06:24 +08:00
|
|
|
static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
|
|
|
unsigned mode1, mode2;
|
|
|
|
const char *path1, *path2;
|
|
|
|
const unsigned char *sha1, *sha2;
|
|
|
|
int cmp, pathlen1, pathlen2;
|
2008-07-16 22:54:02 +08:00
|
|
|
char *fullname;
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2006-02-01 06:10:56 +08:00
|
|
|
sha1 = tree_entry_extract(t1, &path1, &mode1);
|
|
|
|
sha2 = tree_entry_extract(t2, &path2, &mode2);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-18 11:06:24 +08:00
|
|
|
pathlen1 = tree_entry_len(path1, sha1);
|
|
|
|
pathlen2 = tree_entry_len(path2, sha2);
|
2005-10-21 12:05:05 +08:00
|
|
|
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
|
|
|
|
if (cmp < 0) {
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, "-", t1, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (cmp > 0) {
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, "+", t2, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
return 1;
|
|
|
|
}
|
2007-11-11 03:05:14 +08:00
|
|
|
if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
|
2005-10-21 12:05:05 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the filemode has changed to/from a directory from/to a regular
|
|
|
|
* file, we need to consider it a remove and an add.
|
|
|
|
*/
|
|
|
|
if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, "-", t1, base, baselen);
|
|
|
|
show_entry(opt, "+", t2, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-11-11 03:05:14 +08:00
|
|
|
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
|
2005-10-21 12:05:05 +08:00
|
|
|
int retval;
|
2007-03-18 11:06:24 +08:00
|
|
|
char *newbase = malloc_base(base, baselen, path1, pathlen1);
|
2008-07-16 22:54:02 +08:00
|
|
|
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
|
|
|
|
newbase[baselen + pathlen1] = 0;
|
2005-10-21 12:05:05 +08:00
|
|
|
opt->change(opt, mode1, mode2,
|
2010-01-19 04:26:18 +08:00
|
|
|
sha1, sha2, newbase, 0, 0);
|
2008-07-16 22:54:02 +08:00
|
|
|
newbase[baselen + pathlen1] = '/';
|
|
|
|
}
|
2005-10-21 12:05:05 +08:00
|
|
|
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
|
|
|
|
free(newbase);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2008-07-16 22:54:02 +08:00
|
|
|
fullname = malloc_fullname(base, baselen, path1, pathlen1);
|
2010-01-19 04:26:18 +08:00
|
|
|
opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
|
2008-07-16 22:54:02 +08:00
|
|
|
free(fullname);
|
2005-10-21 12:05:05 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
/*
|
|
|
|
* Is a tree entry interesting given the pathspec we have?
|
|
|
|
*
|
|
|
|
* Return:
|
2007-03-22 08:00:27 +08:00
|
|
|
* - 2 for "yes, and all subsequent entries will be"
|
|
|
|
* - 1 for yes
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
* - zero for no
|
|
|
|
* - negative for "no, and no subsequent entries will be either"
|
|
|
|
*/
|
|
|
|
static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
|
|
|
const char *path;
|
2007-03-18 11:06:24 +08:00
|
|
|
const unsigned char *sha1;
|
2005-10-21 12:05:05 +08:00
|
|
|
unsigned mode;
|
|
|
|
int i;
|
2007-03-18 11:06:24 +08:00
|
|
|
int pathlen;
|
2007-03-22 00:51:47 +08:00
|
|
|
int never_interesting = -1;
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2006-04-11 07:39:11 +08:00
|
|
|
if (!opt->nr_paths)
|
2005-10-21 12:05:05 +08:00
|
|
|
return 1;
|
|
|
|
|
2007-03-18 11:06:24 +08:00
|
|
|
sha1 = tree_entry_extract(desc, &path, &mode);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-18 11:06:24 +08:00
|
|
|
pathlen = tree_entry_len(path, sha1);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-22 00:51:47 +08:00
|
|
|
for (i = 0; i < opt->nr_paths; i++) {
|
2006-04-11 07:39:11 +08:00
|
|
|
const char *match = opt->paths[i];
|
|
|
|
int matchlen = opt->pathlens[i];
|
2007-03-22 03:34:46 +08:00
|
|
|
int m = -1; /* signals that we haven't called strncmp() */
|
2005-10-21 12:05:05 +08:00
|
|
|
|
|
|
|
if (baselen >= matchlen) {
|
|
|
|
/* If it doesn't match, move along... */
|
|
|
|
if (strncmp(base, match, matchlen))
|
|
|
|
continue;
|
|
|
|
|
2007-03-22 08:00:27 +08:00
|
|
|
/*
|
2009-03-31 23:05:01 +08:00
|
|
|
* If the base is a subdirectory of a path which
|
|
|
|
* was specified, all of them are interesting.
|
2007-03-22 08:00:27 +08:00
|
|
|
*/
|
2009-03-31 23:05:01 +08:00
|
|
|
if (!matchlen ||
|
|
|
|
base[matchlen] == '/' ||
|
|
|
|
match[matchlen - 1] == '/')
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
/* Just a random prefix match */
|
|
|
|
continue;
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Does the base match? */
|
|
|
|
if (strncmp(base, match, baselen))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
match += baselen;
|
|
|
|
matchlen -= baselen;
|
|
|
|
|
2007-03-22 03:34:46 +08:00
|
|
|
if (never_interesting) {
|
|
|
|
/*
|
|
|
|
* We have not seen any match that sorts later
|
|
|
|
* than the current path.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Does match sort strictly earlier than path
|
|
|
|
* with their common parts?
|
|
|
|
*/
|
|
|
|
m = strncmp(match, path,
|
|
|
|
(matchlen < pathlen) ? matchlen : pathlen);
|
|
|
|
if (m < 0)
|
|
|
|
continue;
|
2007-03-22 00:51:47 +08:00
|
|
|
|
2007-03-22 03:34:46 +08:00
|
|
|
/*
|
|
|
|
* If we come here even once, that means there is at
|
|
|
|
* least one pathspec that would sort equal to or
|
|
|
|
* later than the path we are currently looking at.
|
|
|
|
* In other words, if we have never reached this point
|
|
|
|
* after iterating all pathspecs, it means all
|
|
|
|
* pathspecs are either outside of base, or inside the
|
|
|
|
* base but sorts strictly earlier than the current
|
|
|
|
* one. In either case, they will never match the
|
|
|
|
* subsequent entries. In such a case, we initialized
|
|
|
|
* the variable to -1 and that is what will be
|
|
|
|
* returned, allowing the caller to terminate early.
|
|
|
|
*/
|
|
|
|
never_interesting = 0;
|
|
|
|
}
|
2007-03-22 00:51:47 +08:00
|
|
|
|
2005-10-21 12:05:05 +08:00
|
|
|
if (pathlen > matchlen)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (matchlen > pathlen) {
|
|
|
|
if (match[pathlen] != '/')
|
|
|
|
continue;
|
|
|
|
if (!S_ISDIR(mode))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2007-03-22 03:34:46 +08:00
|
|
|
if (m == -1)
|
|
|
|
/*
|
|
|
|
* we cheated and did not do strncmp(), so we do
|
|
|
|
* that here.
|
|
|
|
*/
|
|
|
|
m = strncmp(match, path, pathlen);
|
|
|
|
|
2007-03-22 00:51:47 +08:00
|
|
|
/*
|
|
|
|
* If common part matched earlier then it is a hit,
|
|
|
|
* because we rejected the case where path is not a
|
|
|
|
* leading directory and is shorter than match.
|
|
|
|
*/
|
|
|
|
if (!m)
|
|
|
|
return 1;
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
2007-03-22 00:51:47 +08:00
|
|
|
return never_interesting; /* No matches */
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* A whole sub-tree went away or appeared */
|
2007-03-18 11:06:24 +08:00
|
|
|
static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
2007-03-22 08:00:27 +08:00
|
|
|
int all_interesting = 0;
|
2005-10-21 12:05:05 +08:00
|
|
|
while (desc->size) {
|
2007-03-22 08:00:27 +08:00
|
|
|
int show;
|
|
|
|
|
|
|
|
if (all_interesting)
|
|
|
|
show = 1;
|
|
|
|
else {
|
|
|
|
show = tree_entry_interesting(desc, base, baselen,
|
|
|
|
opt);
|
|
|
|
if (show == 2)
|
|
|
|
all_interesting = 1;
|
|
|
|
}
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
if (show < 0)
|
|
|
|
break;
|
|
|
|
if (show)
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, prefix, desc, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
update_tree_entry(desc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A file entry went away or appeared */
|
2006-08-15 04:39:27 +08:00
|
|
|
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
|
2007-03-18 11:06:24 +08:00
|
|
|
const char *base, int baselen)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
|
|
|
unsigned mode;
|
|
|
|
const char *path;
|
2006-02-01 06:10:56 +08:00
|
|
|
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
|
2008-07-16 22:54:02 +08:00
|
|
|
int pathlen = tree_entry_len(path, sha1);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-11-11 03:05:14 +08:00
|
|
|
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
|
2007-02-27 03:55:59 +08:00
|
|
|
enum object_type type;
|
2007-03-18 11:06:24 +08:00
|
|
|
char *newbase = malloc_base(base, baselen, path, pathlen);
|
2005-10-21 12:05:05 +08:00
|
|
|
struct tree_desc inner;
|
|
|
|
void *tree;
|
2007-03-22 01:08:25 +08:00
|
|
|
unsigned long size;
|
2005-10-21 12:05:05 +08:00
|
|
|
|
2007-03-22 01:08:25 +08:00
|
|
|
tree = read_sha1_file(sha1, &type, &size);
|
2007-02-27 03:55:59 +08:00
|
|
|
if (!tree || type != OBJ_TREE)
|
2005-10-21 12:05:05 +08:00
|
|
|
die("corrupt tree sha %s", sha1_to_hex(sha1));
|
|
|
|
|
2009-06-14 08:06:09 +08:00
|
|
|
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
|
|
|
|
newbase[baselen + pathlen] = 0;
|
2010-01-19 04:26:18 +08:00
|
|
|
opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
|
2009-06-14 08:06:09 +08:00
|
|
|
newbase[baselen + pathlen] = '/';
|
|
|
|
}
|
|
|
|
|
2007-03-22 01:08:25 +08:00
|
|
|
init_tree_desc(&inner, tree, size);
|
2007-03-18 11:06:24 +08:00
|
|
|
show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
|
2005-10-21 12:05:05 +08:00
|
|
|
|
|
|
|
free(tree);
|
|
|
|
free(newbase);
|
2006-08-15 04:39:27 +08:00
|
|
|
} else {
|
2008-07-16 22:54:02 +08:00
|
|
|
char *fullname = malloc_fullname(base, baselen, path, pathlen);
|
2010-01-19 04:26:18 +08:00
|
|
|
opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
|
2008-07-16 22:54:02 +08:00
|
|
|
free(fullname);
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt)
|
|
|
|
{
|
2007-03-22 08:00:27 +08:00
|
|
|
int all_interesting = 0;
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
while (t->size) {
|
2007-03-22 08:00:27 +08:00
|
|
|
int show;
|
|
|
|
|
|
|
|
if (all_interesting)
|
|
|
|
show = 1;
|
|
|
|
else {
|
|
|
|
show = tree_entry_interesting(t, base, baselen, opt);
|
|
|
|
if (show == 2)
|
|
|
|
all_interesting = 1;
|
|
|
|
}
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
if (!show) {
|
|
|
|
update_tree_entry(t);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Skip it all? */
|
|
|
|
if (show < 0)
|
|
|
|
t->size = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-21 12:05:05 +08:00
|
|
|
int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
|
|
|
|
{
|
2007-03-18 11:06:24 +08:00
|
|
|
int baselen = strlen(base);
|
|
|
|
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
for (;;) {
|
2009-05-23 16:15:35 +08:00
|
|
|
if (DIFF_OPT_TST(opt, QUICK) &&
|
diff: change semantics of "ignore whitespace" options
Traditionally, the --ignore-whitespace* options have merely meant to tell
the diff output routine that some class of differences are not worth
showing in the textual diff output, so that the end user has easier time
to review the remaining (presumably more meaningful) changes. These
options never affected the outcome of the command, given as the exit
status when the --exit-code option was in effect (either directly or
indirectly).
When you have only whitespace changes, however, you might expect
git diff -b --exit-code
to report that there is _no_ change with zero exit status.
Change the semantics of --ignore-whitespace* options to mean more than
"omit showing the difference in text".
The exit status, when --exit-code is in effect, is computed by checking if
we found any differences at the path level, while diff frontends feed
filepairs to the diffcore engine. When "ignore whitespace" options are in
effect, we defer this determination until the very end of diffcore
transformation. We simply do not know until the textual diff is
generated, which comes very late in the pipeline.
When --quiet is in effect, various diff frontends optimize by breaking out
early from the loop that enumerates the filepairs, when we find the first
path level difference; when --ignore-whitespace* is used the above change
automatically disables this optimization.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-05-23 03:45:29 +08:00
|
|
|
DIFF_OPT_TST(opt, HAS_CHANGES))
|
2007-03-15 02:12:51 +08:00
|
|
|
break;
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
if (opt->nr_paths) {
|
|
|
|
skip_uninteresting(t1, base, baselen, opt);
|
|
|
|
skip_uninteresting(t2, base, baselen, opt);
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
if (!t1->size) {
|
Set up for better tree diff optimizations
This is mainly just a cleanup patch, and sets up for later changes where
the tree-diff.c "interesting()" function can return more than just a
yes/no value.
In particular, it should be quite possible to say "no subsequent entries
in this tree can possibly be interesting any more", and thus allow the
callers to short-circuit the tree entirely.
In fact, changing the callers to do so is trivial, and is really all this
patch really does, because changing "interesting()" itself to say that
nothing further is going to be interesting is definitely more complicated,
considering that we may have arbitrary pathspecs.
But in cleaning up the callers, this actually fixes a potential small
performance issue in diff_tree(): if the second tree has a lot of
uninterestign crud in it, we would keep on doing the "is it interesting?"
check on the first tree for each uninteresting entry in the second one.
The answer is obviously not going to change, so that was just not helping.
The new code is clearer and simpler and avoids this issue entirely.
I also renamed "interesting()" to "tree_entry_interesting()", because I
got frustrated by the fact that
- we actually had *another* function called "interesting()" in another
file, and I couldn't tell from the profiles which one was the one that
mattered more.
- when rewriting it to return a ternary value, you can't just do
if (interesting(...))
...
any more, but want to assign the return value to a local variable. The
name of choice for that variable would normally be "interesting", so
I just wanted to make the function name be more specific, and avoid
that whole issue (even though I then didn't choose that name for either
of the users, just to avoid confusion in the patch itself ;)
In other words, this doesn't really change anything, but I think it's a
good thing to do, and if somebody comes along and writes the logic for
"yeah, none of the pathspecs you have are interesting", we now support
that trivially.
It could easily be a meaningful optimization for things like "blame",
where there's just one pathspec, and stopping when you've seen it would
allow you to avoid about 50% of the tree traversals on average.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-03-19 06:18:30 +08:00
|
|
|
if (!t2->size)
|
|
|
|
break;
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, "+", t2, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
update_tree_entry(t2);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!t2->size) {
|
2007-03-18 11:06:24 +08:00
|
|
|
show_entry(opt, "-", t1, base, baselen);
|
2005-10-21 12:05:05 +08:00
|
|
|
update_tree_entry(t1);
|
|
|
|
continue;
|
|
|
|
}
|
2007-03-18 11:06:24 +08:00
|
|
|
switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
|
2005-10-21 12:05:05 +08:00
|
|
|
case -1:
|
|
|
|
update_tree_entry(t1);
|
|
|
|
continue;
|
|
|
|
case 0:
|
|
|
|
update_tree_entry(t1);
|
|
|
|
/* Fallthrough */
|
|
|
|
case 1:
|
|
|
|
update_tree_entry(t2);
|
|
|
|
continue;
|
|
|
|
}
|
2008-09-01 00:39:19 +08:00
|
|
|
die("git diff-tree: internal error");
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
2007-06-22 01:22:59 +08:00
|
|
|
struct diff_queue_struct *q = &diff_queued_diff;
|
|
|
|
struct diff_filepair *choice;
|
|
|
|
const char *paths[1];
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
int i;
|
|
|
|
|
2007-06-22 01:22:59 +08:00
|
|
|
/* Remove the file creation entry from the diff queue, and remember it */
|
|
|
|
choice = q->queue[0];
|
|
|
|
q->nr = 0;
|
|
|
|
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
diff_setup(&diff_opts);
|
2007-11-11 03:05:14 +08:00
|
|
|
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
diff_opts.detect_rename = DIFF_DETECT_RENAME;
|
|
|
|
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
|
|
diff_opts.single_follow = opt->paths[0];
|
Fix diffcore-break total breakage
Ok, so on the kernel list, some people noticed that "git log --follow"
doesn't work too well with some files in the x86 merge, because a lot of
files got renamed in very special ways.
In particular, there was a pattern of doing single commits with renames
that looked basically like
- rename "filename.h" -> "filename_64.h"
- create new "filename.c" that includes "filename_32.h" or
"filename_64.h" depending on whether we're 32-bit or 64-bit.
which was preparatory for smushing the two trees together.
Now, there's two issues here:
- "filename.c" *remained*. Yes, it was a rename, but there was a new file
created with the old name in the same commit. This was important,
because we wanted each commit to compile properly, so that it was
bisectable, so splitting the rename into one commit and the "create
helper file" into another was *not* an option.
So we need to break associations where the contents change too much.
Fine. We have the -B flag for that. When we break things up, then the
rename detection will be able to figure out whether there are better
alternatives.
- "git log --follow" didn't with with -B.
Now, the second case was really simple: we use a different "diffopt"
structure for the rename detection than the basic one (which we use for
showing the diffs). So that second case is trivially fixed by a trivial
one-liner that just copies the break_opt values from the "real" diffopts
to the one used for rename following. So now "git log -B --follow" works
fine:
diff --git a/tree-diff.c b/tree-diff.c
index 26bdbdd..7c261fd 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -319,6 +319,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = opt->paths[0];
+ diff_opts.break_opt = opt->break_opt;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
however, the end result does *not* work. Because our diffcore-break.c
logic is totally bogus!
In particular:
- it used to do
if (base_size < MINIMUM_BREAK_SIZE)
return 0; /* we do not break too small filepair */
which basically says "don't bother to break small files". But that
"base_size" is the *smaller* of the two sizes, which means that if some
large file was rewritten into one that just includes another file, we
would look at the (small) result, and decide that it's smaller than the
break size, so it cannot be worth it to break it up! Even if the other
side was ten times bigger and looked *nothing* like the samell file!
That's clearly bogus. I replaced "base_size" with "max_size", so that
we compare the *bigger* of the filepair with the break size.
- It calculated a "merge_score", which was the score needed to merge it
back together if nothing else wanted it. But even if it was *so*
different that we would never want to merge it back, we wouldn't
consider it a break! That makes no sense. So I added
if (*merge_score_p > break_score)
return 1;
to make it clear that if we wouldn't want to merge it at the end, it
was *definitely* a break.
- It compared the whole "extent of damage", counting all inserts and
deletes, but it based this score on the "base_size", and generated the
damage score with
delta_size = src_removed + literal_added;
damage_score = delta_size * MAX_SCORE / base_size;
but that makes no sense either, since quite often, this will result in
a number that is *bigger* than MAX_SCORE! Why? Because base_size is
(again) the smaller of the two files we compare, and when you start out
from a small file and add a lot (or start out from a large file and
remove a lot), the base_size is going to be much smaller than the
damage!
Again, the fix was to replace "base_size" with "max_size", at which
point the damage actually becomes a sane percentage of the whole.
With these changes in place, not only does "git log -B --follow" work for
the case that triggered this in the first place, ie now
git log -B --follow arch/x86/kernel/vmlinux_64.lds.S
actually gives reasonable results. But I also wanted to verify it in
general, by doing a full-history
git log --stat -B -C
on my kernel tree with the old code and the new code.
There's some tweaking to be done, but generally, the new code generates
much better results wrt breaking up files (and then finding better rename
candidates). Here's a few examples of the "--stat" output:
- This:
include/asm-x86/Kbuild | 2 -
include/asm-x86/debugreg.h | 79 +++++++++++++++++++++++++++++++++++------
include/asm-x86/debugreg_32.h | 64 ---------------------------------
include/asm-x86/debugreg_64.h | 65 ---------------------------------
4 files changed, 68 insertions(+), 142 deletions(-)
Becomes:
include/asm-x86/Kbuild | 2 -
include/asm-x86/{debugreg_64.h => debugreg.h} | 9 +++-
include/asm-x86/debugreg_32.h | 64 -------------------------
3 files changed, 7 insertions(+), 68 deletions(-)
- This:
include/asm-x86/bug.h | 41 +++++++++++++++++++++++++++++++++++++++--
include/asm-x86/bug_32.h | 37 -------------------------------------
include/asm-x86/bug_64.h | 34 ----------------------------------
3 files changed, 39 insertions(+), 73 deletions(-)
Becomes
include/asm-x86/{bug_64.h => bug.h} | 20 +++++++++++++-----
include/asm-x86/bug_32.h | 37 -----------------------------------
2 files changed, 14 insertions(+), 43 deletions(-)
Now, in some other cases, it does actually turn a rename into a real
"delete+create" pair, and then the diff is usually bigger, so truth in
advertizing: it doesn't always generate a nicer diff. But for what -B was
meant for, I think this is a big improvement, and I suspect those cases
where it generates a bigger diff are tweakable.
So I think this diff fixes a real bug, but we might still want to tweak
the default values and perhaps the exact rules for when a break happens.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
2007-10-21 03:31:31 +08:00
|
|
|
diff_opts.break_opt = opt->break_opt;
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
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);
|
2007-12-12 05:59:55 +08:00
|
|
|
diff_tree_release_paths(&diff_opts);
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
|
2007-06-22 01:22:59 +08:00
|
|
|
/* 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];
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Found a source? Not only do we use that for the new
|
2007-06-22 01:22:59 +08:00
|
|
|
* diff_queued_diff, we will also use that as the path in
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
* the future!
|
|
|
|
*/
|
|
|
|
if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
|
2007-06-22 01:22:59 +08:00
|
|
|
/* Switch the file-pairs around */
|
|
|
|
q->queue[i] = choice;
|
|
|
|
choice = p;
|
|
|
|
|
|
|
|
/* Update the path we use from now on.. */
|
2007-12-12 05:59:55 +08:00
|
|
|
diff_tree_release_paths(opt);
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
opt->paths[0] = xstrdup(p->one->path);
|
|
|
|
diff_tree_setup_paths(opt->paths, opt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-04-18 02:13:30 +08:00
|
|
|
* Then, discard all the non-relevant file pairs...
|
2007-06-22 01:22:59 +08:00
|
|
|
*/
|
|
|
|
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)
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
*/
|
2007-06-22 01:22:59 +08:00
|
|
|
q->queue[0] = choice;
|
|
|
|
q->nr = 1;
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
}
|
|
|
|
|
2005-10-21 12:05:05 +08:00
|
|
|
int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
|
|
|
|
{
|
|
|
|
void *tree1, *tree2;
|
|
|
|
struct tree_desc t1, t2;
|
2007-03-22 01:08:25 +08:00
|
|
|
unsigned long size1, size2;
|
2005-10-21 12:05:05 +08:00
|
|
|
int retval;
|
|
|
|
|
2007-03-22 01:08:25 +08:00
|
|
|
tree1 = read_object_with_reference(old, tree_type, &size1, NULL);
|
2005-10-21 12:05:05 +08:00
|
|
|
if (!tree1)
|
|
|
|
die("unable to read source tree (%s)", sha1_to_hex(old));
|
2007-03-22 01:08:25 +08:00
|
|
|
tree2 = read_object_with_reference(new, tree_type, &size2, NULL);
|
2005-10-21 12:05:05 +08:00
|
|
|
if (!tree2)
|
|
|
|
die("unable to read destination tree (%s)", sha1_to_hex(new));
|
2007-03-22 01:08:25 +08:00
|
|
|
init_tree_desc(&t1, tree1, size1);
|
|
|
|
init_tree_desc(&t2, tree2, size2);
|
2005-10-21 12:05:05 +08:00
|
|
|
retval = diff_tree(&t1, &t2, base, opt);
|
2007-11-11 03:05:14 +08:00
|
|
|
if (DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
|
Finally implement "git log --follow"
Ok, I've really held off doing this too damn long, because I'm lazy, and I
was always hoping that somebody else would do it.
But no, people keep asking for it, but nobody actually did anything, so I
decided I might as well bite the bullet, and instead of telling people
they could add a "--follow" flag to "git log" to do what they want to do,
I decided that it looks like I just have to do it for them..
The code wasn't actually that complicated, in that the diffstat for this
patch literally says "70 insertions(+), 1 deletions(-)", but I will have
to admit that in order to get to this fairly simple patch, you did have to
know and understand the internal git diff generation machinery pretty
well, and had to really be able to follow how commit generation interacts
with generating patches and generating the log.
So I suspect that while I was right that it wasn't that hard, I might have
been expecting too much of random people - this patch does seem to be
firmly in the core "Linus or Junio" territory.
To make a long story short: I'm sorry for it taking so long until I just
did it.
I'm not going to guarantee that this works for everybody, but you really
can just look at the patch, and after the appropriate appreciative noises
("Ooh, aah") over how clever I am, you can then just notice that the code
itself isn't really that complicated.
All the real new code is in the new "try_to_follow_renames()" function. It
really isn't rocket science: we notice that the pathname we were looking
at went away, so we start a full tree diff and try to see if we can
instead make that pathname be a rename or a copy from some other previous
pathname. And if we can, we just continue, except we show *that*
particular diff, and ever after we use the _previous_ pathname.
One thing to look out for: the "rename detection" is considered to be a
singular event in the _linear_ "git log" output! That's what people want
to do, but I just wanted to point out that this patch is *not* carrying
around a "commit,pathname" kind of pair and it's *not* going to be able to
notice the file coming from multiple *different* files in earlier history.
IOW, if you use "git log --follow", then you get the stupid CVS/SVN kind
of "files have single identities" kind of semantics, and git log will just
pick the identity based on the normal move/copy heuristics _as_if_ the
history could be linearized.
Put another way: I think the model is broken, but given the broken model,
I think this patch does just about as well as you can do. If you have
merges with the same "file" having different filenames over the two
branches, git will just end up picking _one_ of the pathnames at the point
where the newer one goes away. It never looks at multiple pathnames in
parallel.
And if you understood all that, you probably didn't need it explained, and
if you didn't understand the above blathering, it doesn't really mtter to
you. What matters to you is that you can now do
git log -p --follow builtin-rev-list.c
and it will find the point where the old "rev-list.c" got renamed to
"builtin-rev-list.c" and show it as such.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-06-20 05:22:46 +08:00
|
|
|
init_tree_desc(&t1, tree1, size1);
|
|
|
|
init_tree_desc(&t2, tree2, size2);
|
|
|
|
try_to_follow_renames(&t1, &t2, base, opt);
|
|
|
|
}
|
2005-10-21 12:05:05 +08:00
|
|
|
free(tree1);
|
|
|
|
free(tree2);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2006-10-27 00:52:39 +08:00
|
|
|
int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
void *tree;
|
2007-03-22 01:08:25 +08:00
|
|
|
unsigned long size;
|
2006-10-27 00:52:39 +08:00
|
|
|
struct tree_desc empty, real;
|
|
|
|
|
2007-03-22 01:08:25 +08:00
|
|
|
tree = read_object_with_reference(new, tree_type, &size, NULL);
|
2006-10-27 00:52:39 +08:00
|
|
|
if (!tree)
|
|
|
|
die("unable to read root tree (%s)", sha1_to_hex(new));
|
2007-03-22 01:08:25 +08:00
|
|
|
init_tree_desc(&real, tree, size);
|
2006-10-27 00:52:39 +08:00
|
|
|
|
2007-03-22 01:08:25 +08:00
|
|
|
init_tree_desc(&empty, "", 0);
|
2006-10-27 00:52:39 +08:00
|
|
|
retval = diff_tree(&empty, &real, base, opt);
|
|
|
|
free(tree);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2005-10-21 12:05:05 +08:00
|
|
|
static int count_paths(const char **paths)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
while (*paths++)
|
|
|
|
i++;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2006-04-11 07:39:11 +08:00
|
|
|
void diff_tree_release_paths(struct diff_options *opt)
|
2005-10-21 12:05:05 +08:00
|
|
|
{
|
2006-04-11 07:39:11 +08:00
|
|
|
free(opt->pathlens);
|
|
|
|
}
|
|
|
|
|
|
|
|
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
|
|
|
|
{
|
|
|
|
opt->nr_paths = 0;
|
|
|
|
opt->pathlens = NULL;
|
|
|
|
opt->paths = NULL;
|
|
|
|
|
2005-10-21 12:05:05 +08:00
|
|
|
if (p) {
|
|
|
|
int i;
|
|
|
|
|
2006-04-11 07:39:11 +08:00
|
|
|
opt->paths = p;
|
|
|
|
opt->nr_paths = count_paths(p);
|
|
|
|
if (opt->nr_paths == 0) {
|
|
|
|
opt->pathlens = NULL;
|
2005-12-27 04:34:56 +08:00
|
|
|
return;
|
|
|
|
}
|
2006-04-11 07:39:11 +08:00
|
|
|
opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
|
|
|
|
for (i=0; i < opt->nr_paths; i++)
|
|
|
|
opt->pathlens[i] = strlen(p[i]);
|
2005-10-21 12:05:05 +08:00
|
|
|
}
|
|
|
|
}
|