fetch-pack, when fetching a literal SHA-1 from a server that is not
configured with uploadpack.allowtipsha1inwant (or similar), always
returns an error message of the form "Server does not allow request for
unadvertised object %s". However, it is sometimes the case that such
object is advertised. This situation would occur, for example, if a user
or a script was provided a SHA-1 instead of a branch or tag name for
fetching, and wanted to invoke "git fetch" or "git fetch-pack" using
that SHA-1.

Teach fetch-pack to also check the SHA-1s of the refs in the received
ref advertisement if a literal SHA-1 was given by the user.

Helped-by: Jeff King <p...@peff.net>
Signed-off-by: Jonathan Tan <jonathanta...@google.com>
---

OK, one combined function (for lazy initialization and checking
containment in the oidset) with comment it is.

Change from v6: changed back to "tip_oids_contain", and included Peff's
comment.
---
 fetch-pack.c          | 40 ++++++++++++++++++++++++++++++++++++++--
 t/t5500-fetch-pack.sh | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/fetch-pack.c b/fetch-pack.c
index afb8b0502..703e7ec78 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -15,6 +15,7 @@
 #include "version.h"
 #include "prio-queue.h"
 #include "sha1-array.h"
+#include "oidset.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -592,13 +593,38 @@ static void mark_recent_complete_commits(struct 
fetch_pack_args *args,
        }
 }
 
+static void add_refs_to_oidset(struct oidset *oids, struct ref *refs)
+{
+       for (; refs; refs = refs->next)
+               oidset_insert(oids, &refs->old_oid);
+}
+
+static int tip_oids_contain(struct oidset *tip_oids,
+                           struct ref *unmatched, struct ref *newlist,
+                           const struct object_id *id)
+{
+       /*
+        * Note that this only looks at the ref lists the first time it's
+        * called. This works out in filter_refs() because even though it may
+        * add to "newlist" between calls, the additions will always be for
+        * oids that are already in the set.
+        */
+       if (!tip_oids->map.tablesize) {
+               add_refs_to_oidset(tip_oids, unmatched);
+               add_refs_to_oidset(tip_oids, newlist);
+       }
+       return oidset_contains(tip_oids, id);
+}
+
 static void filter_refs(struct fetch_pack_args *args,
                        struct ref **refs,
                        struct ref **sought, int nr_sought)
 {
        struct ref *newlist = NULL;
        struct ref **newtail = &newlist;
+       struct ref *unmatched = NULL;
        struct ref *ref, *next;
+       struct oidset tip_oids = OIDSET_INIT;
        int i;
 
        i = 0;
@@ -631,7 +657,8 @@ static void filter_refs(struct fetch_pack_args *args,
                        ref->next = NULL;
                        newtail = &ref->next;
                } else {
-                       free(ref);
+                       ref->next = unmatched;
+                       unmatched = ref;
                }
        }
 
@@ -648,7 +675,9 @@ static void filter_refs(struct fetch_pack_args *args,
                        continue;
 
                if ((allow_unadvertised_object_request &
-                   (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) {
+                    (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)) ||
+                   tip_oids_contain(&tip_oids, unmatched, newlist,
+                                    &ref->old_oid)) {
                        ref->match_status = REF_MATCHED;
                        *newtail = copy_ref(ref);
                        newtail = &(*newtail)->next;
@@ -656,6 +685,13 @@ static void filter_refs(struct fetch_pack_args *args,
                        ref->match_status = REF_UNADVERTISED_NOT_ALLOWED;
                }
        }
+
+       oidset_clear(&tip_oids);
+       for (ref = unmatched; ref; ref = next) {
+               next = ref->next;
+               free(ref);
+       }
+
        *refs = newlist;
 }
 
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index b5865b385..80a1a3239 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -547,6 +547,41 @@ test_expect_success 'fetch-pack can fetch a raw sha1' '
        git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one)
 '
 
+test_expect_success 'fetch-pack can fetch a raw sha1 that is advertised as a 
ref' '
+       rm -rf server client &&
+       git init server &&
+       test_commit -C server 1 &&
+
+       git init client &&
+       git -C client fetch-pack ../server \
+               $(git -C server rev-parse refs/heads/master)
+'
+
+test_expect_success 'fetch-pack can fetch a raw sha1 overlapping a named ref' '
+       rm -rf server client &&
+       git init server &&
+       test_commit -C server 1 &&
+       test_commit -C server 2 &&
+
+       git init client &&
+       git -C client fetch-pack ../server \
+               $(git -C server rev-parse refs/tags/1) refs/tags/1
+'
+
+test_expect_success 'fetch-pack cannot fetch a raw sha1 that is not advertised 
as a ref' '
+       rm -rf server &&
+
+       git init server &&
+       test_commit -C server 5 &&
+       git -C server tag -d 5 &&
+       test_commit -C server 6 &&
+
+       git init client &&
+       test_must_fail git -C client fetch-pack ../server \
+               $(git -C server rev-parse refs/heads/master^) 2>err &&
+       test_i18ngrep "Server does not allow request for unadvertised object" 
err
+'
+
 check_prot_path () {
        cat >expected <<-EOF &&
        Diag: url=$1
-- 
2.13.0.rc2.291.g57267f2277-goog

Reply via email to