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 functionsmaint
						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 | ||||||
|  |  | ||||||
|  | @ -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, "?*["); | ||||||
|  |  | ||||||
|  | @ -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
	
	 Junio C Hamano
						Junio C Hamano