init, clone: support --separate-git-dir for .git file
--separate-git-dir tells git to create git dir at the specified location, instead of where it is supposed to be. A .git file that points to that location will be put in place so that it appears normal to repo discovery process. 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
Mar 19, 2011 at 22:16 UTC
b57fb80a7d7d19102b31ab94a28ed43ea1ee07bb
7 files changed
+157
-6
Documentation/git-clone.txt
+10
@@ -12,6 +12,7 @@ SYNOPSIS
12
'git clone' [--template=<template_directory>]
13
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
14
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15
+ [--separate-git-dir|-L <git dir>]
16
[--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
17
[<directory>]
18
@@ -176,6 +177,15 @@ objects from the source repository into a pack in the cloned repository.
177
repository does not have a worktree/checkout (i.e. if any of
178
`--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
179
180
+-L=<git dir>::
181
+--separate-git-dir=<git dir>::
182
+ Instead of placing the cloned repository where it is supposed
183
+ to be, place the cloned repository at the specified directory,
184
+ then make a filesytem-agnostic git symbolic link to there.
185
+ The result is git repository can be separated from working
186
+ tree.
187
+
188
+
189
<repository>::
190
The (possibly remote) repository to clone from. See the
191
<<URLS,URLS>> section below for more information on specifying
Documentation/git-init.txt
+15
-2
@@ -8,7 +8,9 @@ git-init - Create an empty git repository or reinitialize an existing one
8
9
SYNOPSIS
10
--------
11
-'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory]
11
+'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
12
+ [--separate-git-dir|-L <git dir>]
13
+ [--shared[=<permissions>]] [directory]
14
15
16
DESCRIPTION
@@ -29,7 +31,8 @@ directory is used.
31
32
Running 'git init' in an existing repository is safe. It will not
33
overwrite things that are already there. The primary reason for
32
-rerunning 'git init' is to pick up newly added templates.
34
+rerunning 'git init' is to pick up newly added templates (or to move
35
+the repository to another place if --separate-git-dir is given).
36
37
OPTIONS
38
-------
@@ -51,6 +54,16 @@ current working directory.
54
Specify the directory from which templates will be used. (See the "TEMPLATE
55
DIRECTORY" section below.)
56
57
+-L=<git dir>::
58
+--separate-git-dir=<git dir>::
59
+
60
+Instead of initializing the repository where it is supposed to be,
61
+place a filesytem-agnostic git symbolic link there, pointing to the
62
+specified git path, and initialize a git repository at the path. The
63
+result is git repository can be separated from working tree. If this
64
+is reinitialization, the repository will be moved to the specified
65
+path.
66
+
67
--shared[=(false|true|umask|group|all|world|everybody|0xxx)]::
68
69
Specify that the git repository is to be shared amongst several users. This
builtin/clone.c
+7
-1
@@ -42,6 +42,7 @@ static int option_local, option_no_hardlinks, option_shared, option_recursive;
42
static char *option_template, *option_reference, *option_depth;
43
static char *option_origin = NULL;
44
static char *option_branch = NULL;
45
+static const char *real_git_dir;
46
static char *option_upload_pack = "git-upload-pack";
47
static int option_verbosity;
48
static int option_progress;
@@ -80,6 +81,8 @@ static struct option builtin_clone_options[] = {
81
"path to git-upload-pack on the remote"),
82
OPT_STRING(0, "depth", &option_depth, "depth",
83
"create a shallow clone of that depth"),
84
+ OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
85
+ "separate git dir from working tree"),
86
87
OPT_END()
88
};
@@ -466,7 +469,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
469
470
if (safe_create_leading_directories_const(git_dir) < 0)
471
die("could not create leading directories of '%s'", git_dir);
469
- set_git_dir(real_path(git_dir));
472
+
473
+ set_git_dir_init(git_dir, real_git_dir, 0);
474
+ if (real_git_dir)
475
+ git_dir = real_git_dir;
476
477
if (0 <= option_verbosity)
478
printf("Cloning into %s%s...\n",
builtin/init-db.c
+65
-3
@@ -21,6 +21,7 @@
21
static int init_is_bare_repository = 0;
22
static int init_shared_repository = -1;
23
static const char *init_db_template_dir;
24
+static const char *git_link;
25
26
static void safe_create_dir(const char *dir, int share)
27
{
@@ -311,11 +312,67 @@ static void create_object_directory(void)
312
free(path);
313
}
314
315
+int set_git_dir_init(const char *git_dir, const char *real_git_dir,
316
+ int exist_ok)
317
+{
318
+ if (real_git_dir) {
319
+ struct stat st;
320
+
321
+ if (!exist_ok && !stat(git_dir, &st))
322
+ die("%s already exists", git_dir);
323
+
324
+ if (!exist_ok && !stat(real_git_dir, &st))
325
+ die("%s already exists", real_git_dir);
326
+
327
+ /*
328
+ * make sure symlinks are resolved because we'll be
329
+ * moving the target repo later on in separate_git_dir()
330
+ */
331
+ git_link = xstrdup(real_path(git_dir));
332
+ }
333
+ else {
334
+ real_git_dir = real_path(git_dir);
335
+ git_link = NULL;
336
+ }
337
+ set_git_dir(real_path(real_git_dir));
338
+ return 0;
339
+}
340
+
341
+static void separate_git_dir(const char *git_dir)
342
+{
343
+ struct stat st;
344
+ FILE *fp;
345
+
346
+ if (!stat(git_link, &st)) {
347
+ const char *src;
348
+
349
+ if (S_ISREG(st.st_mode))
350
+ src = read_gitfile_gently(git_link);
351
+ else if (S_ISDIR(st.st_mode))
352
+ src = git_link;
353
+ else
354
+ die("unable to handle file type %d", st.st_mode);
355
+
356
+ if (rename(src, git_dir))
357
+ die_errno("unable to move %s to %s", src, git_dir);
358
+ }
359
+
360
+ fp = fopen(git_link, "w");
361
+ if (!fp)
362
+ die("Could not create git link %s", git_link);
363
+ fprintf(fp, "gitdir: %s\n", git_dir);
364
+ fclose(fp);
365
+}
366
+
367
int init_db(const char *template_dir, unsigned int flags)
368
{
369
int reinit;
370
+ const char *git_dir = get_git_dir();
371
318
- safe_create_dir(get_git_dir(), 0);
372
+ if (git_link)
373
+ separate_git_dir(git_dir);
374
+
375
+ safe_create_dir(git_dir, 0);
376
377
init_is_bare_repository = is_bare_repository();
378
@@ -352,7 +409,6 @@ int init_db(const char *template_dir, unsigned int flags)
409
}
410
411
if (!(flags & INIT_DB_QUIET)) {
355
- const char *git_dir = get_git_dir();
412
int len = strlen(git_dir);
413
printf("%s%s Git repository in %s%s\n",
414
reinit ? "Reinitialized existing" : "Initialized empty",
@@ -414,6 +470,7 @@ static const char *const init_db_usage[] = {
470
int cmd_init_db(int argc, const char **argv, const char *prefix)
471
{
472
const char *git_dir;
473
+ const char *real_git_dir = NULL;
474
const char *work_tree;
475
const char *template_dir = NULL;
476
unsigned int flags = 0;
@@ -427,11 +484,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
484
"specify that the git repository is to be shared amongst several users",
485
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
486
OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
487
+ OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
488
+ "separate git dir from working tree"),
489
OPT_END()
490
};
491
492
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
493
494
+ if (real_git_dir && !is_absolute_path(real_git_dir))
495
+ real_git_dir = xstrdup(real_path(real_git_dir));
496
+
497
if (argc == 1) {
498
int mkdir_tried = 0;
499
retry:
@@ -522,7 +584,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
584
set_git_work_tree(real_path(work_tree));
585
}
586
525
- set_git_dir(real_path(git_dir));
587
+ set_git_dir_init(git_dir, real_git_dir, 1);
588
589
return init_db(template_dir, flags);
590
}
cache.h
+1
@@ -436,6 +436,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
436
437
#define INIT_DB_QUIET 0x0001
438
439
+extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
440
extern int init_db(const char *template_dir, unsigned int flags);
441
442
#define alloc_nr(x) (((x)+16)*3/2)
t/t0001-init.sh
+46
@@ -374,4 +374,50 @@ test_expect_success 'init prefers command line to GIT_DIR' '
374
! test -d otherdir/refs
375
'
376
377
+test_expect_success 'init with separate gitdir' '
378
+ rm -rf newdir &&
379
+ git init --separate-git-dir realgitdir newdir &&
380
+ echo "gitdir: `pwd`/realgitdir" >expected &&
381
+ test_cmp expected newdir/.git &&
382
+ test -d realgitdir/refs
383
+'
384
+
385
+test_expect_success 're-init to update git link' '
386
+ (
387
+ cd newdir &&
388
+ git init --separate-git-dir ../surrealgitdir
389
+ ) &&
390
+ echo "gitdir: `pwd`/surrealgitdir" >expected &&
391
+ test_cmp expected newdir/.git &&
392
+ test -d surrealgitdir/refs &&
393
+ ! test -d realgitdir/refs
394
+'
395
+
396
+test_expect_success 're-init to move gitdir' '
397
+ rm -rf newdir realgitdir surrealgitdir &&
398
+ git init newdir &&
399
+ (
400
+ cd newdir &&
401
+ git init --separate-git-dir ../realgitdir
402
+ ) &&
403
+ echo "gitdir: `pwd`/realgitdir" >expected &&
404
+ test_cmp expected newdir/.git &&
405
+ test -d realgitdir/refs
406
+'
407
+
408
+test_expect_success 're-init to move gitdir symlink' '
409
+ rm -rf newdir realgitdir &&
410
+ git init newdir &&
411
+ (
412
+ cd newdir &&
413
+ mv .git here &&
414
+ ln -s here .git &&
415
+ git init -L ../realgitdir
416
+ ) &&
417
+ echo "gitdir: `pwd`/realgitdir" >expected &&
418
+ test_cmp expected newdir/.git &&
419
+ test -d realgitdir/refs &&
420
+ ! test -d newdir/here
421
+'
422
+
423
test_done
t/t5601-clone.sh
+13
@@ -192,4 +192,17 @@ test_expect_success 'do not respect url-encoding of non-url path' '
192
git clone x+y xy-regular
193
'
194
195
+test_expect_success 'clone separate gitdir' '
196
+ rm -rf dst &&
197
+ git clone --separate-git-dir realgitdir src dst &&
198
+ echo "gitdir: `pwd`/realgitdir" >expected &&
199
+ test_cmp expected dst/.git &&
200
+ test -d realgitdir/refs
201
+'
202
+
203
+test_expect_success 'clone separate gitdir where target already exists' '
204
+ rm -rf dst &&
205
+ test_must_fail git clone --separate-git-dir realgitdir src dst
206
+'
207
+
208
test_done