{"thread":{"id":"5180","subject":"[PATCH and RFC] gitweb: Remove --full-history from git_history","startedAt":"2006-08-09T10:57:19Z","lastAt":"2006-08-14T11:10:54Z","messageCount":10,"participants":["Jakub Narebski","Junio C Hamano","Fredrik Kuivinen","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"25019","messageId":"200608091257.19461.jnareb@gmail.com","threadId":"5180","inReplyTo":null,"subject":"[PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-09T10:57:19Z","receivedAt":"2006-08-09T10:57:19Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Stop pretending that gitweb is rename-aware, and remove --full-history\noption to git-rev-list in git_history (for \"history\" action):\n\n * gitweb cannot handle renames as of yet; it uses constant original\n   $file_name as 'f' argument in links\n * at least for git 1.4.1.1 --full-history option doesn't follow\n   renames (check 'git rev-list next -- gitweb/test/file+plus+sign'\n   with and without --full-history option and compare to\n   'git rev-list next -- gitweb/test/file+plus+sign test/file+plus+sign')\n   and is three times slower than git-rev-list without --full-history\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis is call for better rename support in git (better than --full-history).\nThere was such an attempt in \"[RFC, PATCH] Teach revision.c about renames\"\nby Fredrik Kuivinen\n  http://marc.theaimsgroup.com/?l=git&m=114772921317920\nbut IIRC patch was dropped in the favor of --full-history option.\n\nWhat is needed by gitweb is for git-rev-list to not only follow revisions\nof file contents across renames, and return more revisions, but also\nreturn _how_ filename changes, without need to call git-diff-tree on each\nreturned revision.\n\nPROPOSAL:\n---------\n\nImplement --follow/--follow-path/--follow-contents option to git-rev-list,\nwhich would output besides revision ids also current path limit; the format\ncould be for example:\n\n  <commit> [ -- <paths>...]\n\nfor example:\n$ git rev-list --follow db58b69ba -- gitweb/test/file+plus+sign\n0a8f4f0020cb35095005852c0797f0b90e9ebb74 -- gitweb/test/file+plus+sign\n85852d44e48c1d1c6d815cc5fccf1b580f2f2cad -- test/file+plus+sign\ncc3245b6512a01d74c0fd460d762ba8a1e8b968a -- test/file+plus+sign\n\nThere might be better format, as the proposed isn't best for copying\ndetections support, as path limit gets larger and one cannot immediately\nget which element of new <paths>... correspond to which original <paths>...\nelement. Same with splitting file vs. simply creating file.\n\nIt should be not that hard to implement, just detecting copying, renaming\nand filename vanishing, and updating current path limit; or putting\nmodified path limit and revision to start modified path-limit from to\nthe queue...\n\n\n gitweb/gitweb.perl |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex ae13e3e..59222c2 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -2284,7 +2284,7 @@ sub git_history {\n \tgit_print_page_path($file_name, $ftype);\n \n \topen my $fd, \"-|\",\n-\t\t$GIT, \"rev-list\", \"--full-history\", $hash_base, \"--\", $file_name;\n+\t\t$GIT, \"rev-list\", $hash_base, \"--\", $file_name;\n \tprint \"<table cellspacing=\\\"0\\\">\\n\";\n \tmy $alternate = 0;\n \twhile (my $line = <$fd>) {\n-- \n1.4.1.1\n"},{"id":"25021","messageId":"7vu04m413f.fsf@assigned-by-dhcp.cox.net","threadId":"5180","inReplyTo":"200608091257.19461.jnareb@gmail.com","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-08-09T11:09:40Z","receivedAt":"2006-08-09T11:09:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> Stop pretending that gitweb is rename-aware, and remove --full-history\n> option to git-rev-list in git_history (for \"history\" action):\n\nWhere did you get the idea that --full-history has anything to\ndo with renames?\n\nMessage-ID: <20060701005924.7726.qmail@web31812.mail.mud.yahoo.com>\nMessage-ID: <Pine.LNX.4.64.0606301818480.12404@g5.osdl.org>\n"},{"id":"25024","messageId":"ebcivb$t6m$1@sea.gmane.org","threadId":"5180","inReplyTo":"7vu04m413f.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-09T12:04:11Z","receivedAt":"2006-08-09T12:04:11Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Junio C Hamano wrote:\n\n> Jakub Narebski <jnareb@gmail.com> writes:\n> \n>> Stop pretending that gitweb is rename-aware, and remove --full-history\n>> option to git-rev-list in git_history (for \"history\" action):\n> \n> Where did you get the idea that --full-history has anything to\n> do with renames?\n\n> Message-ID: <20060701005924.7726.qmail@web31812.mail.mud.yahoo.com>\n  http://marc.theaimsgroup.com/?l=git&m=115171557714437\n> Message-ID: <Pine.LNX.4.64.0606301818480.12404@g5.osdl.org>\n  http://marc.theaimsgroup.com/?l=git&m=115171683714119\n\nOoops, sorry, my mistake. Still, even if the patch is to be dropped,\nthe proposal about rename detection is still valid.\n\nI wonder which version is faster: --full-history, or filter using \ndiff-tree?\n\nab -n 10 \"http://localhost/cgi-bin/gitweb/gitweb.cgi?p=git.git;a=history;hb=next;f=gitweb/gitweb.perl\"\n(ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0) says\n\n  Requests per second:    0.09 [#/sec] (mean)\n  Time per request:       10918.552 [ms] (mean)\n  Time per request:       10918.552 [ms] (mean, across all concurrent requests)\n  Transfer rate:          2.13 [Kbytes/sec] received\n\n  Connection Times (ms)\n                min  mean[+/-sd] median     max\n  Connect:        0    0     0.0      0       0\n  Processing:  8851 10917 2776.1   9284   16420\n  Waiting:      407  457    95.1    428     721\n  Total:       8851 10917 2776.1   9284   16420\n\nfor --full-history version, and\n\n  Requests per second:    0.11 [#/sec] (mean)\n  Time per request:       9076.865 [ms] (mean)\n  Time per request:       9076.865 [ms] (mean, across all concurrent requests)\n  Transfer rate:          2.57 [Kbytes/sec] received\n\n  Connection Times (ms)\n                min  mean[+/-sd] median   max\n  Connect:        0    0   0.0      0       0\n  Processing:  8741 9076 271.7   9100    9581\n  Waiting:      299  405  49.5    404     507\n  Total:       8741 9076 271.7   9100    9581\n\nfor the pipe through git-diff-tree version, both with very similar\ntimes (check out median column), although --full-history version\nseems slightly slower...\n\nStill, it is ab on workstation, not separate server, an only average \nover 10 requests.\n\nAnd \n1025:jnareb@roke:~/git> time git rev-list next -- gitweb/gitweb.perl\n  [...]\n  real    0m2.623s\n  user    0m2.536s\n  sys     0m0.016s\n1024:jnareb@roke:~/git> time git rev-list next | git diff-tree -r --stdin -- gitweb/gitweb.perl\n  [...]\n  real    0m6.857s\n  user    0m6.024s\n  sys     0m0.068s\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"25025","messageId":"ebcm1j$6gi$1@sea.gmane.org","threadId":"5180","inReplyTo":"ebcivb$t6m$1@sea.gmane.org","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-09T12:56:36Z","receivedAt":"2006-08-09T12:56:36Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jakub Narebski wrote:\n\n> I wonder which version is faster: --full-history, or filter using \n> diff-tree?\n> \n> ab -n 10 \"http://localhost/cgi-bin/gitweb/gitweb.cgi?p=git.git;a=history;hb=next;f=gitweb/gitweb.perl\"\n> (ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0) says\n> \n>   Requests per second:    0.09 [#/sec] (mean)\n>   Time per request:       10918.552 [ms] (mean)\n>   Time per request:       10918.552 [ms] (mean, across all concurrent requests)\n>   Transfer rate:          2.13 [Kbytes/sec] received\n> \n>   Connection Times (ms)\n>                 min  mean[+/-sd] median     max\n>   Connect:        0    0     0.0      0       0\n>   Processing:  8851 10917 2776.1   9284   16420\n>   Waiting:      407  457    95.1    428     721\n>   Total:       8851 10917 2776.1   9284   16420\n\nAdding \"--remove-empty\" (would this change output for git_history much?)\nchanges this to:\n\n  Requests per second:    0.75 [#/sec] (mean)\n  Time per request:       1341.702 [ms] (mean)\n  Time per request:       1341.702 [ms] (mean, across all concurrent requests)\n  Transfer rate:          17.37 [Kbytes/sec] received\n\n  Connection Times (ms)\n                min  mean[+/-sd] median   max\n  Connect:        0    0   0.0      0       0\n  Processing:  1208 1340 206.6   1241    1729\n  Waiting:      386  408  36.6    403     510\n  Total:       1208 1340 206.6   1241    1729\n\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"25042","messageId":"20060809192815.GA7954@c165.ib.student.liu.se","threadId":"5180","inReplyTo":"200608091257.19461.jnareb@gmail.com","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Fredrik Kuivinen","fromEmail":"freku045@student.liu.se","sentAt":"2006-08-09T19:28:15Z","receivedAt":"2006-08-09T19:28:15Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"On Wed, Aug 09, 2006 at 12:57:19PM +0200, Jakub Narebski wrote:\n> This is call for better rename support in git (better than --full-history).\n> There was such an attempt in \"[RFC, PATCH] Teach revision.c about renames\"\n> by Fredrik Kuivinen\n>   http://marc.theaimsgroup.com/?l=git&m=114772921317920\n> but IIRC patch was dropped in the favor of --full-history option.\n\nI don't think it was dropped in favor --full-history. Anyway, I did\nsome more work on that patch back then. Compared to the one posted in\nthe mail referenced above a couple of bugs have been fixed and a few\ntests have been added. If anyone wants to play with it see the patch\nin the end of this mail. It is on top of\ned90cbf5f681c0144909457be3f4792b47604a5b, so it's quite old and will\nmost probably not apply cleanly on the current master branch. I just\nnoticed that it makes t/t6003-rev-list-topo-order.sh fail too.\n\n> What is needed by gitweb is for git-rev-list to not only follow revisions\n> of file contents across renames, and return more revisions, \n\nNote that it is not enough to only return more revisions.\n\nFor example, consider the commits (newest commit first)\nA: Rename \"bar\" to \"foo\"\nB: No changes to \"bar\"\nC: No changes to \"bar\", delete \"foo\"\n<more commits here>\n\nThen you want \"git-rev-list --renames A -- foo\" to return A,... not A,C,...\n\n>                                                             but also\n> return _how_ filename changes, without need to call git-diff-tree on each\n> returned revision.\n\nThis is currently not done by the patch. Should be straight forward to\nadd though.\n\n\n- Fredrik\n\n\n---\n Documentation/git-rev-list.txt |    6 \n Makefile                       |    2 \n log-tree.c                     |    9 \n revision.c                     |  636 ++++++++++++++++++++++++++++++++++++++++-\n revision.h                     |   38 ++\n t/t6004-rev-list-rename.sh     |  100 ++++++\n 6 files changed, 780 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex ad6d14c..f7ecf8e 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -103,6 +103,12 @@ OPTIONS\n \ttopological order (i.e. descendant commits are shown\n \tbefore their parents).\n \n+--renames::\n+\tWhen optional paths are given together with '--renames' the\n+\tpaths are tracked, and any renames detected by Git are\n+\tfollowed. This is useful for tracking the history of files\n+\tacross renames.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>\ndiff --git a/Makefile b/Makefile\nindex dbf19c6..4212a05 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -85,7 +85,7 @@ uname_P := $(shell sh -c 'uname -p 2>/de\n \n # CFLAGS and LDFLAGS are for the users to override from the command line.\n \n-CFLAGS = -g -O2 -Wall\n+CFLAGS = -g3 -O0 -Wall\n LDFLAGS =\n ALL_CFLAGS = $(CFLAGS)\n ALL_LDFLAGS = $(LDFLAGS)\ndiff --git a/log-tree.c b/log-tree.c\nindex 58b0163..e53dd19 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -133,11 +133,12 @@ int log_tree_diff_flush(struct rev_info \n }\n \n static int diff_root_tree(struct rev_info *opt,\n-\t\t\t  const unsigned char *new, const char *base)\n+\t\t\t  struct commit* commit, const char *base)\n {\n \tint retval;\n \tvoid *tree;\n \tstruct tree_desc empty, real;\n+\tconst unsigned char* new = commit->object.sha1;\n \n \ttree = read_object_with_reference(new, tree_type, &real.size, NULL);\n \tif (!tree)\n@@ -146,9 +147,11 @@ static int diff_root_tree(struct rev_inf\n \n \tempty.buf = \"\";\n \tempty.size = 0;\n+\trev_setup_diffopt_paths(opt, commit, NULL);\n \tretval = diff_tree(&empty, &real, base, &opt->diffopt);\n \tfree(tree);\n \tlog_tree_diff_flush(opt);\n+\trev_free_diffopt_paths(opt, commit);\n \treturn retval;\n }\n \n@@ -178,7 +181,7 @@ static int log_tree_diff(struct rev_info\n \tparents = commit->parents;\n \tif (!parents) {\n \t\tif (opt->show_root_diff)\n-\t\t\tdiff_root_tree(opt, sha1, \"\");\n+\t\t\tdiff_root_tree(opt, commit, \"\");\n \t\treturn !opt->loginfo;\n \t}\n \n@@ -197,8 +200,10 @@ static int log_tree_diff(struct rev_info\n \tfor (;;) {\n \t\tstruct commit *parent = parents->item;\n \n+\t\trev_setup_diffopt_paths(opt, commit, parent);\n \t\tdiff_tree_sha1(parent->object.sha1, sha1, \"\", &opt->diffopt);\n \t\tlog_tree_diff_flush(opt);\n+\t\trev_free_diffopt_paths(opt, commit);\n \n \t\tshowed_log |= !opt->loginfo;\n \ndiff --git a/revision.c b/revision.c\nindex 2294b16..6d31e2d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1,12 +1,17 @@\n+#include <assert.h>\n+\n #include \"cache.h\"\n #include \"tag.h\"\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"commit.h\"\n #include \"diff.h\"\n+#include \"diffcore.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n \n+#define DEBUG 0\n+\n static char *path_name(struct name_path *path, const char *name)\n {\n \tstruct name_path *p;\n@@ -398,28 +403,67 @@ static void add_parents_to_list(struct r\n \t}\n }\n \n+static struct commit_list* remove_duplicates(struct commit_list* list)\n+{\n+\tstruct commit_list* res = list, *end;\n+\n+\tif (!list)\n+\t\treturn NULL;\n+\n+\tif (DEBUG) {\n+\t\tprintf(\"removing duplicates... list:\\n\");\n+\t\tcommit_list_print(list);\n+\t\tprintf(\"end of list\\n\");\n+\t}\n+\n+\tres->item->object.flags |= DUPLICATE;\n+\tend = res;\n+\tfor(list = list->next; list; list = list->next) {\n+\t\tif (!(list->item->object.flags & DUPLICATE)) {\n+\t\t\tend->next = list;\n+\t\t\tend = list;\n+\t\t\tlist->item->object.flags |= DUPLICATE;\n+\t\t} else if(DEBUG)\n+\t\t\tprintf(\"removing duplicate: %s\\n\", sha1_to_hex(list->item->object.sha1));\n+\t}\n+\tend->next = NULL;\n+\n+\tif (DEBUG){\n+\t\tprintf(\"new list:\\n\");\n+\t\tcommit_list_print(res);\n+\t\tprintf(\"end of list\\n\");\n+\t}\n+\n+\treturn res;\n+}\n+\n static void limit_list(struct rev_info *revs)\n {\n \tstruct commit_list *list = revs->commits;\n \tstruct commit_list *newlist = NULL;\n \tstruct commit_list **p = &newlist;\n \n-\twhile (list) {\n-\t\tstruct commit_list *entry = list;\n-\t\tstruct commit *commit = list->item;\n+\t/* We have to be a bit careful here because objects may be\n+\t * added to revs->commits by functions called by\n+\t * add_parents_to_list and we want to process all those\n+\t * objects, in addition to the objects which are in the list\n+\t * in the beginning. */\n+\twhile (revs->commits) {\n+\t\tstruct commit_list *entry = revs->commits;\n+\t\tstruct commit *commit = revs->commits->item;\n \t\tstruct object *obj = &commit->object;\n \n-\t\tlist = list->next;\n+\t\trevs->commits = revs->commits->next;\n \t\tfree(entry);\n \n \t\tif (revs->max_age != -1 && (commit->date < revs->max_age))\n \t\t\tobj->flags |= UNINTERESTING;\n \t\tif (revs->unpacked && has_sha1_pack(obj->sha1))\n \t\t\tobj->flags |= UNINTERESTING;\n-\t\tadd_parents_to_list(revs, commit, &list);\n+\t\tadd_parents_to_list(revs, commit, &revs->commits);\n \t\tif (obj->flags & UNINTERESTING) {\n \t\t\tmark_parents_uninteresting(commit);\n-\t\t\tif (everybody_uninteresting(list))\n+\t\t\tif (everybody_uninteresting(revs->commits))\n \t\t\t\tbreak;\n \t\t\tcontinue;\n \t\t}\n@@ -457,6 +501,8 @@ static void limit_list(struct rev_info *\n \t\t\tcommit->object.flags &= ~TMP_MARK;\n \t\t}\n \t}\n+\tif (revs->follow_renames)\n+\t\tnewlist = remove_duplicates(newlist);\n \trevs->commits = newlist;\n }\n \n@@ -507,6 +553,512 @@ static int add_parents_only(struct rev_i\n \treturn 1;\n }\n \n+/* Rename following code */\n+struct mem_pool\n+{\n+\t/* This is slightly ugly. We don't really store paths in this\n+\t * list, we store pointers to our memory blocks. */\n+\tstruct path_list* list;\n+\n+\tint block_size, len;\n+};\n+\n+static void* mem_pool_alloc(struct mem_pool* pool, size_t size)\n+{\n+\tassert(size < pool->block_size);\n+\tif (pool->list && pool->block_size - pool->len > size) {\n+\t\tint old_len = pool->len;\n+\t\tpool->len += size;\n+\t\treturn pool->list->item + old_len;\n+\t} else {\n+\t\tif (DEBUG)\n+\t\t\tprintf(\"allocating new block\\n\");\n+\t\tpath_list_insert(xmalloc(pool->block_size), &pool->list);\n+\t\tpool->len = size;\n+\t\treturn pool->list->item;\n+\t}\n+}\n+\n+static void mem_pool_free(struct mem_pool* pool)\n+{\n+\tstruct path_list* next;\n+\tfor (; pool->list; pool->list = next) {\n+\t\tfree(pool->list->item);\n+\t\tnext = pool->list->next;\n+\t\tfree(pool->list);\n+\t}\n+}\n+\n+static void mem_pool_init(struct mem_pool* pool, int block_size)\n+{\n+\tpool->list = NULL;\n+\tpool->len = 0;\n+\tpool->block_size = block_size;\n+}\n+\n+const struct path_list* path_list_find(const struct path_list* list,\n+\t\t\t\t       const char* path)\n+{\n+\tfor(; list; list = list->next) {\n+\t\tif (!strcmp(path, list->item))\n+\t\t\treturn list;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+struct path_list* path_list_insert(char *item, struct path_list **list_p)\n+{\n+\tstruct path_list *new_list = xmalloc(sizeof(struct path_list));\n+\tnew_list->item = item;\n+\tnew_list->next = *list_p;\n+\t*list_p = new_list;\n+\treturn new_list;\n+}\n+\n+/* Return non-zero if l1 is a (not necessarily proper) subset of l2\n+ * and zero otherwise.\n+ *\n+ * Yes, this is slow. */\n+int path_list_issubseteq(struct path_list* l1, struct path_list* l2)\n+{\n+\tfor (; l1; l1 = l1->next) {\n+\t\tif (!path_list_find(l2, l1->item))\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+struct path_list* path_list_insert_pool(char *item, struct path_list **list_p,\n+\t\t\t\t\tstruct mem_pool* pool)\n+{\n+\tstruct path_list *new_list =\n+\t\tmem_pool_alloc(pool, sizeof(struct path_list));\n+\tnew_list->item = item;\n+\tnew_list->next = *list_p;\n+\t*list_p = new_list;\n+\treturn new_list;\n+}\n+\n+\n+struct path_list* path_list_union_pool(struct path_list* l1,\n+\t\t\t\t       struct path_list* l2,\n+\t\t\t\t       struct mem_pool* pool)\n+{\n+\tstruct path_list* res;\n+\tfor (; l1; l1 = l1->next)\n+\t\tpath_list_insert_pool(l1->item, &res, pool);\n+\tfor (; l2; l2 = l2->next)\n+\t\tpath_list_insert_pool(l2->item, &res, pool);\n+\treturn res;\n+}\n+\n+void path_list_print(FILE* file, struct path_list* list)\n+{\n+\tfor(; list; list = list->next)\n+\t\tfprintf(file, \"%s \", list->item);\n+\tfputc('\\n', file);\n+}\n+\n+void path_list_free(struct path_list *list)\n+{\n+\twhile (list) {\n+\t\tstruct path_list *temp = list;\n+\t\tlist = temp->next;\n+\t\tfree(temp);\n+\t}\n+}\n+\n+const char** convert_to_pathspec(struct path_list* l1, struct path_list* l2)\n+{\n+\tstruct path_list* l;\n+\tconst char** ret;\n+\tint i, len = 0;\n+\n+\tfor(l = l1; l; l = l->next, len++)\n+\t\t;\n+\tfor(l = l2; l; l = l->next, len++)\n+\t\t;\n+\n+\tret = xmalloc((len+1)*sizeof(char*));\n+\tfor(i = 0; l1; l1 = l1->next, i++)\n+\t\tret[i] = l1->item;\n+ \tfor(; l2; l2 = l2->next, i++)\n+\t\tret[i] = l2->item;\n+\n+\tret[len] = NULL;\n+\treturn ret;\n+}\n+\n+static int file_exists_helper(unsigned char *sha1, const char *base,\n+\t\t\t      int baselen, const char *pathname,\n+\t\t\t      unsigned mode, int stage);\n+\n+static int file_found;\n+static const char* file_to_find;\n+static int file_exists(struct tree *t, const char *pathname)\n+{\n+\tconst char *pathspec[2];\n+\n+\tfile_to_find = pathname;\n+\tpathspec[0] = pathname;\n+\tpathspec[1] = NULL;\n+\tfile_found = 0;\n+\tread_tree_recursive(t, \"\", 0, 0, pathspec, file_exists_helper);\n+\n+\treturn file_found;\n+}\n+\n+static int file_exists_helper(unsigned char *sha1, const char *base,\n+\t\t\t      int baselen, const char *pathname,\n+\t\t\t      unsigned mode, int stage)\n+{\n+\tif (S_ISDIR(mode))\n+\t\treturn READ_TREE_RECURSIVE;\n+\n+\tif (strncmp(file_to_find, base, baselen) ||\n+\t    strcmp(file_to_find + baselen, pathname))\n+\t\treturn -1;\n+\n+\tfile_found = 1;\n+\treturn -1;\n+}\n+\n+static struct rev_commit_info* get_util(struct commit *commit)\n+{\n+\tstruct rev_commit_info *util = commit->object.util;\n+\n+\tif (util)\n+\t\treturn util;\n+\n+\tutil = xcalloc(1, sizeof(struct rev_commit_info));\n+\tcommit->object.util = util;\n+\treturn util;\n+}\n+\n+void rev_setup_diffopt_paths(struct rev_info* revs,\n+\t\t\t     struct commit* commit,\n+\t\t\t     struct commit* parent)\n+{\n+\tconst char** pathspec;\n+\n+\tif (!revs->follow_renames)\n+\t\treturn;\n+\n+\tpathspec = convert_to_pathspec(get_util(commit)->paths,\n+\t\t\t\t       parent ? get_util(parent)->paths : NULL);\n+\n+\tdiff_tree_setup_paths(pathspec, &revs->diffopt);\n+}\n+\n+void rev_free_diffopt_paths(struct rev_info* revs, struct commit* commit)\n+{\n+\tif (!revs->follow_renames)\n+\t\treturn;\n+\n+\tfree(revs->diffopt.paths);\n+\tdiff_tree_release_paths(&revs->diffopt);\n+}\n+\n+\n+static void topo_setter(struct commit* c, void* data)\n+{\n+\tstruct rev_commit_info* util = c->object.util;\n+\tutil->topo_data = data;\n+}\n+\n+static void* topo_getter(struct commit* c)\n+{\n+\tstruct rev_commit_info* util = c->object.util;\n+\treturn util->topo_data;\n+}\n+\n+static int same_tree_as_empty_paths(struct rev_info *revs, struct tree* t1,\n+\t\t\t\t    struct path_list* paths)\n+{\n+\tint ret;\n+\n+\tif (!paths)\n+\t\treturn 1;\n+\n+\tconst char** pathspec = convert_to_pathspec(paths, NULL);\n+\tdiff_tree_setup_paths(pathspec, &revs->pruning);\n+\tret = rev_same_tree_as_empty(revs, t1);\n+\tdiff_tree_release_paths(&revs->pruning);\n+\tfree(pathspec);\n+\treturn ret;\n+}\n+\n+\n+static struct path_list* file_removals;\n+static struct mem_pool *current_string_pool, *current_list_pool;\n+static void file_add_remove_ren(struct diff_options *options,\n+\t\t\t\tint addremove, unsigned mode,\n+\t\t\t\tconst unsigned char *sha1,\n+\t\t\t\tconst char *base, const char *path)\n+{\n+\tif (DEBUG)\n+\t\tprintf(\"%c base: '%s' path: '%s'\\n\", addremove, base, path);\n+\n+\tif (addremove == '-') {\n+\t\tchar* p = mem_pool_alloc(current_string_pool,\n+\t\t\t\t\t strlen(base) + strlen(path) + 1);\n+\t\tstrcpy(p, base);\n+\t\tstrcat(p, path);\n+\t\tpath_list_insert_pool(p, &file_removals, current_list_pool);\n+\t\ttree_difference = REV_TREE_NEW;\n+\t} else {\n+\t\t; //assert(0);\n+\t}\n+}\n+\n+static void file_change_ren(struct diff_options *options,\n+\t\t\t    unsigned old_mode, unsigned new_mode,\n+\t\t\t    const unsigned char *old_sha1,\n+\t\t\t    const unsigned char *new_sha1,\n+\t\t\t    const char *base, const char *path)\n+{\n+\tif (tree_difference == REV_TREE_SAME)\n+\t\ttree_difference = REV_TREE_DIFFERENT;\n+}\n+\n+static int compare_tree_paths(struct rev_info* revs,\n+\t\t\t      struct commit* parent, struct commit* commit,\n+\t\t\t      struct path_list** added)\n+{\n+\tconst char** pathspec;\n+\tstruct diff_options dopts;\n+\tstruct path_list* paths = get_util(commit)->paths;\n+\n+\tif (!paths) {\n+\t\t*added = NULL;\n+\t\treturn REV_TREE_SAME;\n+\t}\n+\n+\tdiff_setup(&dopts);\n+\tdopts.recursive = 1;\n+\tdopts.add_remove = file_add_remove_ren;\n+\tdopts.change = file_change_ren;\n+\tdopts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\n+\tpathspec = convert_to_pathspec(paths, NULL);\n+\tdiff_tree_setup_paths(pathspec, &dopts);\n+\n+\tif (diff_setup_done(&dopts) < 0)\n+\t\tdie(\"diff_setup_done failed\");\n+\n+\tfile_removals = NULL;\n+\ttree_difference = REV_TREE_SAME;\n+\tcurrent_string_pool = revs->string_pool;\n+\tcurrent_list_pool = revs->list_pool;\n+\tdiff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,\n+\t\t       \"\", &dopts);\n+\n+\tdiff_flush(&dopts);\n+\tdiff_tree_release_paths(&dopts);\n+\tfree(pathspec);\n+\n+\t*added = file_removals;\n+\treturn tree_difference;\n+}\n+\n+static struct path_list* find_renames(struct rev_info* revs,\n+\t\t\t\t      struct commit* commit,\n+\t\t\t\t      struct commit* parent,\n+\t\t\t\t      struct path_list* paths)\n+{\n+\tint i;\n+\tstruct diff_options dopts;\n+\tstruct path_list* ret = NULL;\n+\n+\tif (DEBUG) {\n+\t\tprintf(\"find_renames commit: %s \",\n+\t\t       sha1_to_hex(commit->object.sha1));\n+\t\tputs(sha1_to_hex(parent->object.sha1));\n+\t\tprintf(\"rename from paths: \");\n+\t\tpath_list_print(stdout, paths);\n+\t}\n+\n+\tdiff_setup(&dopts);\n+\tdopts.recursive = 1;\n+\tdopts.detect_rename = DIFF_DETECT_RENAME;\n+\tdopts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\n+\tif (diff_setup_done(&dopts) < 0)\n+\t\tdie(\"diff_setup_done failed\");\n+\n+\tdiff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,\n+\t\t       \"\", &dopts);\n+\tdiffcore_std(&dopts);\n+\n+\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n+\t\tstruct diff_filepair *p = diff_queued_diff.queue[i];\n+\n+\t\tif (0 && p->status == 'R' && DEBUG)\n+\t\t\tprintf(\"rename %s -> %s\\n\", p->one->path, p->two->path);\n+\n+\t\tif (p->status == 'R' && path_list_find(paths, p->one->path)) {\n+\t\t\tchar *str = mem_pool_alloc(revs->string_pool,\n+\t\t\t\t\t\t   strlen(p->two->path)+1);\n+\t\t\tstrcpy(str, p->two->path);\n+\t\t\tpath_list_insert_pool(str, &ret, revs->list_pool);\n+\n+\t\t\tif (DEBUG) {\n+\t\t\t\tprintf(\"rename %s -> %s\\n\",\n+\t\t\t\t       p->one->path, p->two->path);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tdiff_flush(&dopts);\n+\n+\tif (DEBUG) {\n+\t\tprintf(\"rename result: \");\n+\t\tpath_list_print(stdout, ret);\n+\t}\n+\treturn ret;\n+}\n+\n+static struct path_list* rewrite_paths(struct rev_info *revs,\n+\t\t\t\t       struct commit* commit,\n+\t\t\t\t       struct commit* parent,\n+\t\t\t\t       struct path_list* removed)\n+{\n+\tstruct path_list* new_paths = find_renames(revs, commit, parent,\n+\t\t\t\t\t\t   removed);\n+\tstruct path_list* cpaths = get_util(commit)->paths;\n+\tfor(; cpaths; cpaths = cpaths->next) {\n+\t\tif (!path_list_find(removed, cpaths->item))\n+\t\t\tpath_list_insert(cpaths->item, &new_paths);\n+\t}\n+\treturn new_paths;\n+}\n+\n+static void reinsert_commit(struct rev_info* revs, struct commit* commit)\n+{\n+\t/* Reparse the commit. We want the original parent list again\n+\t * as the set of files we are following has grown. */\n+\n+\tfree_commit_list(commit->parents);\n+\t/* We need the following, otherwise parse_commit wont do the\n+\t * right thing with the parents list. */\n+\tcommit->parents = NULL;\n+\n+\tcommit->object.parsed = 0;\n+\tparse_commit(commit);\n+\n+\tcommit->object.flags &= ~ADDED;\n+\tcommit_list_insert(commit, &revs->commits);\n+\tif (DEBUG)\n+\t\tprintf(\"reinsert commit %s\\n\",\n+\t\t       sha1_to_hex(commit->object.sha1));\n+}\n+\n+static void simplify_commit_rename(struct rev_info *revs,\n+\t\t\t\t   struct commit *commit)\n+{\n+\tstruct commit_list **pp, *parent;\n+\tstruct rev_commit_info* cutil = get_util(commit);\n+\tint tree_changed = 0;\n+\n+\tif (!commit->tree)\n+\t\treturn;\n+\n+\tif (!commit->parents) {\n+\t\tif (!same_tree_as_empty_paths(revs, commit->tree,\n+\t\t\t\t\t      cutil->paths))\n+\t\t\tcommit->object.flags |= TREECHANGE;\n+\t\treturn;\n+\t}\n+\n+\tpp = &commit->parents;\n+\twhile ((parent = *pp) != NULL) {\n+\t\tstruct commit *p = parent->item;\n+\t\tstruct path_list* removed;\n+\t\tstruct rev_commit_info* putil = get_util(p);\n+\n+\t\tparse_commit(p);\n+\t\tswitch (compare_tree_paths(revs, p, commit, &removed)) {\n+\t\tcase REV_TREE_SAME:\n+\t\t{\n+\t\t\tint do_return = 1;\n+\t\t\tif (!putil->paths)\n+\t\t\t\tputil->paths = cutil->paths;\n+\t\t\telse if (!path_list_issubseteq(cutil->paths,\n+\t\t\t\t\t\t       putil->paths)) {\n+\t\t\t\tputil->paths =\n+\t\t\t\t\tpath_list_union_pool(cutil->paths,\n+\t\t\t\t\t\t\t     putil->paths,\n+\t\t\t\t\t\t\t     revs->list_pool);\n+\t\t\t\treinsert_commit(revs, p);\n+\t\t\t\ttree_changed = 1;\n+\t\t\t\tdo_return = 0;\n+\t\t\t}\n+\t\t\tif (p->object.flags & UNINTERESTING) {\n+\t\t\t\t/* Even if a merge with an uninteresting\n+\t\t\t\t * side branch brought the entire change\n+\t\t\t\t * we are interested in, we do not want\n+\t\t\t\t * to lose the other branches of this\n+\t\t\t\t * merge, so we just keep going.\n+\t\t\t\t */\n+\t\t\t\tpp = &parent->next;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (do_return) {\n+\t\t\t\tparent->next = NULL;\n+\t\t\t\tcommit->parents = parent;\n+\t\t\t\treturn;\n+\t\t\t} else {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\n+\t\tcase REV_TREE_NEW:\n+\t\t{\n+\t\t\tstruct path_list* new_paths =\n+\t\t\t\trewrite_paths(revs, commit, p, removed);\n+\t\t\tif (new_paths) {\n+\t\t\t\tif (!putil->paths)\n+\t\t\t\t\tputil->paths = new_paths;\n+\t\t\t\telse if (!path_list_issubseteq(new_paths,\n+\t\t\t\t\t\t\t       putil->paths)) {\n+\t\t\t\t\tputil->paths =\n+\t\t\t\t\t\tpath_list_union_pool(new_paths,\n+\t\t\t\t\t\t\t\t     putil->paths,\n+\t\t\t\t\t\t\t\t     revs->list_pool);\n+\t\t\t\t\treinsert_commit(revs, p);\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tparse_commit(p);\n+\t\t\t\tp->parents = NULL;\n+\t\t\t}\n+\t\t\ttree_changed = 1;\n+\t\t\tpp = &parent->next;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tcase REV_TREE_DIFFERENT:\n+\t\t\ttree_changed = 1;\n+\t\t\tpp = &parent->next;\n+\t\t\tif (!putil->paths)\n+\t\t\t\tputil->paths = cutil->paths;\n+\t\t\telse if (!path_list_issubseteq(cutil->paths,\n+\t\t\t\t\t\t       putil->paths)) {\n+\t\t\t\tputil->paths =\n+\t\t\t\t\tpath_list_union_pool(cutil->paths,\n+\t\t\t\t\t\t\t     putil->paths,\n+\t\t\t\t\t\t\t     revs->list_pool);\n+\t\t\t\treinsert_commit(revs, p);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\tdie(\"bad tree compare for commit %s\",\n+\t\t    sha1_to_hex(commit->object.sha1));\n+\t}\n+\tif (tree_changed)\n+\t\tcommit->object.flags |= TREECHANGE;\n+}\n+\n void init_revisions(struct rev_info *revs)\n {\n \tmemset(revs, 0, sizeof(*revs));\n@@ -742,6 +1294,11 @@ int setup_revisions(int argc, const char\n \t\t\t\trevs->full_diff = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--renames\")) {\n+\t\t\t\trevs->follow_renames = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n \t\t\topts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);\n \t\t\tif (opts > 0) {\n \t\t\t\trevs->diff = 1;\n@@ -841,15 +1398,65 @@ int setup_revisions(int argc, const char\n \t\t    (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))\n \t\t\trevs->diffopt.output_format = DIFF_FORMAT_PATCH;\n \t}\n+\tif (revs->follow_renames && revs->prune_data) {\n+\t\tchar** p;\n+\t\tif (revs->remove_empty_trees)\n+\t\t\tdie(\"--renames and --remove-empty are currently \"\n+\t\t\t    \"mutually exclusive\");\n+\t\trevs->prune_fn = simplify_commit_rename;\n+\t\trevs->topo_setter = topo_setter;\n+\t\trevs->topo_getter = topo_getter;\n+\t\trevs->limited = 1;\n+\t\trevs->string_pool = xmalloc(sizeof(struct mem_pool));\n+\t\trevs->list_pool = xmalloc(sizeof(struct mem_pool));\n+\t\tmem_pool_init(revs->string_pool, 1024*4);\n+\t\tmem_pool_init(revs->list_pool, 1024*4);\n+\n+\t\trevs->initial_paths = NULL;\n+\t\tfor(p = revs->prune_data; *p; p++) {\n+\t\t\tchar* str = mem_pool_alloc(revs->string_pool,\n+\t\t\t\t\t\t   strlen(*p)+1);\n+\t\t\tstrcpy(str, *p);\n+\t\t\tpath_list_insert(str, &revs->initial_paths);\n+\t\t\tif (DEBUG)\n+\t\t\t\tprintf(\"filename: %s\\n\", *p);\n+\t\t}\n+\n+\t\tif (DEBUG)\n+\t\t\tprintf(\"prefix: %s\\n\", revs->prefix);\n+\t}\n+\n \trevs->diffopt.abbrev = revs->abbrev;\n \tdiff_setup_done(&revs->diffopt);\n \n \treturn left;\n }\n \n+void free_revisions(struct rev_info *revs)\n+{\n+\tif (revs->follow_renames) {\n+\t\tmem_pool_free(revs->string_pool);\n+\t\tmem_pool_free(revs->list_pool);\n+\n+\t\tfree(revs->string_pool);\n+\t\tfree(revs->list_pool);\n+\t}\n+}\n+\n+static void insert_by_date_commits(struct rev_info* revs,\n+\t\t\t\t   struct commit* commit)\n+{\n+\tinsert_by_date(commit, &revs->commits);\n+\tif (!revs->last_commit)\n+\t\trevs->last_commit = revs->commits;\n+\telse if (revs->last_commit->next)\n+\t\trevs->last_commit = revs->last_commit->next;\n+}\n+\n void prepare_revision_walk(struct rev_info *revs)\n {\n \tstruct object_list *list;\n+\tstruct commit_list *clist;\n \n \tlist = revs->pending_objects;\n \trevs->pending_objects = NULL;\n@@ -858,12 +1465,27 @@ void prepare_revision_walk(struct rev_in\n \t\tif (commit) {\n \t\t\tif (!(commit->object.flags & SEEN)) {\n \t\t\t\tcommit->object.flags |= SEEN;\n-\t\t\t\tinsert_by_date(commit, &revs->commits);\n+\t\t\t\tinsert_by_date_commits(revs, commit);\n \t\t\t}\n \t\t}\n \t\tlist = list->next;\n \t}\n \n+\tfor (clist = revs->commits; clist; clist = clist->next) {\n+\t\tstruct commit *commit = clist->item;\n+\t\tstruct rev_commit_info *util = get_util(commit);\n+\t\tif (!util->paths) {\n+\t\t\tstruct path_list* l;\n+\t\t\tfor (l = revs->initial_paths; l; l = l->next) {\n+\t\t\t\tif (file_exists(commit->tree, l->item)) {\n+\t\t\t\t\tpath_list_insert(l->item, &util->paths);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (DEBUG)\n+\t\t\t\tprintf(\"NULL paths\\n\");\n+\t\t}\n+\t}\n+\n \tif (revs->no_walk)\n \t\treturn;\n \tif (revs->limited)\ndiff --git a/revision.h b/revision.h\nindex bdbdd23..a2d9666 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -9,15 +9,19 @@ #define TMP_MARK\t(1u<<4) /* for isolated\n #define BOUNDARY\t(1u<<5)\n #define BOUNDARY_SHOW\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n+#define DUPLICATE\t(1u<<8)\n \n struct rev_info;\n struct log_info;\n+struct path_list;\n \n typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);\n \n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n+\t  /* the last entry in the 'commits' list */\n+\tstruct commit_list *last_commit;\n \tstruct object_list *pending_objects;\n \n \t/* Basic information */\n@@ -39,7 +43,8 @@ struct rev_info {\n \t\t\tlimited:1,\n \t\t\tunpacked:1,\n \t\t\tboundary:1,\n-\t\t\tparents:1;\n+\t\t\tparents:1,\n+\t\t\tfollow_renames:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n@@ -70,6 +75,11 @@ struct rev_info {\n \tstruct diff_options diffopt;\n \tstruct diff_options pruning;\n \n+\t/* Rename following */\n+\tstruct path_list* initial_paths;\n+\tstruct mem_pool* string_pool;\n+\tstruct mem_pool* list_pool;\n+\n \ttopo_sort_set_fn_t topo_setter;\n \ttopo_sort_get_fn_t topo_getter;\n };\n@@ -84,6 +94,7 @@ extern int rev_compare_tree(struct rev_i\n \n extern void init_revisions(struct rev_info *revs);\n extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);\n+extern void free_revisions(struct rev_info *revs);\n extern void prepare_revision_walk(struct rev_info *revs);\n extern struct commit *get_revision(struct rev_info *revs);\n \n@@ -101,4 +112,29 @@ extern struct object_list **add_object(s\n \t\t\t\t       struct name_path *path,\n \t\t\t\t       const char *name);\n \n+struct path_list {\n+\tchar* item;\n+\tstruct path_list *next;\n+};\n+\n+struct rev_commit_info {\n+\tstruct path_list* paths;\n+\tvoid* topo_data;\n+};\n+\n+extern const struct path_list* path_list_find(const struct path_list* list,\n+\t\t\t\t\t      const char* path);\n+extern struct path_list* path_list_insert(char *item,\n+\t\t\t\t\t  struct path_list **list_p);\n+extern void path_list_print(FILE*, struct path_list* list);\n+extern void free_path_list(struct path_list *list);\n+extern const char** convert_to_pathspec(struct path_list* l1,\n+\t\t\t\t\tstruct path_list* l2);\n+\n+extern void rev_setup_diffopt_paths(struct rev_info* revs,\n+\t\t\t\t    struct commit* commit,\n+\t\t\t\t    struct commit* parent);\n+extern void rev_free_diffopt_paths(struct rev_info* revs,\n+\t\t\t\t   struct commit* commit);\n+\n #endif\ndiff --git a/t/t6004-rev-list-rename.sh b/t/t6004-rev-list-rename.sh\nnew file mode 100755\nindex 0000000..e2d37d1\n--- /dev/null\n+++ b/t/t6004-rev-list-rename.sh\n@@ -0,0 +1,100 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2006 Fredrik Kuivinen\n+# Bases on t6003-rev-list-topo-order.sh\n+\n+test_description='Tests git-rev-list --renames functionality'\n+\n+. ./test-lib.sh\n+. ../t6000lib.sh # t6xxx specific functions\n+\n+list_duplicates()\n+{\n+    \"$@\" | sort | uniq -d\n+}\n+\n+unique_commit_wd()\n+{\n+\t_text=$1\n+        _tree=`git-write-tree`\n+\tshift 1\n+    \techo $_text | git-commit-tree $_tree \"$@\"\n+}\n+\n+cp ../../COPYING .\n+cp ../../README .\n+git-update-index --add COPYING README\n+hide_error save_tag root unique_commit_wd root\n+\n+echo '1' >> COPYING\n+git-update-index COPYING\n+save_tag l0 unique_commit_wd l0 -p root\n+\n+echo '1' >> README\n+git-update-index COPYING README\n+save_tag l1 unique_commit_wd l1 -p l0\n+\n+echo '2' >> COPYING\n+echo '2' >> README\n+git-update-index COPYING README\n+save_tag l2 unique_commit_wd l2 -p l1\n+\n+git mv README README-ren\n+save_tag l3 unique_commit_wd l3 -p l2\n+\n+git mv README-ren README-ren2\n+save_tag a0 unique_commit_wd a0 -p l3\n+\n+git mv README-ren2 README-ren\n+git mv COPYING COPYING-ren\n+\n+save_tag b0 unique_commit_wd b0 -p l3\n+git mv README-ren README-ren2\n+\n+save_tag m unique_commit_wd m -p a0 -p b0\n+git mv README-ren2 README-ren3\n+echo '3' >> COPYING-ren\n+git-update-index COPYING-ren\n+save_tag head unique_commit_wd head -p m\n+\n+test_output_expect_success \"--renames head -- README-ren3\" \"git-rev-list --topo-order --renames head -- README-ren3\" <<EOF\n+head\n+a0\n+l3\n+l2\n+l1\n+root\n+EOF\n+\n+test_output_expect_success \"--renames head a0 -- COPYING\" \"git-rev-list --topo-order --renames head a0 -- COPYING\" <<EOF\n+l2\n+l0\n+root\n+EOF\n+\n+test_output_expect_success \"--renames head -- README-ren3 COPYING-ren\" \"git-rev-list --topo-order --renames head -- README-ren3 COPYING-ren\" <<EOF\n+head\n+m\n+b0\n+a0\n+l3\n+l2\n+l1\n+l0\n+root\n+EOF\n+\n+test_output_expect_success \"--renames --all -- README-ren3 COPYING\" \"git-rev-list --topo-order --renames --all -- README-ren3 COPYING\" <<EOF\n+head\n+a0\n+l3\n+l2\n+l1\n+l0\n+root\n+EOF\n+\n+test_output_expect_success \"--renames head -- COPYING README\" \"git-rev-list --renames head -- COPYING README\" <<EOF\n+EOF\n+\n+test_done\n"},{"id":"25047","messageId":"Pine.LNX.4.63.0608092205410.1800@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"5180","inReplyTo":"20060809192815.GA7954@c165.ib.student.liu.se","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-08-09T20:08:12Z","receivedAt":"2006-08-09T20:08:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nyour struct path_list and find_renames() is so similar to what is already \nin merge-recursive.c that it is not even funny.\n\nFurthermore, the struct path_list from path-list.[ch] implements a sorted \nlist, so that lookups are way cheaper than with linked lists.\n\nIMHO this whole renaming stuff should go into a new file, renames.c, and \nbe reused as often as possible.\n\nCiao,\nDscho\n"},{"id":"25056","messageId":"7vk65h1t8q.fsf@assigned-by-dhcp.cox.net","threadId":"5180","inReplyTo":"20060809192815.GA7954@c165.ib.student.liu.se","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-08-09T21:42:13Z","receivedAt":"2006-08-09T21:42:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fredrik Kuivinen <freku045@student.liu.se> writes:\n\n> I don't think it was dropped in favor --full-history.\n\nCorrect.\n\nThe --full-history option is about merge simplification and has\nnothing to do with renames.\n\nThe thing is, your patch, while I very much liked the direction\nit was taking us, was so intrusive that it scared the h*ck out\nof me ;-).\n\n>> What is needed by gitweb is for git-rev-list to not only follow revisions\n>> of file contents across renames, and return more revisions, \n>\n> Note that it is not enough to only return more revisions.\n>\n> For example, consider the commits (newest commit first)\n> A: Rename \"bar\" to \"foo\"\n> B: No changes to \"bar\"\n> C: No changes to \"bar\", delete \"foo\"\n> <more commits here>\n>\n> Then you want \"git-rev-list --renames A -- foo\" to return A,... not A,C,...\n\nYes.  For this \"following renames for a single file\" example, we\nshould not extend the pathspec which originally starts out as\n\"foo\" to _include_ \"bar\"; instead it should _replace_, and after\nthat point we should not care about \"foo\".\n\nThis was another reason I shied away from your patch back then.\nWe would need to think about interactions between this pathspec\nfor a single file (which should be switched) and pathspecs for\ndirectories (\"more useful form of usage than single file\", as\nLinus would put it).\n\nI have this vague feeling, without revisiting the code to make\nan informed argument, that it might be cleaner and with less\nimpact (read: smaller chance to break the \"directories\" usage)\nif we special case \"follow a single file\" case.  I dunno.\n\nAlso I was not sure how well your pathspec switching worked\nacross forks and merges.\n\n                            M bar\n           .----------------4------5\n          /                         \\\n A bar   /    R bar->foo  M foo      \\     M foo\n 0------1-----2-----------3-----------6----7-------8\n\nSuppose we are at 8 and start digging for the history of \"foo\".\nOur pathspec starts out as \"foo\" at 8 and stays so until we hit\n6.  If the lower branch renamed bar->foo while the upper didn't,\nand merge-recursive merged them correctly at 6, we would use\n\"foo\" as pathspec while on the lower branch while traversing 6,\n3 and 2 (at that point we switch to \"bar\").  On the upper\nbranch, we switch pathspec to \"bar\" while traversing 6, 5, 4.\nWhen we hit 1, pathspec of both of its children (2 and 4) happen\nto match in this example.  But what would we do if for some\nreason they didn't?  Do we care?  Is that die(\"an internal\nerror\")?  I did not have a good anser to that question, and I\nstill don't.\n"},{"id":"25086","messageId":"ebf2pu$8qv$1@sea.gmane.org","threadId":"5180","inReplyTo":"7vk65h1t8q.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-10T10:46:44Z","receivedAt":"2006-08-10T10:46:44Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Junio C Hamano wrote:\n\n> Fredrik Kuivinen <freku045@student.liu.se> writes:\n> \n>> I don't think it was dropped in favor --full-history.\n> \n> Correct.\n> \n> The --full-history option is about merge simplification and has\n> nothing to do with renames.\n\nBy the way, --full-history option is not documented: neither in\nDocumentation/git-rev-list.txt, nor in git-rev-list --usage\n\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"25117","messageId":"20060810213920.GC13446@c165.ib.student.liu.se","threadId":"5180","inReplyTo":"7vk65h1t8q.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Fredrik Kuivinen","fromEmail":"freku045@student.liu.se","sentAt":"2006-08-10T21:39:20Z","receivedAt":"2006-08-10T21:39:20Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"On Wed, Aug 09, 2006 at 02:42:13PM -0700, Junio C Hamano wrote:\n> Fredrik Kuivinen <freku045@student.liu.se> writes:\n>\n> The thing is, your patch, while I very much liked the direction\n> it was taking us, was so intrusive that it scared the h*ck out\n> of me ;-).\n> \n\nI kind of suspected that :) It certainly is intrusive and it changes\nrevision.c which is at the very core of git.\n\n\n> >> What is needed by gitweb is for git-rev-list to not only follow revisions\n> >> of file contents across renames, and return more revisions, \n> >\n> > Note that it is not enough to only return more revisions.\n> >\n> > For example, consider the commits (newest commit first)\n> > A: Rename \"bar\" to \"foo\"\n> > B: No changes to \"bar\"\n> > C: No changes to \"bar\", delete \"foo\"\n> > <more commits here>\n> >\n> > Then you want \"git-rev-list --renames A -- foo\" to return A,... not A,C,...\n> \n> Yes.  For this \"following renames for a single file\" example, we\n> should not extend the pathspec which originally starts out as\n> \"foo\" to _include_ \"bar\"; instead it should _replace_, and after\n> that point we should not care about \"foo\".\n> \n> This was another reason I shied away from your patch back then.\n> We would need to think about interactions between this pathspec\n> for a single file (which should be switched) and pathspecs for\n> directories (\"more useful form of usage than single file\", as\n> Linus would put it).\n\nI mentioned a couple of different strategies for handling the\ndirectory case in the original mail. \n\n> I have this vague feeling, without revisiting the code to make\n> an informed argument, that it might be cleaner and with less\n> impact (read: smaller chance to break the \"directories\" usage)\n> if we special case \"follow a single file\" case.  I dunno.\n\nYes, it most certainly is. We actually already have the \"follow a\nsingle file from a single commit\" case implemented already, in\nblame.c. But it is much cooler to be able to track multiple files from\nseveral different starting points (\"git-rev-list --renames A B C --\nfoo bar\") :)   It also makes the git-rev-list interface more\nconsistent. No special cases such as \"if you use --renames then you\ncan only specify one pathspec (which has to be a filename)\".\n\n> Also I was not sure how well your pathspec switching worked\n> across forks and merges.\n> \n>                             M bar\n>            .----------------4------5\n>           /                         \\\n>  A bar   /    R bar->foo  M foo      \\     M foo\n>  0------1-----2-----------3-----------6----7-------8\n> \n> Suppose we are at 8 and start digging for the history of \"foo\".\n> Our pathspec starts out as \"foo\" at 8 and stays so until we hit\n> 6.  If the lower branch renamed bar->foo while the upper didn't,\n> and merge-recursive merged them correctly at 6, we would use\n> \"foo\" as pathspec while on the lower branch while traversing 6,\n> 3 and 2 (at that point we switch to \"bar\").  On the upper\n> branch, we switch pathspec to \"bar\" while traversing 6, 5, 4.\n> When we hit 1, pathspec of both of its children (2 and 4) happen\n> to match in this example.  But what would we do if for some\n> reason they didn't?  Do we care?  Is that die(\"an internal\n> error\")?  I did not have a good anser to that question, and I\n> still don't.\n\nI _think_ that the patch as it currently is would track both files\nfrom 1 and onwards. That is, if the upper branch tracks \"foobar\" when\nwe hit 1 and the lower branch tracks \"bar\" then we would track both\n\"foobar\" and \"bar\" at 1 and 0.\n\n- Fredrik\n"},{"id":"25249","messageId":"ebplmi$b77$1@sea.gmane.org","threadId":"5180","inReplyTo":"200608091257.19461.jnareb@gmail.com","subject":"Re: [PATCH and RFC] gitweb: Remove --full-history from git_history","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-14T11:10:54Z","receivedAt":"2006-08-14T11:10:54Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jakub Narebski wrote:\n\n> PROPOSAL:\n> ---------\n> \n> Implement --follow/--follow-path/--follow-contents option to git-rev-list,\n> which would output besides revision ids also current path limit; the\nformat\n> could be for example:\n> \n>   <commit> [ -- <paths>...]\n> \n> for example:\n> $ git rev-list --follow db58b69ba -- gitweb/test/file+plus+sign\n> 0a8f4f0020cb35095005852c0797f0b90e9ebb74 -- gitweb/test/file+plus+sign\n> 85852d44e48c1d1c6d815cc5fccf1b580f2f2cad -- test/file+plus+sign\n> cc3245b6512a01d74c0fd460d762ba8a1e8b968a -- test/file+plus+sign\n\nAnd of course git-diff-tree --stdin should accept not only list\nof revisions or list of tree pairs, but also path limits.\n\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"}]}