Add platform-independent .git "symlink"
This patch allows .git to be a regular textfile containing the path of the real git directory (prefixed with "gitdir: "), which can be useful on platforms lacking support for real symlinks. Signed-off-by: Lars Hjemli <hjemli@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Lars Hjemli committed
Feb 20, 2008 at 23:13 UTC
b44ebb19e3234c5dffe9869ceac5408bb44c2e20
5 files changed
+157
-1
Documentation/repository-layout.txt
+4
-1
@@ -3,7 +3,10 @@ git repository layout
3
4
You may find these things in your git repository (`.git`
5
directory for a repository associated with your working tree, or
6
-`'project'.git` directory for a public 'bare' repository).
6
+`'project'.git` directory for a public 'bare' repository. It is
7
+also possible to have a working tree where `.git` is a plain
8
+ascii file containing `gitdir: <path>`, i.e. the path to the
9
+real git repository).
10
11
objects::
12
Object store associated with this repository. Usually
cache.h
+1
@@ -311,6 +311,7 @@ extern char *get_index_file(void);
311
extern char *get_graft_file(void);
312
extern int set_git_dir(const char *path);
313
extern const char *get_git_work_tree(void);
314
+extern const char *read_gitfile_gently(const char *path);
315
316
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
317
environment.c
+2
@@ -49,6 +49,8 @@ static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
49
static void setup_git_env(void)
50
{
51
git_dir = getenv(GIT_DIR_ENVIRONMENT);
52
+ if (!git_dir)
53
+ git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
54
if (!git_dir)
55
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
56
git_object_dir = getenv(DB_ENVIRONMENT);
setup.c
+47
@@ -314,6 +314,44 @@ static int check_repository_format_gently(int *nongit_ok)
314
return 0;
315
}
316
317
+/*
318
+ * Try to read the location of the git directory from the .git file,
319
+ * return path to git directory if found.
320
+ */
321
+const char *read_gitfile_gently(const char *path)
322
+{
323
+ char *buf;
324
+ struct stat st;
325
+ int fd;
326
+ size_t len;
327
+
328
+ if (stat(path, &st))
329
+ return NULL;
330
+ if (!S_ISREG(st.st_mode))
331
+ return NULL;
332
+ fd = open(path, O_RDONLY);
333
+ if (fd < 0)
334
+ die("Error opening %s: %s", path, strerror(errno));
335
+ buf = xmalloc(st.st_size + 1);
336
+ len = read_in_full(fd, buf, st.st_size);
337
+ close(fd);
338
+ if (len != st.st_size)
339
+ die("Error reading %s", path);
340
+ buf[len] = '\0';
341
+ if (prefixcmp(buf, "gitdir: "))
342
+ die("Invalid gitfile format: %s", path);
343
+ while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
344
+ len--;
345
+ if (len < 9)
346
+ die("No path in gitfile: %s", path);
347
+ buf[len] = '\0';
348
+ if (!is_git_directory(buf + 8))
349
+ die("Not a git repository: %s", buf + 8);
350
+ path = make_absolute_path(buf + 8);
351
+ free(buf);
352
+ return path;
353
+}
354
+
355
/*
356
* We cannot decide in this function whether we are in the work tree or
357
* not, since the config can only be read _after_ this function was called.
@@ -323,6 +361,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
361
const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
362
static char cwd[PATH_MAX+1];
363
const char *gitdirenv;
364
+ const char *gitfile_dir;
365
int len, offset;
366
367
/*
@@ -377,8 +416,10 @@ const char *setup_git_directory_gently(int *nongit_ok)
416
417
/*
418
* Test in the following order (relative to the cwd):
419
+ * - .git (file containing "gitdir: <path>")
420
* - .git/
421
* - ./ (bare)
422
+ * - ../.git
423
* - ../.git/
424
* - ../ (bare)
425
* - ../../.git/
@@ -386,6 +427,12 @@ const char *setup_git_directory_gently(int *nongit_ok)
427
*/
428
offset = len = strlen(cwd);
429
for (;;) {
430
+ gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
431
+ if (gitfile_dir) {
432
+ if (set_git_dir(gitfile_dir))
433
+ die("Repository setup failed");
434
+ break;
435
+ }
436
if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
437
break;
438
if (is_git_directory(".")) {
t/t0002-gitfile.sh
new
+103
@@ -0,0 +1,103 @@
1
+#!/bin/sh
2
+
3
+test_description='.git file
4
+
5
+Verify that plumbing commands work when .git is a file
6
+'
7
+. ./test-lib.sh
8
+
9
+objpath() {
10
+ echo "$1" | sed -e 's|\(..\)|\1/|'
11
+}
12
+
13
+objck() {
14
+ p=$(objpath "$1")
15
+ if test ! -f "$REAL/objects/$p"
16
+ then
17
+ echo "Object not found: $REAL/objects/$p"
18
+ false
19
+ fi
20
+}
21
+
22
+
23
+test_expect_success 'initial setup' '
24
+ REAL="$(pwd)/.real" &&
25
+ mv .git "$REAL"
26
+'
27
+
28
+test_expect_success 'bad setup: invalid .git file format' '
29
+ echo "gitdir $REAL" >.git &&
30
+ if git rev-parse 2>.err
31
+ then
32
+ echo "git rev-parse accepted an invalid .git file"
33
+ false
34
+ fi &&
35
+ if ! grep -qe "Invalid gitfile format" .err
36
+ then
37
+ echo "git rev-parse returned wrong error"
38
+ false
39
+ fi
40
+'
41
+
42
+test_expect_success 'bad setup: invalid .git file path' '
43
+ echo "gitdir: $REAL.not" >.git &&
44
+ if git rev-parse 2>.err
45
+ then
46
+ echo "git rev-parse accepted an invalid .git file path"
47
+ false
48
+ fi &&
49
+ if ! grep -qe "Not a git repository" .err
50
+ then
51
+ echo "git rev-parse returned wrong error"
52
+ false
53
+ fi
54
+'
55
+
56
+test_expect_success 'final setup + check rev-parse --git-dir' '
57
+ echo "gitdir: $REAL" >.git &&
58
+ test "$REAL" = "$(git rev-parse --git-dir)"
59
+'
60
+
61
+test_expect_success 'check hash-object' '
62
+ echo "foo" >bar &&
63
+ SHA=$(cat bar | git hash-object -w --stdin) &&
64
+ objck $SHA
65
+'
66
+
67
+test_expect_success 'check cat-file' '
68
+ git cat-file blob $SHA >actual &&
69
+ diff -u bar actual
70
+'
71
+
72
+test_expect_success 'check update-index' '
73
+ if test -f "$REAL/index"
74
+ then
75
+ echo "Hmm, $REAL/index exists?"
76
+ false
77
+ fi &&
78
+ rm -f "$REAL/objects/$(objpath $SHA)" &&
79
+ git update-index --add bar &&
80
+ if ! test -f "$REAL/index"
81
+ then
82
+ echo "$REAL/index not found"
83
+ false
84
+ fi &&
85
+ objck $SHA
86
+'
87
+
88
+test_expect_success 'check write-tree' '
89
+ SHA=$(git write-tree) &&
90
+ objck $SHA
91
+'
92
+
93
+test_expect_success 'check commit-tree' '
94
+ SHA=$(echo "commit bar" | git commit-tree $SHA) &&
95
+ objck $SHA
96
+'
97
+
98
+test_expect_success 'check rev-list' '
99
+ echo $SHA >"$REAL/HEAD" &&
100
+ test "$SHA" = "$(git rev-list HEAD)"
101
+'
102
+
103
+test_done