{"thread":{"id":"17115","subject":"[RFC/PATCH 3/7] replace_object: add mechanism to replace objects found in \"refs/replace/\"","startedAt":"2009-01-12T17:44:03Z","lastAt":"2009-01-15T17:40:12Z","messageCount":4,"participants":["Christian Couder","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"100142","messageId":"20090112184403.ebb99b75.chriscool@tuxfamily.org","threadId":"17115","inReplyTo":null,"subject":"[RFC/PATCH 3/7] replace_object: add mechanism to replace objects found in \"refs/replace/\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2009-01-12T17:44:03Z","receivedAt":"2009-01-12T17:44:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"The code implementing this mechanism has been copied from the commit\ngraft code.\n\nThis mechanism is used in \"read_sha1_file\". sha1 passed to this\nfunction that match a ref name in \"refs/replace/\" are replaced by\nthe sha1 that has been read in the ref.\n\nWe \"die\" if the replacement recursion depth is too high or if we\ncan't read the replacement object.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Makefile         |    1 +\n commit.h         |    2 +\n replace_object.c |  116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n sha1_file.c      |   14 +++++-\n 4 files changed, 130 insertions(+), 3 deletions(-)\n create mode 100644 replace_object.c\n\ndiff --git a/Makefile b/Makefile\nindex dee97c1..7cf5d9a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -471,6 +471,7 @@ LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\n LIB_OBJS += refs.o\n LIB_OBJS += remote.o\n+LIB_OBJS += replace_object.o\n LIB_OBJS += rerere.o\n LIB_OBJS += revision.o\n LIB_OBJS += run-command.o\ndiff --git a/commit.h b/commit.h\nindex 3a7b06a..37aa763 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -122,6 +122,8 @@ struct commit_graft *read_graft_line(char *buf, int len);\n int register_commit_graft(struct commit_graft *, int);\n struct commit_graft *lookup_commit_graft(const unsigned char *sha1);\n \n+const unsigned char *lookup_replace_object(const unsigned char *sha1);\n+\n extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);\n extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);\n extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);\ndiff --git a/replace_object.c b/replace_object.c\nnew file mode 100644\nindex 0000000..25b3ef3\n--- /dev/null\n+++ b/replace_object.c\n@@ -0,0 +1,116 @@\n+#include \"cache.h\"\n+#include \"refs.h\"\n+\n+static struct replace_object {\n+\tunsigned char sha1[2][20];\n+} **replace_object;\n+\n+static int replace_object_alloc, replace_object_nr;\n+\n+static int replace_object_pos(const unsigned char *sha1)\n+{\n+\tint lo, hi;\n+\tlo = 0;\n+\thi = replace_object_nr;\n+\twhile (lo < hi) {\n+\t\tint mi = (lo + hi) / 2;\n+\t\tstruct replace_object *rep = replace_object[mi];\n+\t\tint cmp = hashcmp(sha1, rep->sha1[0]);\n+\t\tif (!cmp)\n+\t\t\treturn mi;\n+\t\tif (cmp < 0)\n+\t\t\thi = mi;\n+\t\telse\n+\t\t\tlo = mi + 1;\n+\t}\n+\treturn -lo - 1;\n+}\n+\n+static int register_replace_object(struct replace_object *replace,\n+\t\t\t\t   int ignore_dups)\n+{\n+\tint pos = replace_object_pos(replace->sha1[0]);\n+\n+\tif (0 <= pos) {\n+\t\tif (ignore_dups)\n+\t\t\tfree(replace);\n+\t\telse {\n+\t\t\tfree(replace_object[pos]);\n+\t\t\treplace_object[pos] = replace;\n+\t\t}\n+\t\treturn 1;\n+\t}\n+\tpos = -pos - 1;\n+\tif (replace_object_alloc <= ++replace_object_nr) {\n+\t\treplace_object_alloc = alloc_nr(replace_object_alloc);\n+\t\treplace_object = xrealloc(replace_object,\n+\t\t\t\t\t  sizeof(*replace_object) *\n+\t\t\t\t\t  replace_object_alloc);\n+\t}\n+\tif (pos < replace_object_nr)\n+\t\tmemmove(replace_object + pos + 1,\n+\t\t\treplace_object + pos,\n+\t\t\t(replace_object_nr - pos - 1) *\n+\t\t\tsizeof(*replace_object));\n+\treplace_object[pos] = replace;\n+\treturn 0;\n+}\n+\n+static int register_replace_ref(const char *refname,\n+\t\t\t\tconst unsigned char *sha1,\n+\t\t\t\tint flag, void *cb_data)\n+{\n+\t/* Get sha1 from refname */\n+\tconst char *slash = strrchr(refname, '/');\n+\tconst char *hash = slash ? slash + 1 : refname;\n+\tstruct replace_object * repl_obj = xmalloc(sizeof(*repl_obj));\n+\n+\tif (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) {\n+\t\tfree(repl_obj);\n+\t\twarning(\"bad replace ref name: %s\", refname);\n+\t}\n+\n+\t/* Copy sha1 from the read ref */\n+\thashcpy(repl_obj->sha1[1], sha1);\n+\n+\t/* Register new object */\n+\tif (register_replace_object(repl_obj, 1))\n+\t\twarning(\"duplicate replace ref: %s\", refname);\n+\n+\treturn 0;\n+}\n+\n+static void prepare_replace_object(void)\n+{\n+\tstatic int replace_object_prepared;\n+\n+\tif (replace_object_prepared)\n+\t\treturn;\n+\n+\tfor_each_replace_ref(register_replace_ref, NULL);\n+\treplace_object_prepared = 1;\n+}\n+\n+/* We allow \"recursive\" replacement. Only within reason, though */\n+#define MAXREPLACEDEPTH 5\n+\n+const unsigned char *lookup_replace_object(const unsigned char *sha1)\n+{\n+\tint pos, depth = MAXREPLACEDEPTH;\n+\tconst unsigned char *cur = sha1;\n+\n+\tprepare_replace_object();\n+\n+\t/* Try to recursively replace the object */\n+\tdo {\n+\t\tif (--depth < 0)\n+\t\t\tdie(\"replace depth too high for object %s\",\n+\t\t\t    sha1_to_hex(sha1));\n+\n+\t\tpos = replace_object_pos(cur);\n+\t\tif (0 <= pos)\n+\t\t\tcur = replace_object[pos]->sha1[1];\n+\t} while (0 <= pos);\n+\n+\treturn cur;\n+}\ndiff --git a/sha1_file.c b/sha1_file.c\nindex f08493f..4f2fd10 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -2163,10 +2163,18 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,\n void *read_sha1_file(const unsigned char *sha1, enum object_type *type,\n \t\t     unsigned long *size)\n {\n-\tvoid *data = read_object(sha1, type, size);\n+\tconst unsigned char *repl = lookup_replace_object(sha1);\n+\tvoid *data = read_object(repl, type, size);\n+\n+\t/* die if we replaced an object with one that does not exist */\n+\tif (!data && repl != sha1)\n+\t\tdie(\"replacement %s not found for %s\",\n+\t\t    sha1_to_hex(repl), sha1_to_hex(sha1));\n+\n \t/* legacy behavior is to die on corrupted objects */\n-\tif (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))\n-\t\tdie(\"object %s is corrupted\", sha1_to_hex(sha1));\n+\tif (!data && (has_loose_object(repl) || has_packed_and_bad(repl)))\n+\t\tdie(\"object %s is corrupted\", sha1_to_hex(repl));\n+\n \treturn data;\n }\n \n-- \n1.6.1.83.g16e5\n"},{"id":"100215","messageId":"7vd4esf0tv.fsf@gitster.siamese.dyndns.org","threadId":"17115","inReplyTo":"20090112184403.ebb99b75.chriscool@tuxfamily.org","subject":"Re: [RFC/PATCH 3/7] replace_object: add mechanism to replace objects found in \"refs/replace/\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-13T01:31:56Z","receivedAt":"2009-01-13T01:31:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <chriscool@tuxfamily.org> writes:\n\n> diff --git a/replace_object.c b/replace_object.c\n> new file mode 100644\n> index 0000000..25b3ef3\n> --- /dev/null\n> +++ b/replace_object.c\n> @@ -0,0 +1,116 @@\n> +#include \"cache.h\"\n> +#include \"refs.h\"\n> +\n> +static struct replace_object {\n> +\tunsigned char sha1[2][20];\n> +} **replace_object;\n> +\n> +static int replace_object_alloc, replace_object_nr;\n> +\n> +static int replace_object_pos(const unsigned char *sha1)\n> +{\n> +\tint lo, hi;\n> +\tlo = 0;\n> +\thi = replace_object_nr;\n> +\twhile (lo < hi) {\n> +\t\tint mi = (lo + hi) / 2;\n> +\t\tstruct replace_object *rep = replace_object[mi];\n> +\t\tint cmp = hashcmp(sha1, rep->sha1[0]);\n> +\t\tif (!cmp)\n> +\t\t\treturn mi;\n> +\t\tif (cmp < 0)\n> +\t\t\thi = mi;\n> +\t\telse\n> +\t\t\tlo = mi + 1;\n> +\t}\n> +\treturn -lo - 1;\n> +}\n\nHmm, this is a tangent of this topic, but I wonder if we can do something\nmore generic to factor out many binary search like this we have throughout\nthe code.  Also I wonder if they can be made more efficient by taking\nadvantage of the fact that our hash is expected to produce a good uniform\ndistribution, similar to the way patch-ids.c::patch_pos() does this.\n\nI guess the performance should not matter much for this table, as we\nexpect there won't be massive object replacements.\n\nAlso I recall Dscho muttered something about hashmap I didn't quite\nunderstand.\n\n> +static int register_replace_ref(const char *refname,\n> +\t\t\t\tconst unsigned char *sha1,\n> +\t\t\t\tint flag, void *cb_data)\n> +{\n> +\t/* Get sha1 from refname */\n> +\tconst char *slash = strrchr(refname, '/');\n> +\tconst char *hash = slash ? slash + 1 : refname;\n> +\tstruct replace_object * repl_obj = xmalloc(sizeof(*repl_obj));\n\n\tstruct replace_object *repl_obj = ...\n\n> +\tif (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) {\n> +\t\tfree(repl_obj);\n> +\t\twarning(\"bad replace ref name: %s\", refname);\n> +\t}\n> +\n> +\t/* Copy sha1 from the read ref */\n> +\thashcpy(repl_obj->sha1[1], sha1);\n\nUpon an error, you free and warn and then still copy into it?\n\n> +\t/* Register new object */\n> +\tif (register_replace_object(repl_obj, 1))\n> +\t\twarning(\"duplicate replace ref: %s\", refname);\n\nI'd say this is a grave error and should be reported as a repository\ncorruption.\n\n> +static void prepare_replace_object(void)\n> +{\n> +\tstatic int replace_object_prepared;\n> +\n> +\tif (replace_object_prepared)\n> +\t\treturn;\n> +\n> +\tfor_each_replace_ref(register_replace_ref, NULL);\n> +\treplace_object_prepared = 1;\n> +}\n> +\n> +/* We allow \"recursive\" replacement. Only within reason, though */\n> +#define MAXREPLACEDEPTH 5\n> +\n> +const unsigned char *lookup_replace_object(const unsigned char *sha1)\n> +{\n> +\tint pos, depth = MAXREPLACEDEPTH;\n> +\tconst unsigned char *cur = sha1;\n> +\n> +\tprepare_replace_object();\n> +\n> +\t/* Try to recursively replace the object */\n> +\tdo {\n> +\t\tif (--depth < 0)\n> +\t\t\tdie(\"replace depth too high for object %s\",\n> +\t\t\t    sha1_to_hex(sha1));\n> +\n> +\t\tpos = replace_object_pos(cur);\n> +\t\tif (0 <= pos)\n> +\t\t\tcur = replace_object[pos]->sha1[1];\n> +\t} while (0 <= pos);\n> +\n> +\treturn cur;\n> +}\n\nSince your paradigm is prepare replacement once at the beginning, never\nallowing to update it, I think you can update the table while you look it\nup.  E.g. if A->B and B->C exists, and A is asked for, you find A->B (to\ntentatively make cur to point at B) and then you find B->C, and before\nreturning you can rewrite the first mapping to A->C.  Later look-up won't\nneed to dereference the table twice that way.\n\nThis assumes that there will be small number of replacements, but the same\nobject can be asked for more than once during the process.\n\n> diff --git a/sha1_file.c b/sha1_file.c\n> index f08493f..4f2fd10 100644\n> --- a/sha1_file.c\n> +++ b/sha1_file.c\n> @@ -2163,10 +2163,18 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,\n>  void *read_sha1_file(const unsigned char *sha1, enum object_type *type,\n>  \t\t     unsigned long *size)\n>  {\n> -\tvoid *data = read_object(sha1, type, size);\n> +\tconst unsigned char *repl = lookup_replace_object(sha1);\n> +\tvoid *data = read_object(repl, type, size);\n> +\n> +\t/* die if we replaced an object with one that does not exist */\n> +\tif (!data && repl != sha1)\n> +\t\tdie(\"replacement %s not found for %s\",\n> +\t\t    sha1_to_hex(repl), sha1_to_hex(sha1));\n> +\n>  \t/* legacy behavior is to die on corrupted objects */\n> -\tif (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))\n> -\t\tdie(\"object %s is corrupted\", sha1_to_hex(sha1));\n> +\tif (!data && (has_loose_object(repl) || has_packed_and_bad(repl)))\n> +\t\tdie(\"object %s is corrupted\", sha1_to_hex(repl));\n> +\n>  \treturn data;\n>  }\n\nLater we'd need a global switch to forbid the replacement for connectivity\nwalkers, but other than that I think this is sane.\n\nI also looked at 1/ and 2/ which looked Ok.\n\nThanks.\n"},{"id":"100553","messageId":"200901151044.45967.chriscool@tuxfamily.org","threadId":"17115","inReplyTo":"7vd4esf0tv.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH 3/7] replace_object: add mechanism to replace objects found in \"refs/replace/\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2009-01-15T09:44:45Z","receivedAt":"2009-01-15T09:44:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Le mardi 13 janvier 2009, Junio C Hamano a écrit :\n> Christian Couder <chriscool@tuxfamily.org> writes:\n> > diff --git a/replace_object.c b/replace_object.c\n> > new file mode 100644\n> > index 0000000..25b3ef3\n> > --- /dev/null\n> > +++ b/replace_object.c\n> > @@ -0,0 +1,116 @@\n> > +#include \"cache.h\"\n> > +#include \"refs.h\"\n> > +\n> > +static struct replace_object {\n> > +\tunsigned char sha1[2][20];\n> > +} **replace_object;\n> > +\n> > +static int replace_object_alloc, replace_object_nr;\n> > +\n> > +static int replace_object_pos(const unsigned char *sha1)\n> > +{\n> > +\tint lo, hi;\n> > +\tlo = 0;\n> > +\thi = replace_object_nr;\n> > +\twhile (lo < hi) {\n> > +\t\tint mi = (lo + hi) / 2;\n> > +\t\tstruct replace_object *rep = replace_object[mi];\n> > +\t\tint cmp = hashcmp(sha1, rep->sha1[0]);\n> > +\t\tif (!cmp)\n> > +\t\t\treturn mi;\n> > +\t\tif (cmp < 0)\n> > +\t\t\thi = mi;\n> > +\t\telse\n> > +\t\t\tlo = mi + 1;\n> > +\t}\n> > +\treturn -lo - 1;\n> > +}\n>\n> Hmm, this is a tangent of this topic, but I wonder if we can do something\n> more generic to factor out many binary search like this we have\n> throughout the code.  Also I wonder if they can be made more efficient by\n> taking advantage of the fact that our hash is expected to produce a good\n> uniform distribution, similar to the way patch-ids.c::patch_pos() does\n> this.\n>\n> I guess the performance should not matter much for this table, as we\n> expect there won't be massive object replacements.\n>\n> Also I recall Dscho muttered something about hashmap I didn't quite\n> understand.\n\nYeah, maybe it's possible to get faster code and to refactor the \nmany binary search we have, but I will leave that for latter or for other \npeople interested in these topics, if you let me.\n\n> > +static int register_replace_ref(const char *refname,\n> > +\t\t\t\tconst unsigned char *sha1,\n> > +\t\t\t\tint flag, void *cb_data)\n> > +{\n> > +\t/* Get sha1 from refname */\n> > +\tconst char *slash = strrchr(refname, '/');\n> > +\tconst char *hash = slash ? slash + 1 : refname;\n> > +\tstruct replace_object * repl_obj = xmalloc(sizeof(*repl_obj));\n>\n> \tstruct replace_object *repl_obj = ...\n\nOk.\n\n> > +\tif (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) {\n> > +\t\tfree(repl_obj);\n> > +\t\twarning(\"bad replace ref name: %s\", refname);\n> > +\t}\n> > +\n> > +\t/* Copy sha1 from the read ref */\n> > +\thashcpy(repl_obj->sha1[1], sha1);\n>\n> Upon an error, you free and warn and then still copy into it?\n\nOoops, I forgot a \"return 0;\" statement after the warning.\n\n> > +\t/* Register new object */\n> > +\tif (register_replace_object(repl_obj, 1))\n> > +\t\twarning(\"duplicate replace ref: %s\", refname);\n>\n> I'd say this is a grave error and should be reported as a repository\n> corruption.\n\nIf we let people have a set of replace refs in \"refs/replace/\" and another \none in \"refs/replace/bisect/\", and the latter one is used only when \nbisecting, then it may happen that the same commit has one ref \nin \"refs/replace/\" and another one in \"refs/replace/bisect/\". In this case \nit should probably be considered as something we should not even warn on, \nand the replace ref in \"refs/replace/bisect/\" should be used when \nbisecting.\n\nBut, as we don't have a mechanism to do that yet, you are right, we should \nprobably \"die\" for now here.\n\n> > +static void prepare_replace_object(void)\n> > +{\n> > +\tstatic int replace_object_prepared;\n> > +\n> > +\tif (replace_object_prepared)\n> > +\t\treturn;\n> > +\n> > +\tfor_each_replace_ref(register_replace_ref, NULL);\n> > +\treplace_object_prepared = 1;\n> > +}\n> > +\n> > +/* We allow \"recursive\" replacement. Only within reason, though */\n> > +#define MAXREPLACEDEPTH 5\n> > +\n> > +const unsigned char *lookup_replace_object(const unsigned char *sha1)\n> > +{\n> > +\tint pos, depth = MAXREPLACEDEPTH;\n> > +\tconst unsigned char *cur = sha1;\n> > +\n> > +\tprepare_replace_object();\n> > +\n> > +\t/* Try to recursively replace the object */\n> > +\tdo {\n> > +\t\tif (--depth < 0)\n> > +\t\t\tdie(\"replace depth too high for object %s\",\n> > +\t\t\t    sha1_to_hex(sha1));\n> > +\n> > +\t\tpos = replace_object_pos(cur);\n> > +\t\tif (0 <= pos)\n> > +\t\t\tcur = replace_object[pos]->sha1[1];\n> > +\t} while (0 <= pos);\n> > +\n> > +\treturn cur;\n> > +}\n>\n> Since your paradigm is prepare replacement once at the beginning, never\n> allowing to update it, I think you can update the table while you look it\n> up.  E.g. if A->B and B->C exists, and A is asked for, you find A->B (to\n> tentatively make cur to point at B) and then you find B->C, and before\n> returning you can rewrite the first mapping to A->C.  Later look-up won't\n> need to dereference the table twice that way.\n>\n> This assumes that there will be small number of replacements, but the\n> same object can be asked for more than once during the process.\n\nIf we allow different sets of replace refs, for example A->B \nin \"refs/replace/\" and B->C in \"refs/replace/bisect/\", then we cannot \nrewrite as you suggest. We could add A->C in \"refs/replace/bisect/\", so \nthat it overcomes A->B and B->C when we bisect, but we would not gain much.\n\nSo I prefer not to do that right now. Maybe later if we decide we really \ndon't want to allow different sets of replace refs, we can do what you \nsuggest. \n\n> > diff --git a/sha1_file.c b/sha1_file.c\n> > index f08493f..4f2fd10 100644\n> > --- a/sha1_file.c\n> > +++ b/sha1_file.c\n> > @@ -2163,10 +2163,18 @@ static void *read_object(const unsigned char\n> > *sha1, enum object_type *type, void *read_sha1_file(const unsigned char\n> > *sha1, enum object_type *type, unsigned long *size)\n> >  {\n> > -\tvoid *data = read_object(sha1, type, size);\n> > +\tconst unsigned char *repl = lookup_replace_object(sha1);\n> > +\tvoid *data = read_object(repl, type, size);\n> > +\n> > +\t/* die if we replaced an object with one that does not exist */\n> > +\tif (!data && repl != sha1)\n> > +\t\tdie(\"replacement %s not found for %s\",\n> > +\t\t    sha1_to_hex(repl), sha1_to_hex(sha1));\n> > +\n> >  \t/* legacy behavior is to die on corrupted objects */\n> > -\tif (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))\n> > -\t\tdie(\"object %s is corrupted\", sha1_to_hex(sha1));\n> > +\tif (!data && (has_loose_object(repl) || has_packed_and_bad(repl)))\n> > +\t\tdie(\"object %s is corrupted\", sha1_to_hex(repl));\n> > +\n> >  \treturn data;\n> >  }\n>\n> Later we'd need a global switch to forbid the replacement for\n> connectivity walkers, \n\nYeah, I am slowly working on it. My next series (hopefully in a few days) \nwhere the above errors are fixed will include it.\n\n> but other than that I think this is sane. \n>\n> I also looked at 1/ and 2/ which looked Ok.\n\nThanks,\nChristian.\n"},{"id":"100638","messageId":"200901151840.12511.chriscool@tuxfamily.org","threadId":"17115","inReplyTo":"200901151044.45967.chriscool@tuxfamily.org","subject":"Re: [RFC/PATCH 3/7] replace_object: add mechanism to replace objects found in \"refs/replace/\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2009-01-15T17:40:12Z","receivedAt":"2009-01-15T17:40:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Le jeudi 15 janvier 2009, Christian Couder a écrit :\n> Le mardi 13 janvier 2009, Junio C Hamano a écrit :\n>\n> > Since your paradigm is prepare replacement once at the beginning, never\n> > allowing to update it, I think you can update the table while you look\n> > it up.  E.g. if A->B and B->C exists, and A is asked for, you find A->B\n> > (to tentatively make cur to point at B) and then you find B->C, and\n> > before returning you can rewrite the first mapping to A->C.  Later\n> > look-up won't need to dereference the table twice that way.\n> >\n> > This assumes that there will be small number of replacements, but the\n> > same object can be asked for more than once during the process.\n>\n> If we allow different sets of replace refs, for example A->B\n> in \"refs/replace/\" and B->C in \"refs/replace/bisect/\", then we cannot\n> rewrite as you suggest. We could add A->C in \"refs/replace/bisect/\", so\n> that it overcomes A->B and B->C when we bisect, but we would not gain\n> much.\n\nSorry, I just realized I misunderstood what you said. I don't know why but I \nthought you talked about updating the refs. But in fact you are right it \nshould be possible to update the \"replace_object\" table.\n\nRegards,\nChristian.\n"}]}