Skip to content

Commit 6ed547b

Browse files
committed
Merge branch 'js/ref-namespaces'
* js/ref-namespaces: ref namespaces: tests ref namespaces: documentation ref namespaces: Support remote repositories via upload-pack and receive-pack ref namespaces: infrastructure Fix prefix handling in ref iteration functions
2 parents 6dd5622 + bf7930c commit 6ed547b

15 files changed

+326
-24
lines changed

Documentation/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
66
gitrepository-layout.txt
77
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
88
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
9-
gitdiffcore.txt gitrevisions.txt gitworkflows.txt
9+
gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
1010

1111
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
1212
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))

Documentation/git-http-backend.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ ScriptAliasMatch \
119119

120120
ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
121121
----------------------------------------------------------------
122+
+
123+
To serve multiple repositories from different linkgit:gitnamespaces[7] in a
124+
single repository:
125+
+
126+
----------------------------------------------------------------
127+
SetEnvIf Request_URI "^/git/([^/]*)" GIT_NAMESPACE=$1
128+
ScriptAliasMatch ^/git/[^/]*(.*) /usr/libexec/git-core/git-http-backend/storage.git$1
129+
----------------------------------------------------------------
122130

123131
Accelerated static Apache 2.x::
124132
Similar to the above, but Apache can be used to return static

Documentation/git-receive-pack.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ if the repository is packed and is served via a dumb transport.
153153

154154
SEE ALSO
155155
--------
156-
linkgit:git-send-pack[1]
156+
linkgit:git-send-pack[1], linkgit:gitnamespaces[7]
157157

158158
GIT
159159
---

Documentation/git-upload-pack.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ OPTIONS
3434
<directory>::
3535
The repository to sync from.
3636

37+
SEE ALSO
38+
--------
39+
linkgit:gitnamespaces[7]
40+
3741
GIT
3842
---
3943
Part of the linkgit:git[1] suite

Documentation/git.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
13-
[-p|--paginate|--no-pager] [--no-replace-objects]
14-
[--bare] [--git-dir=<path>] [--work-tree=<path>]
13+
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
14+
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
1515
[-c <name>=<value>]
1616
[--help] <command> [<args>]
1717

@@ -330,6 +330,11 @@ help ...`.
330330
variable (see core.worktree in linkgit:git-config[1] for a
331331
more detailed discussion).
332332

333+
--namespace=<path>::
334+
Set the git namespace. See linkgit:gitnamespaces[7] for more
335+
details. Equivalent to setting the `GIT_NAMESPACE` environment
336+
variable.
337+
333338
--bare::
334339
Treat the repository as a bare repository. If GIT_DIR
335340
environment is not set, it is set to the current working
@@ -593,6 +598,10 @@ git so take care if using Cogito etc.
593598
This can also be controlled by the '--work-tree' command line
594599
option and the core.worktree configuration variable.
595600

601+
'GIT_NAMESPACE'::
602+
Set the git namespace; see linkgit:gitnamespaces[7] for details.
603+
The '--namespace' command-line option also sets this value.
604+
596605
'GIT_CEILING_DIRECTORIES'::
597606
This should be a colon-separated list of absolute paths.
598607
If set, it is a list of directories that git should not chdir

Documentation/gitnamespaces.txt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
gitnamespaces(7)
2+
================
3+
4+
NAME
5+
----
6+
gitnamespaces - Git namespaces
7+
8+
DESCRIPTION
9+
-----------
10+
11+
Git supports dividing the refs of a single repository into multiple
12+
namespaces, each of which has its own branches, tags, and HEAD. Git can
13+
expose each namespace as an independent repository to pull from and push
14+
to, while sharing the object store, and exposing all the refs to
15+
operations such as linkgit:git-gc[1].
16+
17+
Storing multiple repositories as namespaces of a single repository
18+
avoids storing duplicate copies of the same objects, such as when
19+
storing multiple branches of the same source. The alternates mechanism
20+
provides similar support for avoiding duplicates, but alternates do not
21+
prevent duplication between new objects added to the repositories
22+
without ongoing maintenance, while namespaces do.
23+
24+
To specify a namespace, set the `GIT_NAMESPACE` environment variable to
25+
the namespace. For each ref namespace, git stores the corresponding
26+
refs in a directory under `refs/namespaces/`. For example,
27+
`GIT_NAMESPACE=foo` will store refs under `refs/namespaces/foo/`. You
28+
can also specify namespaces via the `--namespace` option to
29+
linkgit:git[1].
30+
31+
Note that namespaces which include a `/` will expand to a hierarchy of
32+
namespaces; for example, `GIT_NAMESPACE=foo/bar` will store refs under
33+
`refs/namespaces/foo/refs/namespaces/bar/`. This makes paths in
34+
`GIT_NAMESPACE` behave hierarchically, so that cloning with
35+
`GIT_NAMESPACE=foo/bar` produces the same result as cloning with
36+
`GIT_NAMESPACE=foo` and cloning from that repo with `GIT_NAMESPACE=bar`. It
37+
also avoids ambiguity with strange namespace paths such as `foo/refs/heads/`,
38+
which could otherwise generate directory/file conflicts within the `refs`
39+
directory.
40+
41+
linkgit:git-upload-pack[1] and linkgit:git-receive-pack[1] rewrite the
42+
names of refs as specified by `GIT_NAMESPACE`. git-upload-pack and
43+
git-receive-pack will ignore all references outside the specified
44+
namespace.
45+
46+
The smart HTTP server, linkgit:git-http-backend[1], will pass
47+
GIT_NAMESPACE through to the backend programs; see
48+
linkgit:git-http-backend[1] for sample configuration to expose
49+
repository namespaces as repositories.
50+
51+
For a simple local test, you can use linkgit:git-remote-ext[1]:
52+
53+
----------
54+
git clone ext::'git --namespace=foo %s /tmp/prefixed.git'
55+
----------
56+
57+
SECURITY
58+
--------
59+
60+
Anyone with access to any namespace within a repository can potentially
61+
access objects from any other namespace stored in the same repository.
62+
You can't directly say "give me object ABCD" if you don't have a ref to
63+
it, but you can do some other sneaky things like:
64+
65+
. Claiming to push ABCD, at which point the server will optimize out the
66+
need for you to actually send it. Now you have a ref to ABCD and can
67+
fetch it (claiming not to have it, of course).
68+
69+
. Requesting other refs, claiming that you have ABCD, at which point the
70+
server may generate deltas against ABCD.
71+
72+
None of this causes a problem if you only host public repositories, or
73+
if everyone who may read one namespace may also read everything in every
74+
other namespace (for instance, if everyone in an organization has read
75+
permission to every repository).

builtin/receive-pack.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,25 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
120120
return 0;
121121
}
122122

123+
static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
124+
{
125+
path = strip_namespace(path);
126+
/*
127+
* Advertise refs outside our current namespace as ".have"
128+
* refs, so that the client can use them to minimize data
129+
* transfer but will otherwise ignore them. This happens to
130+
* cover ".have" that are thrown in by add_one_alternate_ref()
131+
* to mark histories that are complete in our alternates as
132+
* well.
133+
*/
134+
if (!path)
135+
path = ".have";
136+
return show_ref(path, sha1, flag, cb_data);
137+
}
138+
123139
static void write_head_info(void)
124140
{
125-
for_each_ref(show_ref, NULL);
141+
for_each_ref(show_ref_cb, NULL);
126142
if (!sent_capabilities)
127143
show_ref("capabilities^{}", null_sha1, 0, NULL);
128144

@@ -333,6 +349,8 @@ static void refuse_unconfigured_deny_delete_current(void)
333349
static const char *update(struct command *cmd)
334350
{
335351
const char *name = cmd->ref_name;
352+
struct strbuf namespaced_name_buf = STRBUF_INIT;
353+
const char *namespaced_name;
336354
unsigned char *old_sha1 = cmd->old_sha1;
337355
unsigned char *new_sha1 = cmd->new_sha1;
338356
struct ref_lock *lock;
@@ -343,7 +361,10 @@ static const char *update(struct command *cmd)
343361
return "funny refname";
344362
}
345363

346-
if (is_ref_checked_out(name)) {
364+
strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
365+
namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
366+
367+
if (is_ref_checked_out(namespaced_name)) {
347368
switch (deny_current_branch) {
348369
case DENY_IGNORE:
349370
break;
@@ -371,7 +392,7 @@ static const char *update(struct command *cmd)
371392
return "deletion prohibited";
372393
}
373394

374-
if (!strcmp(name, head_name)) {
395+
if (!strcmp(namespaced_name, head_name)) {
375396
switch (deny_delete_current) {
376397
case DENY_IGNORE:
377398
break;
@@ -427,14 +448,14 @@ static const char *update(struct command *cmd)
427448
rp_warning("Allowing deletion of corrupt ref.");
428449
old_sha1 = NULL;
429450
}
430-
if (delete_ref(name, old_sha1, 0)) {
451+
if (delete_ref(namespaced_name, old_sha1, 0)) {
431452
rp_error("failed to delete %s", name);
432453
return "failed to delete";
433454
}
434455
return NULL; /* good */
435456
}
436457
else {
437-
lock = lock_any_ref_for_update(name, old_sha1, 0);
458+
lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
438459
if (!lock) {
439460
rp_error("failed to lock %s", name);
440461
return "failed to lock";
@@ -491,17 +512,29 @@ static void run_update_post_hook(struct command *commands)
491512

492513
static void check_aliased_update(struct command *cmd, struct string_list *list)
493514
{
515+
struct strbuf buf = STRBUF_INIT;
516+
const char *dst_name;
494517
struct string_list_item *item;
495518
struct command *dst_cmd;
496519
unsigned char sha1[20];
497520
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
498521
int flag;
499522

500-
const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag);
523+
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
524+
dst_name = resolve_ref(buf.buf, sha1, 0, &flag);
525+
strbuf_release(&buf);
501526

502527
if (!(flag & REF_ISSYMREF))
503528
return;
504529

530+
dst_name = strip_namespace(dst_name);
531+
if (!dst_name) {
532+
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
533+
cmd->skip_update = 1;
534+
cmd->error_string = "broken symref";
535+
return;
536+
}
537+
505538
if ((item = string_list_lookup(list, dst_name)) == NULL)
506539
return;
507540

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ static inline enum object_type object_type(unsigned int mode)
394394
}
395395

396396
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
397+
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
397398
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
398399
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
399400
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
@@ -434,6 +435,8 @@ extern char *get_object_directory(void);
434435
extern char *get_index_file(void);
435436
extern char *get_graft_file(void);
436437
extern int set_git_dir(const char *path);
438+
extern const char *get_git_namespace(void);
439+
extern const char *strip_namespace(const char *namespaced_ref);
437440
extern const char *get_git_work_tree(void);
438441
extern const char *read_gitfile_gently(const char *path);
439442
extern void set_git_work_tree(const char *tree);

contrib/completion/git-completion.bash

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,7 @@ _git_help ()
14691469
__gitcomp "$__git_all_commands $(__git_aliases)
14701470
attributes cli core-tutorial cvs-migration
14711471
diffcore gitk glossary hooks ignore modules
1472-
repository-layout tutorial tutorial-2
1472+
namespaces repository-layout tutorial tutorial-2
14731473
workflows
14741474
"
14751475
}
@@ -2640,6 +2640,7 @@ _git ()
26402640
--exec-path
26412641
--html-path
26422642
--work-tree=
2643+
--namespace=
26432644
--help
26442645
"
26452646
;;

environment.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* are.
99
*/
1010
#include "cache.h"
11+
#include "refs.h"
1112

1213
char git_default_email[MAX_GITNAME];
1314
char git_default_name[MAX_GITNAME];
@@ -66,6 +67,9 @@ int core_preload_index = 0;
6667
char *git_work_tree_cfg;
6768
static char *work_tree;
6869

70+
static const char *namespace;
71+
static size_t namespace_len;
72+
6973
static const char *git_dir;
7074
static char *git_object_dir, *git_index_file, *git_graft_file;
7175

@@ -87,6 +91,27 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
8791
NULL
8892
};
8993

94+
static char *expand_namespace(const char *raw_namespace)
95+
{
96+
struct strbuf buf = STRBUF_INIT;
97+
struct strbuf **components, **c;
98+
99+
if (!raw_namespace || !*raw_namespace)
100+
return xstrdup("");
101+
102+
strbuf_addstr(&buf, raw_namespace);
103+
components = strbuf_split(&buf, '/');
104+
strbuf_reset(&buf);
105+
for (c = components; *c; c++)
106+
if (strcmp((*c)->buf, "/") != 0)
107+
strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
108+
strbuf_list_free(components);
109+
if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
110+
die("bad git namespace path \"%s\"", raw_namespace);
111+
strbuf_addch(&buf, '/');
112+
return strbuf_detach(&buf, NULL);
113+
}
114+
90115
static void setup_git_env(void)
91116
{
92117
git_dir = getenv(GIT_DIR_ENVIRONMENT);
@@ -112,6 +137,8 @@ static void setup_git_env(void)
112137
git_graft_file = git_pathdup("info/grafts");
113138
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
114139
read_replace_refs = 0;
140+
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
141+
namespace_len = strlen(namespace);
115142
}
116143

117144
int is_bare_repository(void)
@@ -132,6 +159,20 @@ const char *get_git_dir(void)
132159
return git_dir;
133160
}
134161

162+
const char *get_git_namespace(void)
163+
{
164+
if (!namespace)
165+
setup_git_env();
166+
return namespace;
167+
}
168+
169+
const char *strip_namespace(const char *namespaced_ref)
170+
{
171+
if (prefixcmp(namespaced_ref, get_git_namespace()) != 0)
172+
return NULL;
173+
return namespaced_ref + namespace_len;
174+
}
175+
135176
static int git_work_tree_initialized;
136177

137178
/*

0 commit comments

Comments
 (0)