diff options
author | Junio C Hamano <gitster@pobox.com> | 2013-01-18 16:08:30 -0800 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2013-02-07 13:48:47 -0800 |
commit | daebaa78137d59693a808c1f0bdec0ecb40fc12e (patch) | |
tree | 46a71938843dae2dade7b121a5d7c5eb7d6a65a9 | |
parent | 3f1da57fffdcfb48bd8b85da347f310b955245d7 (diff) | |
download | git-daebaa78137d59693a808c1f0bdec0ecb40fc12e.tar.gz |
upload/receive-pack: allow hiding ref hierarchies
A repository may have refs that are only used for its internal
bookkeeping purposes that should not be exposed to the others that
come over the network.
Teach upload-pack to omit some refs from its initial advertisement
by paying attention to the uploadpack.hiderefs multi-valued
configuration variable. Do the same to receive-pack via the
receive.hiderefs variable. As a convenient short-hand, allow using
transfer.hiderefs to set the value to both of these variables.
Any ref that is under the hierarchies listed on the value of these
variable is excluded from responses to requests made by "ls-remote",
"fetch", etc. (for upload-pack) and "push" (for receive-pack).
Because these hidden refs do not count as OUR_REF, an attempt to
fetch objects at the tip of them will be rejected, and because these
refs do not get advertised, "git push :" will not see local branches
that have the same name as them as "matching" ones to be sent.
An attempt to update/delete these hidden refs with an explicit
refspec, e.g. "git push origin :refs/hidden/22", is rejected. This
is not a new restriction. To the pusher, it would appear that there
is no such ref, so its push request will conclude with "Now that I
sent you all the data, it is time for you to update the refs. I saw
that the ref did not exist when I started pushing, and I want the
result to point at this commit". The receiving end will apply the
compare-and-swap rule to this request and rejects the push with
"Well, your update request conflicts with somebody else; I see there
is such a ref.", which is the right thing to do. Otherwise a push to
a hidden ref will always be "the last one wins", which is not a good
default.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
-rw-r--r-- | Documentation/config.txt | 23 | ||||
-rw-r--r-- | builtin/receive-pack.c | 24 | ||||
-rw-r--r-- | refs.c | 44 | ||||
-rw-r--r-- | refs.h | 3 | ||||
-rwxr-xr-x | t/t5512-ls-remote.sh | 12 | ||||
-rwxr-xr-x | t/t5516-fetch-push.sh | 27 | ||||
-rw-r--r-- | upload-pack.c | 14 |
7 files changed, 146 insertions, 1 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index ef45c99e53..10be9ed90e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1845,6 +1845,15 @@ receive.denyNonFastForwards:: even if that push is forced. This configuration variable is set when initializing a shared repository. +receive.hiderefs:: + String(s) `receive-pack` uses to decide which refs to omit + from its initial advertisement. Use more than one + definitions to specify multiple prefix strings. A ref that + are under the hierarchies listed on the value of this + variable is excluded, and is hidden when responding to `git + push`, and an attempt to update or delete a hidden ref by + `git push` is rejected. + receive.updateserverinfo:: If set to true, git-receive-pack will run git-update-server-info after receiving data from git-push and updating refs. @@ -2057,11 +2066,25 @@ transfer.fsckObjects:: not set, the value of this variable is used instead. Defaults to false. +transfer.hiderefs:: + This variable can be used to set both `receive.hiderefs` + and `uploadpack.hiderefs` at the same time to the same + values. See entries for these other variables. + transfer.unpackLimit:: When `fetch.unpackLimit` or `receive.unpackLimit` are not set, the value of this variable is used instead. The default value is 100. +uploadpack.hiderefs:: + String(s) `upload-pack` uses to decide which refs to omit + from its initial advertisement. Use more than one + definitions to specify multiple prefix strings. A ref that + are under the hierarchies listed on the value of this + variable is excluded, and is hidden from `git ls-remote`, + `git fetch`, etc. An attempt to fetch a hidden ref by `git + fetch` will fail. + url.<base>.insteadOf:: Any URL that starts with this value will be rewritten to start, instead, with <base>. In cases where some site serves a diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ff781febca..131c1635cb 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -59,6 +59,11 @@ static enum deny_action parse_deny_action(const char *var, const char *value) static int receive_pack_config(const char *var, const char *value, void *cb) { + int status = parse_hide_refs_config(var, value, "receive"); + + if (status) + return status; + if (strcmp(var, "receive.denydeletes") == 0) { deny_deletes = git_config_bool(var, value); return 0; @@ -119,6 +124,9 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static void show_ref(const char *path, const unsigned char *sha1) { + if (ref_is_hidden(path)) + return; + if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else @@ -688,6 +696,20 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) return -1; /* end of list */ } +static void reject_updates_to_hidden(struct command *commands) +{ + struct command *cmd; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string || !ref_is_hidden(cmd->ref_name)) + continue; + if (is_null_sha1(cmd->new_sha1)) + cmd->error_string = "deny deleting a hidden ref"; + else + cmd->error_string = "deny updating a hidden ref"; + } +} + static void execute_commands(struct command *commands, const char *unpacker_error) { struct command *cmd; @@ -704,6 +726,8 @@ static void execute_commands(struct command *commands, const char *unpacker_erro 0, &cmd)) set_connectivity_errors(commands); + reject_updates_to_hidden(commands); + if (run_receive_hook(commands, pre_receive_hook, 0)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -3,6 +3,7 @@ #include "object.h" #include "tag.h" #include "dir.h" +#include "string-list.h" /* * Make sure "ref" is something reasonable to have under ".git/refs/"; @@ -2556,3 +2557,46 @@ char *shorten_unambiguous_ref(const char *refname, int strict) free(short_name); return xstrdup(refname); } + +static struct string_list *hide_refs; + +int parse_hide_refs_config(const char *var, const char *value, const char *section) +{ + if (!strcmp("transfer.hiderefs", var) || + /* NEEDSWORK: use parse_config_key() once both are merged */ + (!prefixcmp(var, section) && var[strlen(section)] == '.' && + !strcmp(var + strlen(section), ".hiderefs"))) { + char *ref; + int len; + + if (!value) + return config_error_nonbool(var); + ref = xstrdup(value); + len = strlen(ref); + while (len && ref[len - 1] == '/') + ref[--len] = '\0'; + if (!hide_refs) { + hide_refs = xcalloc(1, sizeof(*hide_refs)); + hide_refs->strdup_strings = 1; + } + string_list_append(hide_refs, ref); + } + return 0; +} + +int ref_is_hidden(const char *refname) +{ + struct string_list_item *item; + + if (!hide_refs) + return 0; + for_each_string_list_item(item, hide_refs) { + int len; + if (prefixcmp(refname, item->string)) + continue; + len = strlen(item->string); + if (!refname[len] || refname[len] == '/') + return 1; + } + return 0; +} @@ -147,4 +147,7 @@ int update_ref(const char *action, const char *refname, const unsigned char *sha1, const unsigned char *oldval, int flags, enum action_on_err onerr); +extern int parse_hide_refs_config(const char *var, const char *value, const char *); +extern int ref_is_hidden(const char *); + #endif /* REFS_H */ diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index d16e5d384a..321c3e5234 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -126,4 +126,16 @@ test_expect_success 'Report match with --exit-code' ' test_cmp expect actual ' +for configsection in transfer uploadpack +do + test_expect_success "Hide some refs with $configsection.hiderefs" ' + test_config $configsection.hiderefs refs/tags && + git ls-remote . >actual && + test_unconfig $configsection.hiderefs && + git ls-remote . | + sed -e "/ refs\/tags\//d" >expect && + test_cmp expect actual + ' +done + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 60093728fe..2b26ecebf7 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1037,4 +1037,31 @@ test_expect_success 'push --prune refspec' ' ! check_push_result $the_first_commit tmp/foo tmp/bar ' +for configsection in transfer receive +do + test_expect_success "push to update a ref hidden by $configsection.hiderefs" ' + mk_test heads/master hidden/one hidden/two hidden/three && + ( + cd testrepo && + git config $configsection.hiderefs refs/hidden + ) && + + # push to unhidden ref succeeds normally + git push testrepo master:refs/heads/master && + check_push_result $the_commit heads/master && + + # push to update a hidden ref should fail + test_must_fail git push testrepo master:refs/hidden/one && + check_push_result $the_first_commit hidden/one && + + # push to delete a hidden ref should fail + test_must_fail git push testrepo :refs/hidden/two && + check_push_result $the_first_commit hidden/two && + + # idempotent push to update a hidden ref should fail + test_must_fail git push testrepo $the_first_commit:refs/hidden/three && + check_push_result $the_first_commit hidden/three + ' +done + test_done diff --git a/upload-pack.c b/upload-pack.c index 3a26a7bc0d..dec0237793 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -12,6 +12,7 @@ #include "run-command.h" #include "sigchain.h" #include "version.h" +#include "string-list.h" static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>"; @@ -719,9 +720,13 @@ static void receive_needs(void) free(shallows.objects); } +/* return non-zero if the ref is hidden, otherwise 0 */ static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = lookup_unknown_object(sha1); + + if (ref_is_hidden(refname)) + return 1; if (!o) die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); o->flags |= OUR_REF; @@ -736,7 +741,8 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo const char *refname_nons = strip_namespace(refname); unsigned char peeled[20]; - mark_our_ref(refname, sha1, flag, cb_data); + if (mark_our_ref(refname, sha1, flag, cb_data)) + return 0; if (capabilities) packet_write(1, "%s %s%c%s%s agent=%s\n", @@ -773,6 +779,11 @@ static void upload_pack(void) } } +static int upload_pack_config(const char *var, const char *value, void *unused) +{ + return parse_hide_refs_config(var, value, "uploadpack"); +} + int main(int argc, char **argv) { char *dir; @@ -824,6 +835,7 @@ int main(int argc, char **argv) die("'%s' does not appear to be a git repository", dir); if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); + git_config(upload_pack_config, NULL); if (getenv("GIT_DEBUG_SEND_PACK")) debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); upload_pack(); |