Skip to content

Commit

Permalink
Merge branch 'js/ref-namespaces'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gitster committed Aug 18, 2011
2 parents 6dd5622 + bf7930c commit 6ed547b
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Documentation/Makefile
Expand Up @@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
gitrepository-layout.txt
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
gitdiffcore.txt gitrevisions.txt gitworkflows.txt
gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt

MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
Expand Down
8 changes: 8 additions & 0 deletions Documentation/git-http-backend.txt
Expand Up @@ -119,6 +119,14 @@ ScriptAliasMatch \

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

Accelerated static Apache 2.x::
Similar to the above, but Apache can be used to return static
Expand Down
2 changes: 1 addition & 1 deletion Documentation/git-receive-pack.txt
Expand Up @@ -153,7 +153,7 @@ if the repository is packed and is served via a dumb transport.

SEE ALSO
--------
linkgit:git-send-pack[1]
linkgit:git-send-pack[1], linkgit:gitnamespaces[7]

GIT
---
Expand Down
4 changes: 4 additions & 0 deletions Documentation/git-upload-pack.txt
Expand Up @@ -34,6 +34,10 @@ OPTIONS
<directory>::
The repository to sync from.

SEE ALSO
--------
linkgit:gitnamespaces[7]

GIT
---
Part of the linkgit:git[1] suite
13 changes: 11 additions & 2 deletions Documentation/git.txt
Expand Up @@ -10,8 +10,8 @@ SYNOPSIS
--------
[verse]
'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects]
[--bare] [--git-dir=<path>] [--work-tree=<path>]
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
[-c <name>=<value>]
[--help] <command> [<args>]

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

--namespace=<path>::
Set the git namespace. See linkgit:gitnamespaces[7] for more
details. Equivalent to setting the `GIT_NAMESPACE` environment
variable.

--bare::
Treat the repository as a bare repository. If GIT_DIR
environment is not set, it is set to the current working
Expand Down Expand Up @@ -593,6 +598,10 @@ git so take care if using Cogito etc.
This can also be controlled by the '--work-tree' command line
option and the core.worktree configuration variable.

'GIT_NAMESPACE'::
Set the git namespace; see linkgit:gitnamespaces[7] for details.
The '--namespace' command-line option also sets this value.

'GIT_CEILING_DIRECTORIES'::
This should be a colon-separated list of absolute paths.
If set, it is a list of directories that git should not chdir
Expand Down
75 changes: 75 additions & 0 deletions Documentation/gitnamespaces.txt
@@ -0,0 +1,75 @@
gitnamespaces(7)
================

NAME
----
gitnamespaces - Git namespaces

DESCRIPTION
-----------

Git supports dividing the refs of a single repository into multiple
namespaces, each of which has its own branches, tags, and HEAD. Git can
expose each namespace as an independent repository to pull from and push
to, while sharing the object store, and exposing all the refs to
operations such as linkgit:git-gc[1].

Storing multiple repositories as namespaces of a single repository
avoids storing duplicate copies of the same objects, such as when
storing multiple branches of the same source. The alternates mechanism
provides similar support for avoiding duplicates, but alternates do not
prevent duplication between new objects added to the repositories
without ongoing maintenance, while namespaces do.

To specify a namespace, set the `GIT_NAMESPACE` environment variable to
the namespace. For each ref namespace, git stores the corresponding
refs in a directory under `refs/namespaces/`. For example,
`GIT_NAMESPACE=foo` will store refs under `refs/namespaces/foo/`. You
can also specify namespaces via the `--namespace` option to
linkgit:git[1].

Note that namespaces which include a `/` will expand to a hierarchy of
namespaces; for example, `GIT_NAMESPACE=foo/bar` will store refs under
`refs/namespaces/foo/refs/namespaces/bar/`. This makes paths in
`GIT_NAMESPACE` behave hierarchically, so that cloning with
`GIT_NAMESPACE=foo/bar` produces the same result as cloning with
`GIT_NAMESPACE=foo` and cloning from that repo with `GIT_NAMESPACE=bar`. It
also avoids ambiguity with strange namespace paths such as `foo/refs/heads/`,
which could otherwise generate directory/file conflicts within the `refs`
directory.

linkgit:git-upload-pack[1] and linkgit:git-receive-pack[1] rewrite the
names of refs as specified by `GIT_NAMESPACE`. git-upload-pack and
git-receive-pack will ignore all references outside the specified
namespace.

The smart HTTP server, linkgit:git-http-backend[1], will pass
GIT_NAMESPACE through to the backend programs; see
linkgit:git-http-backend[1] for sample configuration to expose
repository namespaces as repositories.

For a simple local test, you can use linkgit:git-remote-ext[1]:

----------
git clone ext::'git --namespace=foo %s /tmp/prefixed.git'
----------

SECURITY
--------

Anyone with access to any namespace within a repository can potentially
access objects from any other namespace stored in the same repository.
You can't directly say "give me object ABCD" if you don't have a ref to
it, but you can do some other sneaky things like:

. Claiming to push ABCD, at which point the server will optimize out the
need for you to actually send it. Now you have a ref to ABCD and can
fetch it (claiming not to have it, of course).

. Requesting other refs, claiming that you have ABCD, at which point the
server may generate deltas against ABCD.

None of this causes a problem if you only host public repositories, or
if everyone who may read one namespace may also read everything in every
other namespace (for instance, if everyone in an organization has read
permission to every repository).
45 changes: 39 additions & 6 deletions builtin/receive-pack.c
Expand Up @@ -120,9 +120,25 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
return 0;
}

static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
path = strip_namespace(path);
/*
* Advertise refs outside our current namespace as ".have"
* refs, so that the client can use them to minimize data
* transfer but will otherwise ignore them. This happens to
* cover ".have" that are thrown in by add_one_alternate_ref()
* to mark histories that are complete in our alternates as
* well.
*/
if (!path)
path = ".have";
return show_ref(path, sha1, flag, cb_data);
}

static void write_head_info(void)
{
for_each_ref(show_ref, NULL);
for_each_ref(show_ref_cb, NULL);
if (!sent_capabilities)
show_ref("capabilities^{}", null_sha1, 0, NULL);

Expand Down Expand Up @@ -333,6 +349,8 @@ static void refuse_unconfigured_deny_delete_current(void)
static const char *update(struct command *cmd)
{
const char *name = cmd->ref_name;
struct strbuf namespaced_name_buf = STRBUF_INIT;
const char *namespaced_name;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
struct ref_lock *lock;
Expand All @@ -343,7 +361,10 @@ static const char *update(struct command *cmd)
return "funny refname";
}

if (is_ref_checked_out(name)) {
strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);

if (is_ref_checked_out(namespaced_name)) {
switch (deny_current_branch) {
case DENY_IGNORE:
break;
Expand Down Expand Up @@ -371,7 +392,7 @@ static const char *update(struct command *cmd)
return "deletion prohibited";
}

if (!strcmp(name, head_name)) {
if (!strcmp(namespaced_name, head_name)) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
Expand Down Expand Up @@ -427,14 +448,14 @@ static const char *update(struct command *cmd)
rp_warning("Allowing deletion of corrupt ref.");
old_sha1 = NULL;
}
if (delete_ref(name, old_sha1, 0)) {
if (delete_ref(namespaced_name, old_sha1, 0)) {
rp_error("failed to delete %s", name);
return "failed to delete";
}
return NULL; /* good */
}
else {
lock = lock_any_ref_for_update(name, old_sha1, 0);
lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
if (!lock) {
rp_error("failed to lock %s", name);
return "failed to lock";
Expand Down Expand Up @@ -491,17 +512,29 @@ static void run_update_post_hook(struct command *commands)

static void check_aliased_update(struct command *cmd, struct string_list *list)
{
struct strbuf buf = STRBUF_INIT;
const char *dst_name;
struct string_list_item *item;
struct command *dst_cmd;
unsigned char sha1[20];
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
int flag;

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

if (!(flag & REF_ISSYMREF))
return;

dst_name = strip_namespace(dst_name);
if (!dst_name) {
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
cmd->skip_update = 1;
cmd->error_string = "broken symref";
return;
}

if ((item = string_list_lookup(list, dst_name)) == NULL)
return;

Expand Down
3 changes: 3 additions & 0 deletions cache.h
Expand Up @@ -394,6 +394,7 @@ static inline enum object_type object_type(unsigned int mode)
}

#define GIT_DIR_ENVIRONMENT "GIT_DIR"
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
Expand Down Expand Up @@ -434,6 +435,8 @@ extern char *get_object_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
extern int set_git_dir(const char *path);
extern const char *get_git_namespace(void);
extern const char *strip_namespace(const char *namespaced_ref);
extern const char *get_git_work_tree(void);
extern const char *read_gitfile_gently(const char *path);
extern void set_git_work_tree(const char *tree);
Expand Down
3 changes: 2 additions & 1 deletion contrib/completion/git-completion.bash
Expand Up @@ -1469,7 +1469,7 @@ _git_help ()
__gitcomp "$__git_all_commands $(__git_aliases)
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
repository-layout tutorial tutorial-2
namespaces repository-layout tutorial tutorial-2
workflows
"
}
Expand Down Expand Up @@ -2640,6 +2640,7 @@ _git ()
--exec-path
--html-path
--work-tree=
--namespace=
--help
"
;;
Expand Down
41 changes: 41 additions & 0 deletions environment.c
Expand Up @@ -8,6 +8,7 @@
* are.
*/
#include "cache.h"
#include "refs.h"

char git_default_email[MAX_GITNAME];
char git_default_name[MAX_GITNAME];
Expand Down Expand Up @@ -66,6 +67,9 @@ int core_preload_index = 0;
char *git_work_tree_cfg;
static char *work_tree;

static const char *namespace;
static size_t namespace_len;

static const char *git_dir;
static char *git_object_dir, *git_index_file, *git_graft_file;

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

static char *expand_namespace(const char *raw_namespace)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf **components, **c;

if (!raw_namespace || !*raw_namespace)
return xstrdup("");

strbuf_addstr(&buf, raw_namespace);
components = strbuf_split(&buf, '/');
strbuf_reset(&buf);
for (c = components; *c; c++)
if (strcmp((*c)->buf, "/") != 0)
strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
strbuf_list_free(components);
if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
die("bad git namespace path \"%s\"", raw_namespace);
strbuf_addch(&buf, '/');
return strbuf_detach(&buf, NULL);
}

static void setup_git_env(void)
{
git_dir = getenv(GIT_DIR_ENVIRONMENT);
Expand All @@ -112,6 +137,8 @@ static void setup_git_env(void)
git_graft_file = git_pathdup("info/grafts");
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
read_replace_refs = 0;
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
namespace_len = strlen(namespace);
}

int is_bare_repository(void)
Expand All @@ -132,6 +159,20 @@ const char *get_git_dir(void)
return git_dir;
}

const char *get_git_namespace(void)
{
if (!namespace)
setup_git_env();
return namespace;
}

const char *strip_namespace(const char *namespaced_ref)
{
if (prefixcmp(namespaced_ref, get_git_namespace()) != 0)
return NULL;
return namespaced_ref + namespace_len;
}

static int git_work_tree_initialized;

/*
Expand Down

0 comments on commit 6ed547b

Please sign in to comment.