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
This commit is contained in:
commit
6ed547b53b
@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
|
|||||||
gitrepository-layout.txt
|
gitrepository-layout.txt
|
||||||
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
|
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
|
||||||
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.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_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
|
||||||
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
|
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
|
||||||
|
@ -119,6 +119,14 @@ ScriptAliasMatch \
|
|||||||
|
|
||||||
ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
|
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::
|
Accelerated static Apache 2.x::
|
||||||
Similar to the above, but Apache can be used to return static
|
Similar to the above, but Apache can be used to return static
|
||||||
|
@ -153,7 +153,7 @@ if the repository is packed and is served via a dumb transport.
|
|||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
linkgit:git-send-pack[1]
|
linkgit:git-send-pack[1], linkgit:gitnamespaces[7]
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
---
|
---
|
||||||
|
@ -34,6 +34,10 @@ OPTIONS
|
|||||||
<directory>::
|
<directory>::
|
||||||
The repository to sync from.
|
The repository to sync from.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
--------
|
||||||
|
linkgit:gitnamespaces[7]
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
---
|
---
|
||||||
Part of the linkgit:git[1] suite
|
Part of the linkgit:git[1] suite
|
||||||
|
@ -10,8 +10,8 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
|
'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
|
||||||
[-p|--paginate|--no-pager] [--no-replace-objects]
|
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
|
||||||
[--bare] [--git-dir=<path>] [--work-tree=<path>]
|
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
|
||||||
[-c <name>=<value>]
|
[-c <name>=<value>]
|
||||||
[--help] <command> [<args>]
|
[--help] <command> [<args>]
|
||||||
|
|
||||||
@ -330,6 +330,11 @@ help ...`.
|
|||||||
variable (see core.worktree in linkgit:git-config[1] for a
|
variable (see core.worktree in linkgit:git-config[1] for a
|
||||||
more detailed discussion).
|
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::
|
--bare::
|
||||||
Treat the repository as a bare repository. If GIT_DIR
|
Treat the repository as a bare repository. If GIT_DIR
|
||||||
environment is not set, it is set to the current working
|
environment is not set, it is set to the current working
|
||||||
@ -593,6 +598,10 @@ git so take care if using Cogito etc.
|
|||||||
This can also be controlled by the '--work-tree' command line
|
This can also be controlled by the '--work-tree' command line
|
||||||
option and the core.worktree configuration variable.
|
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'::
|
'GIT_CEILING_DIRECTORIES'::
|
||||||
This should be a colon-separated list of absolute paths.
|
This should be a colon-separated list of absolute paths.
|
||||||
If set, it is a list of directories that git should not chdir
|
If set, it is a list of directories that git should not chdir
|
||||||
|
75
Documentation/gitnamespaces.txt
Normal file
75
Documentation/gitnamespaces.txt
Normal file
@ -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).
|
@ -120,9 +120,25 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
|
|||||||
return 0;
|
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)
|
static void write_head_info(void)
|
||||||
{
|
{
|
||||||
for_each_ref(show_ref, NULL);
|
for_each_ref(show_ref_cb, NULL);
|
||||||
if (!sent_capabilities)
|
if (!sent_capabilities)
|
||||||
show_ref("capabilities^{}", null_sha1, 0, NULL);
|
show_ref("capabilities^{}", null_sha1, 0, NULL);
|
||||||
|
|
||||||
@ -333,6 +349,8 @@ static void refuse_unconfigured_deny_delete_current(void)
|
|||||||
static const char *update(struct command *cmd)
|
static const char *update(struct command *cmd)
|
||||||
{
|
{
|
||||||
const char *name = cmd->ref_name;
|
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 *old_sha1 = cmd->old_sha1;
|
||||||
unsigned char *new_sha1 = cmd->new_sha1;
|
unsigned char *new_sha1 = cmd->new_sha1;
|
||||||
struct ref_lock *lock;
|
struct ref_lock *lock;
|
||||||
@ -343,7 +361,10 @@ static const char *update(struct command *cmd)
|
|||||||
return "funny refname";
|
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) {
|
switch (deny_current_branch) {
|
||||||
case DENY_IGNORE:
|
case DENY_IGNORE:
|
||||||
break;
|
break;
|
||||||
@ -371,7 +392,7 @@ static const char *update(struct command *cmd)
|
|||||||
return "deletion prohibited";
|
return "deletion prohibited";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(name, head_name)) {
|
if (!strcmp(namespaced_name, head_name)) {
|
||||||
switch (deny_delete_current) {
|
switch (deny_delete_current) {
|
||||||
case DENY_IGNORE:
|
case DENY_IGNORE:
|
||||||
break;
|
break;
|
||||||
@ -427,14 +448,14 @@ static const char *update(struct command *cmd)
|
|||||||
rp_warning("Allowing deletion of corrupt ref.");
|
rp_warning("Allowing deletion of corrupt ref.");
|
||||||
old_sha1 = NULL;
|
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);
|
rp_error("failed to delete %s", name);
|
||||||
return "failed to delete";
|
return "failed to delete";
|
||||||
}
|
}
|
||||||
return NULL; /* good */
|
return NULL; /* good */
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
lock = lock_any_ref_for_update(name, old_sha1, 0);
|
lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
rp_error("failed to lock %s", name);
|
rp_error("failed to lock %s", name);
|
||||||
return "failed to lock";
|
return "failed to lock";
|
||||||
@ -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)
|
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 string_list_item *item;
|
||||||
struct command *dst_cmd;
|
struct command *dst_cmd;
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
|
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
|
||||||
int flag;
|
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))
|
if (!(flag & REF_ISSYMREF))
|
||||||
return;
|
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)
|
if ((item = string_list_lookup(list, dst_name)) == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
3
cache.h
3
cache.h
@ -394,6 +394,7 @@ static inline enum object_type object_type(unsigned int mode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
|
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
|
||||||
|
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
|
||||||
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
|
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
|
||||||
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
|
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
|
||||||
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
|
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
|
||||||
@ -434,6 +435,8 @@ extern char *get_object_directory(void);
|
|||||||
extern char *get_index_file(void);
|
extern char *get_index_file(void);
|
||||||
extern char *get_graft_file(void);
|
extern char *get_graft_file(void);
|
||||||
extern int set_git_dir(const char *path);
|
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 *get_git_work_tree(void);
|
||||||
extern const char *read_gitfile_gently(const char *path);
|
extern const char *read_gitfile_gently(const char *path);
|
||||||
extern void set_git_work_tree(const char *tree);
|
extern void set_git_work_tree(const char *tree);
|
||||||
|
@ -1469,7 +1469,7 @@ _git_help ()
|
|||||||
__gitcomp "$__git_all_commands $(__git_aliases)
|
__gitcomp "$__git_all_commands $(__git_aliases)
|
||||||
attributes cli core-tutorial cvs-migration
|
attributes cli core-tutorial cvs-migration
|
||||||
diffcore gitk glossary hooks ignore modules
|
diffcore gitk glossary hooks ignore modules
|
||||||
repository-layout tutorial tutorial-2
|
namespaces repository-layout tutorial tutorial-2
|
||||||
workflows
|
workflows
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
@ -2640,6 +2640,7 @@ _git ()
|
|||||||
--exec-path
|
--exec-path
|
||||||
--html-path
|
--html-path
|
||||||
--work-tree=
|
--work-tree=
|
||||||
|
--namespace=
|
||||||
--help
|
--help
|
||||||
"
|
"
|
||||||
;;
|
;;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* are.
|
* are.
|
||||||
*/
|
*/
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
#include "refs.h"
|
||||||
|
|
||||||
char git_default_email[MAX_GITNAME];
|
char git_default_email[MAX_GITNAME];
|
||||||
char git_default_name[MAX_GITNAME];
|
char git_default_name[MAX_GITNAME];
|
||||||
@ -66,6 +67,9 @@ int core_preload_index = 0;
|
|||||||
char *git_work_tree_cfg;
|
char *git_work_tree_cfg;
|
||||||
static char *work_tree;
|
static char *work_tree;
|
||||||
|
|
||||||
|
static const char *namespace;
|
||||||
|
static size_t namespace_len;
|
||||||
|
|
||||||
static const char *git_dir;
|
static const char *git_dir;
|
||||||
static char *git_object_dir, *git_index_file, *git_graft_file;
|
static char *git_object_dir, *git_index_file, *git_graft_file;
|
||||||
|
|
||||||
@ -87,6 +91,27 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
|
|||||||
NULL
|
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)
|
static void setup_git_env(void)
|
||||||
{
|
{
|
||||||
git_dir = getenv(GIT_DIR_ENVIRONMENT);
|
git_dir = getenv(GIT_DIR_ENVIRONMENT);
|
||||||
@ -112,6 +137,8 @@ static void setup_git_env(void)
|
|||||||
git_graft_file = git_pathdup("info/grafts");
|
git_graft_file = git_pathdup("info/grafts");
|
||||||
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
|
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
|
||||||
read_replace_refs = 0;
|
read_replace_refs = 0;
|
||||||
|
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
|
||||||
|
namespace_len = strlen(namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_bare_repository(void)
|
int is_bare_repository(void)
|
||||||
@ -132,6 +159,20 @@ const char *get_git_dir(void)
|
|||||||
return git_dir;
|
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;
|
static int git_work_tree_initialized;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
18
git.c
18
git.c
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
const char git_usage_string[] =
|
const char git_usage_string[] =
|
||||||
"git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
|
"git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
|
||||||
" [-p|--paginate|--no-pager] [--no-replace-objects]\n"
|
" [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n"
|
||||||
" [--bare] [--git-dir=<path>] [--work-tree=<path>]\n"
|
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
|
||||||
" [-c name=value] [--help]\n"
|
" [-c name=value] [--help]\n"
|
||||||
" <command> [<args>]";
|
" <command> [<args>]";
|
||||||
|
|
||||||
@ -126,6 +126,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
|
|||||||
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
|
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
|
||||||
if (envchanged)
|
if (envchanged)
|
||||||
*envchanged = 1;
|
*envchanged = 1;
|
||||||
|
} else if (!strcmp(cmd, "--namespace")) {
|
||||||
|
if (*argc < 2) {
|
||||||
|
fprintf(stderr, "No namespace given for --namespace.\n" );
|
||||||
|
usage(git_usage_string);
|
||||||
|
}
|
||||||
|
setenv(GIT_NAMESPACE_ENVIRONMENT, (*argv)[1], 1);
|
||||||
|
if (envchanged)
|
||||||
|
*envchanged = 1;
|
||||||
|
(*argv)++;
|
||||||
|
(*argc)--;
|
||||||
|
} else if (!prefixcmp(cmd, "--namespace=")) {
|
||||||
|
setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
|
||||||
|
if (envchanged)
|
||||||
|
*envchanged = 1;
|
||||||
} else if (!strcmp(cmd, "--work-tree")) {
|
} else if (!strcmp(cmd, "--work-tree")) {
|
||||||
if (*argc < 2) {
|
if (*argc < 2) {
|
||||||
fprintf(stderr, "No directory given for --work-tree.\n" );
|
fprintf(stderr, "No directory given for --work-tree.\n" );
|
||||||
|
33
refs.c
33
refs.c
@ -584,7 +584,7 @@ int read_ref(const char *ref, unsigned char *sha1)
|
|||||||
static int do_one_ref(const char *base, each_ref_fn fn, int trim,
|
static int do_one_ref(const char *base, each_ref_fn fn, int trim,
|
||||||
int flags, void *cb_data, struct ref_list *entry)
|
int flags, void *cb_data, struct ref_list *entry)
|
||||||
{
|
{
|
||||||
if (strncmp(base, entry->name, trim))
|
if (prefixcmp(entry->name, base))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
|
if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
|
||||||
@ -728,12 +728,12 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
|||||||
|
|
||||||
int for_each_ref(each_ref_fn fn, void *cb_data)
|
int for_each_ref(each_ref_fn fn, void *cb_data)
|
||||||
{
|
{
|
||||||
return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
|
return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||||
{
|
{
|
||||||
return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
|
return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
|
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
|
||||||
@ -782,6 +782,31 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
|
|||||||
return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
|
return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
|
||||||
|
{
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
int ret = 0;
|
||||||
|
unsigned char sha1[20];
|
||||||
|
int flag;
|
||||||
|
|
||||||
|
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
|
||||||
|
if (resolve_ref(buf.buf, sha1, 1, &flag))
|
||||||
|
ret = fn(buf.buf, sha1, flag, cb_data);
|
||||||
|
strbuf_release(&buf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
|
||||||
|
{
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
int ret;
|
||||||
|
strbuf_addf(&buf, "%srefs/", get_git_namespace());
|
||||||
|
ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
|
||||||
|
strbuf_release(&buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
|
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
|
||||||
const char *prefix, void *cb_data)
|
const char *prefix, void *cb_data)
|
||||||
{
|
{
|
||||||
@ -819,7 +844,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
|
|||||||
|
|
||||||
int for_each_rawref(each_ref_fn fn, void *cb_data)
|
int for_each_rawref(each_ref_fn fn, void *cb_data)
|
||||||
{
|
{
|
||||||
return do_for_each_ref(NULL, "refs/", fn, 0,
|
return do_for_each_ref(NULL, "", fn, 0,
|
||||||
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
|
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
refs.h
3
refs.h
@ -36,6 +36,9 @@ extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, voi
|
|||||||
extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
|
extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
|
||||||
extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
|
extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
|
||||||
|
|
||||||
|
extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
|
||||||
|
extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
|
||||||
|
|
||||||
static inline const char *has_glob_specials(const char *pattern)
|
static inline const char *has_glob_specials(const char *pattern)
|
||||||
{
|
{
|
||||||
return strpbrk(pattern, "?*[");
|
return strpbrk(pattern, "?*[");
|
||||||
|
85
t/t5509-fetch-push-namespaces.sh
Executable file
85
t/t5509-fetch-push-namespaces.sh
Executable file
@ -0,0 +1,85 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='fetch/push involving ref namespaces'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
test_tick &&
|
||||||
|
git init original &&
|
||||||
|
(
|
||||||
|
cd original &&
|
||||||
|
echo 0 >count &&
|
||||||
|
git add count &&
|
||||||
|
test_commit 0 &&
|
||||||
|
echo 1 >count &&
|
||||||
|
git add count &&
|
||||||
|
test_commit 1 &&
|
||||||
|
git remote add pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
|
||||||
|
git remote add pushee-unnamespaced ../pushee
|
||||||
|
) &&
|
||||||
|
commit0=$(cd original && git rev-parse HEAD^) &&
|
||||||
|
commit1=$(cd original && git rev-parse HEAD) &&
|
||||||
|
git init pushee &&
|
||||||
|
git init puller
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pushing into a repository using a ref namespace' '
|
||||||
|
(
|
||||||
|
cd original &&
|
||||||
|
git push pushee-namespaced master &&
|
||||||
|
git ls-remote pushee-namespaced >actual &&
|
||||||
|
printf "$commit1\trefs/heads/master\n" >expected &&
|
||||||
|
test_cmp expected actual &&
|
||||||
|
git push pushee-namespaced --tags &&
|
||||||
|
git ls-remote pushee-namespaced >actual &&
|
||||||
|
printf "$commit0\trefs/tags/0\n" >>expected &&
|
||||||
|
printf "$commit1\trefs/tags/1\n" >>expected &&
|
||||||
|
test_cmp expected actual &&
|
||||||
|
# Verify that the GIT_NAMESPACE environment variable works as well
|
||||||
|
GIT_NAMESPACE=namespace git ls-remote "ext::git %s ../pushee" >actual &&
|
||||||
|
test_cmp expected actual &&
|
||||||
|
# Verify that --namespace overrides GIT_NAMESPACE
|
||||||
|
GIT_NAMESPACE=garbage git ls-remote pushee-namespaced >actual &&
|
||||||
|
test_cmp expected actual &&
|
||||||
|
# Try a namespace with no content
|
||||||
|
git ls-remote "ext::git --namespace=garbage %s ../pushee" >actual &&
|
||||||
|
test_cmp /dev/null actual &&
|
||||||
|
git ls-remote pushee-unnamespaced >actual &&
|
||||||
|
sed -e "s|refs/|refs/namespaces/namespace/refs/|" expected >expected.unnamespaced &&
|
||||||
|
test_cmp expected.unnamespaced actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pulling from a repository using a ref namespace' '
|
||||||
|
(
|
||||||
|
cd puller &&
|
||||||
|
git remote add -f pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
|
||||||
|
git for-each-ref refs/ >actual &&
|
||||||
|
printf "$commit1 commit\trefs/remotes/pushee-namespaced/master\n" >expected &&
|
||||||
|
printf "$commit0 commit\trefs/tags/0\n" >>expected &&
|
||||||
|
printf "$commit1 commit\trefs/tags/1\n" >>expected &&
|
||||||
|
test_cmp expected actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
# This test with clone --mirror checks for possible regressions in clone
|
||||||
|
# or the machinery underneath it. It ensures that no future change
|
||||||
|
# causes clone to ignore refs in refs/namespaces/*. In particular, it
|
||||||
|
# protects against a regression caused by any future change to the refs
|
||||||
|
# machinery that might cause it to ignore refs outside of refs/heads/*
|
||||||
|
# or refs/tags/*. More generally, this test also checks the high-level
|
||||||
|
# functionality of using clone --mirror to back up a set of repos hosted
|
||||||
|
# in the namespaces of a single repo.
|
||||||
|
test_expect_success 'mirroring a repository using a ref namespace' '
|
||||||
|
git clone --mirror pushee mirror &&
|
||||||
|
(
|
||||||
|
cd mirror &&
|
||||||
|
git for-each-ref refs/ >actual &&
|
||||||
|
printf "$commit1 commit\trefs/namespaces/namespace/refs/heads/master\n" >expected &&
|
||||||
|
printf "$commit0 commit\trefs/namespaces/namespace/refs/tags/0\n" >>expected &&
|
||||||
|
printf "$commit1 commit\trefs/namespaces/namespace/refs/tags/1\n" >>expected &&
|
||||||
|
test_cmp expected actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
@ -641,16 +641,17 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
|
|||||||
" 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 = parse_object(sha1);
|
struct object *o = parse_object(sha1);
|
||||||
|
const char *refname_nons = strip_namespace(refname);
|
||||||
|
|
||||||
if (!o)
|
if (!o)
|
||||||
die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
|
die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
|
||||||
|
|
||||||
if (capabilities)
|
if (capabilities)
|
||||||
packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname,
|
packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons,
|
||||||
0, capabilities,
|
0, capabilities,
|
||||||
stateless_rpc ? " no-done" : "");
|
stateless_rpc ? " no-done" : "");
|
||||||
else
|
else
|
||||||
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
|
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
|
||||||
capabilities = NULL;
|
capabilities = NULL;
|
||||||
if (!(o->flags & OUR_REF)) {
|
if (!(o->flags & OUR_REF)) {
|
||||||
o->flags |= OUR_REF;
|
o->flags |= OUR_REF;
|
||||||
@ -659,7 +660,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
|
|||||||
if (o->type == OBJ_TAG) {
|
if (o->type == OBJ_TAG) {
|
||||||
o = deref_tag(o, refname, 0);
|
o = deref_tag(o, refname, 0);
|
||||||
if (o)
|
if (o)
|
||||||
packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
|
packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname_nons);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -680,12 +681,12 @@ static void upload_pack(void)
|
|||||||
{
|
{
|
||||||
if (advertise_refs || !stateless_rpc) {
|
if (advertise_refs || !stateless_rpc) {
|
||||||
reset_timeout();
|
reset_timeout();
|
||||||
head_ref(send_ref, NULL);
|
head_ref_namespaced(send_ref, NULL);
|
||||||
for_each_ref(send_ref, NULL);
|
for_each_namespaced_ref(send_ref, NULL);
|
||||||
packet_flush(1);
|
packet_flush(1);
|
||||||
} else {
|
} else {
|
||||||
head_ref(mark_our_ref, NULL);
|
head_ref_namespaced(mark_our_ref, NULL);
|
||||||
for_each_ref(mark_our_ref, NULL);
|
for_each_namespaced_ref(mark_our_ref, NULL);
|
||||||
}
|
}
|
||||||
if (advertise_refs)
|
if (advertise_refs)
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user