$GIT_COMMON_DIR: a new environment variable

This variable is intended to support multiple working directories attached to a repository. Such a repository may have a main working directory, created by either "git init" or "git clone" and one or more linked working directories. These working directories and the main repository share the same repository directory. In linked working directories, $GIT_COMMON_DIR must be defined to point to the real repository directory and $GIT_DIR points to an unused subdirectory inside $GIT_COMMON_DIR. File locations inside the repository are reorganized from the linked worktree view point: - worktree-specific such as HEAD, logs/HEAD, index, other top-level refs and unrecognized files are from $GIT_DIR. - the rest like objects, refs, info, hooks, packed-refs, shallow... are from $GIT_COMMON_DIR (except info/sparse-checkout, but that's a separate patch) Scripts are supposed to retrieve paths in $GIT_DIR with "git rev-parse --git-path", which will take care of "$GIT_DIR vs $GIT_COMMON_DIR" business. The redirection is done by git_path(), git_pathdup() and strbuf_git_path(). The selected list of paths goes to $GIT_COMMON_DIR, not the other way around in case a developer adds a new worktree-specific file and it's accidentally promoted to be shared across repositories (this includes unknown files added by third party commands) The list of known files that belong to $GIT_DIR are: ADD_EDIT.patch BISECT_ANCESTORS_OK BISECT_EXPECTED_REV BISECT_LOG BISECT_NAMES CHERRY_PICK_HEAD COMMIT_MSG FETCH_HEAD HEAD MERGE_HEAD MERGE_MODE MERGE_RR NOTES_EDITMSG NOTES_MERGE_WORKTREE ORIG_HEAD REVERT_HEAD SQUASH_MSG TAG_EDITMSG fast_import_crash_* logs/HEAD next-index-* rebase-apply rebase-merge rsync-refs-* sequencer/* shallow_* Path mapping is NOT done for git_path_submodule(). Multi-checkouts are not supported as submodules. Helped-by: Jens Lehmann <Jens.Lehmann@web.de> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Nov 30, 2014 at 15:24 UTC c7b3a3d2fe2688a30ddb8d516ed000eeda13c24e
6 files changed +117 -19
Documentation/git.txt
+8
@@ -809,6 +809,14 @@ Git so take care if using Cogito etc.
809 an explicit repository directory set via 'GIT_DIR' or on the
810 command line.
811
812 +'GIT_COMMON_DIR'::
813 + If this variable is set to a path, non-worktree files that are
814 + normally in $GIT_DIR will be taken from this path
815 + instead. Worktree-specific files such as HEAD or index are
816 + taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
817 + details. This variable has lower precedence than other path
818 + variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
819 +
820 Git Commits
821 ~~~~~~~~~~~
822 'GIT_AUTHOR_NAME'::
Documentation/gitrepository-layout.txt
+34 -11
@@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
46 use with dumb transports but otherwise is OK as long as
47 `objects/info/alternates` points at the object stores it
48 borrows from.
49 ++
50 +This directory is ignored if $GIT_COMMON_DIR is set and
51 +"$GIT_COMMON_DIR/objects" will be used instead.
52
53 objects/[0-9a-f][0-9a-f]::
54 A newly created object is stored in its own file.
@@ -92,7 +95,8 @@ refs::
95 References are stored in subdirectories of this
96 directory. The 'git prune' command knows to preserve
97 objects reachable from refs found in this directory and
95 - its subdirectories.
98 + its subdirectories. This directory is ignored if $GIT_COMMON_DIR
99 + is set and "$GIT_COMMON_DIR/refs" will be used instead.
100
101 refs/heads/`name`::
102 records tip-of-the-tree commit objects of branch `name`
@@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::
118 packed-refs::
119 records the same information as refs/heads/, refs/tags/,
120 and friends record in a more efficient way. See
117 - linkgit:git-pack-refs[1].
121 + linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
122 + is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
123
124 HEAD::
125 A symref (see glossary) to the `refs/heads/` namespace
@@ -133,6 +138,11 @@ being a symref to point at the current branch. Such a state
138 is often called 'detached HEAD.' See linkgit:git-checkout[1]
139 for details.
140
141 +config::
142 + Repository specific configuration file. This file is ignored
143 + if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
144 + used instead.
145 +
146 branches::
147 A slightly deprecated way to store shorthands to be used
148 to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -140,7 +150,10 @@ branches::
150 'name' can be given to these commands in place of
151 'repository' argument. See the REMOTES section in
152 linkgit:git-fetch[1] for details. This mechanism is legacy
143 - and not likely to be found in modern repositories.
153 + and not likely to be found in modern repositories. This
154 + directory is ignored if $GIT_COMMON_DIR is set and
155 + "$GIT_COMMON_DIR/branches" will be used instead.
156 +
157
158 hooks::
159 Hooks are customization scripts used by various Git
@@ -149,7 +162,9 @@ hooks::
162 default. To enable, the `.sample` suffix has to be
163 removed from the filename by renaming.
164 Read linkgit:githooks[5] for more details about
152 - each hook.
165 + each hook. This directory is ignored if $GIT_COMMON_DIR is set
166 + and "$GIT_COMMON_DIR/hooks" will be used instead.
167 +
168
169 index::
170 The current index file for the repository. It is
@@ -161,7 +176,8 @@ sharedindex.<SHA-1>::
176
177 info::
178 Additional information about the repository is recorded
164 - in this directory.
179 + in this directory. This directory is ignored if $GIT_COMMON_DIR
180 + is set and "$GIT_COMMON_DIR/index" will be used instead.
181
182 info/refs::
183 This file helps dumb transports discover what refs are
@@ -201,12 +217,15 @@ remotes::
217 when interacting with remote repositories via 'git fetch',
218 'git pull' and 'git push' commands. See the REMOTES section
219 in linkgit:git-fetch[1] for details. This mechanism is legacy
204 - and not likely to be found in modern repositories.
220 + and not likely to be found in modern repositories. This
221 + directory is ignored if $GIT_COMMON_DIR is set and
222 + "$GIT_COMMON_DIR/remotes" will be used instead.
223
224 logs::
207 - Records of changes made to refs are stored in this
208 - directory. See linkgit:git-update-ref[1]
209 - for more information.
225 + Records of changes made to refs are stored in this directory.
226 + See linkgit:git-update-ref[1] for more information. This
227 + directory is ignored if $GIT_COMMON_DIR is set and
228 + "$GIT_COMMON_DIR/logs" will be used instead.
229
230 logs/refs/heads/`name`::
231 Records all changes made to the branch tip named `name`.
@@ -217,10 +236,14 @@ logs/refs/tags/`name`::
236 shallow::
237 This is similar to `info/grafts` but is internally used
238 and maintained by shallow clone mechanism. See `--depth`
220 - option to linkgit:git-clone[1] and linkgit:git-fetch[1].
239 + option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
240 + file is ignored if $GIT_COMMON_DIR is set and
241 + "$GIT_COMMON_DIR/shallow" will be used instead.
242
243 modules::
223 - Contains the git-repositories of the submodules.
244 + Contains the git-repositories of the submodules. This
245 + directory is ignored if $GIT_COMMON_DIR is set and
246 + "$GIT_COMMON_DIR/modules" will be used instead.
247
248 SEE ALSO
249 --------
cache.h
+3 -1
@@ -377,6 +377,7 @@ static inline enum object_type object_type(unsigned int mode)
377
378 /* Double-check local_repo_env below if you add to this list. */
379 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
380 +#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
381 #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
382 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
383 #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@ -430,6 +431,7 @@ extern int is_inside_git_dir(void);
431 extern char *git_work_tree_cfg;
432 extern int is_inside_work_tree(void);
433 extern const char *get_git_dir(void);
434 +extern const char *get_git_common_dir(void);
435 extern int is_git_directory(const char *path);
436 extern char *get_object_directory(void);
437 extern char *get_index_file(void);
@@ -617,7 +619,7 @@ extern int fsync_object_files;
619 extern int core_preload_index;
620 extern int core_apply_sparse_checkout;
621 extern int precomposed_unicode;
620 -extern int git_db_env, git_index_env, git_graft_env;
622 +extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
623
624 /*
625 * The character that begins a commented line in user-editable file
environment.c
+22 -7
@@ -81,9 +81,9 @@ static char *work_tree;
81 static const char *namespace;
82 static size_t namespace_len;
83
84 -static const char *git_dir;
84 +static const char *git_dir, *git_common_dir;
85 static char *git_object_dir, *git_index_file, *git_graft_file;
86 -int git_db_env, git_index_env, git_graft_env;
86 +int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
87
88 /*
89 * Repository-local GIT_* environment variables; see cache.h for details.
@@ -101,6 +101,7 @@ const char * const local_repo_env[] = {
101 NO_REPLACE_OBJECTS_ENVIRONMENT,
102 GIT_PREFIX_ENVIRONMENT,
103 GIT_SHALLOW_FILE_ENVIRONMENT,
104 + GIT_COMMON_DIR_ENVIRONMENT,
105 NULL
106 };
107
@@ -125,8 +126,8 @@ static char *expand_namespace(const char *raw_namespace)
126 return strbuf_detach(&buf, NULL);
127 }
128
128 -static char *git_path_from_env(const char *envvar, const char *path,
129 - int *fromenv)
129 +static char *git_path_from_env(const char *envvar, const char *git_dir,
130 + const char *path, int *fromenv)
131 {
132 const char *value = getenv(envvar);
133 if (!value) {
@@ -149,9 +150,18 @@ static void setup_git_env(void)
150 git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
151 gitfile = read_gitfile(git_dir);
152 git_dir = xstrdup(gitfile ? gitfile : git_dir);
152 - git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects", &git_db_env);
153 - git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index", &git_index_env);
154 - git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts", &git_graft_env);
153 + git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
154 + if (git_common_dir) {
155 + git_common_dir_env = 1;
156 + git_common_dir = xstrdup(git_common_dir);
157 + } else
158 + git_common_dir = git_dir;
159 + git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,
160 + "objects", &git_db_env);
161 + git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,
162 + "index", &git_index_env);
163 + git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir,
164 + "info/grafts", &git_graft_env);
165 if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
166 check_replace_refs = 0;
167 namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
@@ -174,6 +184,11 @@ const char *get_git_dir(void)
184 return git_dir;
185 }
186
187 +const char *get_git_common_dir(void)
188 +{
189 + return git_common_dir;
190 +}
191 +
192 const char *get_git_namespace(void)
193 {
194 if (!namespace)
path.c
+34
@@ -90,6 +90,38 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
90 buf->buf[newlen] = '/';
91 }
92
93 +static const char *common_list[] = {
94 + "/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
95 + "/objects", "/refs", "/remotes", "/rr-cache", "/svn",
96 + "config", "gc.pid", "packed-refs", "shallow",
97 + NULL
98 +};
99 +
100 +static void update_common_dir(struct strbuf *buf, int git_dir_len)
101 +{
102 + char *base = buf->buf + git_dir_len;
103 + const char **p;
104 +
105 + if (is_dir_file(base, "logs", "HEAD"))
106 + return; /* keep this in $GIT_DIR */
107 + for (p = common_list; *p; p++) {
108 + const char *path = *p;
109 + int is_dir = 0;
110 + if (*path == '/') {
111 + path++;
112 + is_dir = 1;
113 + }
114 + if (is_dir && dir_prefix(base, path)) {
115 + replace_dir(buf, git_dir_len, get_git_common_dir());
116 + return;
117 + }
118 + if (!is_dir && !strcmp(base, path)) {
119 + replace_dir(buf, git_dir_len, get_git_common_dir());
120 + return;
121 + }
122 + }
123 +}
124 +
125 static void adjust_git_path(struct strbuf *buf, int git_dir_len)
126 {
127 const char *base = buf->buf + git_dir_len;
@@ -101,6 +133,8 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
133 get_index_file(), strlen(get_index_file()));
134 else if (git_db_env && dir_prefix(base, "objects"))
135 replace_dir(buf, git_dir_len + 7, get_object_directory());
136 + else if (git_common_dir_env)
137 + update_common_dir(buf, git_dir_len);
138 }
139
140 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
t/t0060-path-utils.sh
+16
@@ -262,5 +262,21 @@ test_expect_success 'setup fake objects directory foo' 'mkdir foo'
262 test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
263 test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
264 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
265 +test_expect_success 'setup common repository' 'git --git-dir=bar init'
266 +test_git_path GIT_COMMON_DIR=bar index .git/index
267 +test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD
268 +test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD
269 +test_git_path GIT_COMMON_DIR=bar objects bar/objects
270 +test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar
271 +test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude
272 +test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts
273 +test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar
274 +test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar
275 +test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master
276 +test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master
277 +test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me
278 +test_git_path GIT_COMMON_DIR=bar config bar/config
279 +test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
280 +test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
281
282 test_done