Merge branch 'jc/hidden-refs'

Allow the server side to redact the refs/ namespace it shows to the
client.

Will merge to 'master'.

* jc/hidden-refs:
  upload/receive-pack: allow hiding ref hierarchies
  upload-pack: simplify request validation
  upload-pack: share more code
This commit is contained in:
Junio C Hamano 2013-02-17 15:25:57 -08:00
commit ce735bf7fd
7 changed files with 166 additions and 30 deletions

View File

@ -1880,6 +1880,15 @@ receive.denyNonFastForwards::
even if that push is forced. This configuration variable is even if that push is forced. This configuration variable is
set when initializing a shared repository. 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:: receive.updateserverinfo::
If set to true, git-receive-pack will run git-update-server-info If set to true, git-receive-pack will run git-update-server-info
after receiving data from git-push and updating refs. after receiving data from git-push and updating refs.
@ -2092,11 +2101,25 @@ transfer.fsckObjects::
not set, the value of this variable is used instead. not set, the value of this variable is used instead.
Defaults to false. 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:: transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead. not set, the value of this variable is used instead.
The default value is 100. 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:: url.<base>.insteadOf::
Any URL that starts with this value will be rewritten to Any URL that starts with this value will be rewritten to
start, instead, with <base>. In cases where some site serves a start, instead, with <base>. In cases where some site serves a

View File

@ -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) 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) { if (strcmp(var, "receive.denydeletes") == 0) {
deny_deletes = git_config_bool(var, value); deny_deletes = git_config_bool(var, value);
return 0; 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) static void show_ref(const char *path, const unsigned char *sha1)
{ {
if (ref_is_hidden(path))
return;
if (sent_capabilities) if (sent_capabilities)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
else else
@ -685,6 +693,20 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
return -1; /* end of list */ 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) static void execute_commands(struct command *commands, const char *unpacker_error)
{ {
struct command *cmd; struct command *cmd;
@ -701,6 +723,8 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
0, &cmd)) 0, &cmd))
set_connectivity_errors(commands); set_connectivity_errors(commands);
reject_updates_to_hidden(commands);
if (run_receive_hook(commands, "pre-receive", 0)) { if (run_receive_hook(commands, "pre-receive", 0)) {
for (cmd = commands; cmd; cmd = cmd->next) { for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string) if (!cmd->error_string)

44
refs.c
View File

@ -3,6 +3,7 @@
#include "object.h" #include "object.h"
#include "tag.h" #include "tag.h"
#include "dir.h" #include "dir.h"
#include "string-list.h"
/* /*
* Make sure "ref" is something reasonable to have under ".git/refs/"; * Make sure "ref" is something reasonable to have under ".git/refs/";
@ -2554,3 +2555,46 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
free(short_name); free(short_name);
return xstrdup(refname); 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;
}

3
refs.h
View File

@ -147,4 +147,7 @@ int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval, const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr); 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 */ #endif /* REFS_H */

View File

@ -126,4 +126,16 @@ test_expect_success 'Report match with --exit-code' '
test_cmp expect actual 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 test_done

View File

@ -1016,4 +1016,31 @@ test_expect_success 'push --prune refspec' '
! check_push_result $the_first_commit tmp/foo tmp/bar ! 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 test_done

View File

@ -12,6 +12,7 @@
#include "run-command.h" #include "run-command.h"
#include "sigchain.h" #include "sigchain.h"
#include "version.h" #include "version.h"
#include "string-list.h"
static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>"; static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
@ -28,7 +29,7 @@ static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<
static unsigned long oldest_have; static unsigned long oldest_have;
static int multi_ack, nr_our_refs; static int multi_ack;
static int no_done; static int no_done;
static int use_thin_pack, use_ofs_delta, use_include_tag; static int use_thin_pack, use_ofs_delta, use_include_tag;
static int no_progress, daemon_mode; static int no_progress, daemon_mode;
@ -139,7 +140,6 @@ static void create_pack_file(void)
{ {
struct async rev_list; struct async rev_list;
struct child_process pack_objects; struct child_process pack_objects;
int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr);
char data[8193], progress[128]; char data[8193], progress[128];
char abort_msg[] = "aborting due to possible repository " char abort_msg[] = "aborting due to possible repository "
"corruption on the remote side."; "corruption on the remote side.";
@ -151,9 +151,7 @@ static void create_pack_file(void)
argv[arg++] = "pack-objects"; argv[arg++] = "pack-objects";
if (!shallow_nr) { if (!shallow_nr) {
argv[arg++] = "--revs"; argv[arg++] = "--revs";
if (create_full_pack) if (use_thin_pack)
argv[arg++] = "--all";
else if (use_thin_pack)
argv[arg++] = "--thin"; argv[arg++] = "--thin";
} }
@ -185,15 +183,15 @@ static void create_pack_file(void)
} }
else { else {
FILE *pipe_fd = xfdopen(pack_objects.in, "w"); FILE *pipe_fd = xfdopen(pack_objects.in, "w");
if (!create_full_pack) { int i;
int i;
for (i = 0; i < want_obj.nr; i++)
fprintf(pipe_fd, "%s\n", sha1_to_hex(want_obj.objects[i].item->sha1));
fprintf(pipe_fd, "--not\n");
for (i = 0; i < have_obj.nr; i++)
fprintf(pipe_fd, "%s\n", sha1_to_hex(have_obj.objects[i].item->sha1));
}
for (i = 0; i < want_obj.nr; i++)
fprintf(pipe_fd, "%s\n",
sha1_to_hex(want_obj.objects[i].item->sha1));
fprintf(pipe_fd, "--not\n");
for (i = 0; i < have_obj.nr; i++)
fprintf(pipe_fd, "%s\n",
sha1_to_hex(have_obj.objects[i].item->sha1));
fprintf(pipe_fd, "\n"); fprintf(pipe_fd, "\n");
fflush(pipe_fd); fflush(pipe_fd);
fclose(pipe_fd); fclose(pipe_fd);
@ -729,15 +727,30 @@ static void receive_needs(void)
free(shallows.objects); 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;
return 0;
}
static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{ {
static const char *capabilities = "multi_ack thin-pack side-band" static const char *capabilities = "multi_ack thin-pack side-band"
" side-band-64k ofs-delta shallow no-progress" " side-band-64k ofs-delta shallow no-progress"
" include-tag multi_ack_detailed"; " include-tag multi_ack_detailed";
struct object *o = lookup_unknown_object(sha1);
const char *refname_nons = strip_namespace(refname); const char *refname_nons = strip_namespace(refname);
unsigned char peeled[20]; unsigned char peeled[20];
if (mark_our_ref(refname, sha1, flag, cb_data))
return 0;
if (capabilities) if (capabilities)
packet_write(1, "%s %s%c%s%s agent=%s\n", packet_write(1, "%s %s%c%s%s agent=%s\n",
sha1_to_hex(sha1), refname_nons, sha1_to_hex(sha1), refname_nons,
@ -747,27 +760,11 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
else else
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons); packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
capabilities = NULL; capabilities = NULL;
if (!(o->flags & OUR_REF)) {
o->flags |= OUR_REF;
nr_our_refs++;
}
if (!peel_ref(refname, peeled)) if (!peel_ref(refname, peeled))
packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons); packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
return 0; return 0;
} }
static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
if (!o)
die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
if (!(o->flags & OUR_REF)) {
o->flags |= OUR_REF;
nr_our_refs++;
}
return 0;
}
static void upload_pack(void) static void upload_pack(void)
{ {
if (advertise_refs || !stateless_rpc) { if (advertise_refs || !stateless_rpc) {
@ -789,6 +786,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) int main(int argc, char **argv)
{ {
char *dir; char *dir;
@ -840,6 +842,7 @@ int main(int argc, char **argv)
die("'%s' does not appear to be a git repository", dir); die("'%s' does not appear to be a git repository", dir);
if (is_repository_shallow()) if (is_repository_shallow())
die("attempt to fetch/clone from a shallow repository"); die("attempt to fetch/clone from a shallow repository");
git_config(upload_pack_config, NULL);
if (getenv("GIT_DEBUG_SEND_PACK")) if (getenv("GIT_DEBUG_SEND_PACK"))
debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK"));
upload_pack(); upload_pack();