Skip to content

Commit b57fb80

Browse files
pcloudsgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9d379f4 commit b57fb80

File tree

7 files changed

+157
-6
lines changed

7 files changed

+157
-6
lines changed

Documentation/git-clone.txt

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SYNOPSIS
1212
'git clone' [--template=<template_directory>]
1313
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
1414
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15+
[--separate-git-dir|-L <git dir>]
1516
[--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
1617
[<directory>]
1718

@@ -176,6 +177,15 @@ objects from the source repository into a pack in the cloned repository.
176177
repository does not have a worktree/checkout (i.e. if any of
177178
`--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
178179

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+
179189
<repository>::
180190
The (possibly remote) repository to clone from. See the
181191
<<URLS,URLS>> section below for more information on specifying

Documentation/git-init.txt

+15-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ git-init - Create an empty git repository or reinitialize an existing one
88

99
SYNOPSIS
1010
--------
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]
1214

1315

1416
DESCRIPTION
@@ -29,7 +31,8 @@ directory is used.
2931

3032
Running 'git init' in an existing repository is safe. It will not
3133
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).
3336

3437
OPTIONS
3538
-------
@@ -51,6 +54,16 @@ current working directory.
5154
Specify the directory from which templates will be used. (See the "TEMPLATE
5255
DIRECTORY" section below.)
5356

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+
5467
--shared[=(false|true|umask|group|all|world|everybody|0xxx)]::
5568

5669
Specify that the git repository is to be shared amongst several users. This

builtin/clone.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ static int option_local, option_no_hardlinks, option_shared, option_recursive;
4242
static char *option_template, *option_reference, *option_depth;
4343
static char *option_origin = NULL;
4444
static char *option_branch = NULL;
45+
static const char *real_git_dir;
4546
static char *option_upload_pack = "git-upload-pack";
4647
static int option_verbosity;
4748
static int option_progress;
@@ -80,6 +81,8 @@ static struct option builtin_clone_options[] = {
8081
"path to git-upload-pack on the remote"),
8182
OPT_STRING(0, "depth", &option_depth, "depth",
8283
"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"),
8386

8487
OPT_END()
8588
};
@@ -466,7 +469,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
466469

467470
if (safe_create_leading_directories_const(git_dir) < 0)
468471
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;
470476

471477
if (0 <= option_verbosity)
472478
printf("Cloning into %s%s...\n",

builtin/init-db.c

+65-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
static int init_is_bare_repository = 0;
2222
static int init_shared_repository = -1;
2323
static const char *init_db_template_dir;
24+
static const char *git_link;
2425

2526
static void safe_create_dir(const char *dir, int share)
2627
{
@@ -311,11 +312,67 @@ static void create_object_directory(void)
311312
free(path);
312313
}
313314

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+
314367
int init_db(const char *template_dir, unsigned int flags)
315368
{
316369
int reinit;
370+
const char *git_dir = get_git_dir();
317371

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);
319376

320377
init_is_bare_repository = is_bare_repository();
321378

@@ -352,7 +409,6 @@ int init_db(const char *template_dir, unsigned int flags)
352409
}
353410

354411
if (!(flags & INIT_DB_QUIET)) {
355-
const char *git_dir = get_git_dir();
356412
int len = strlen(git_dir);
357413
printf("%s%s Git repository in %s%s\n",
358414
reinit ? "Reinitialized existing" : "Initialized empty",
@@ -414,6 +470,7 @@ static const char *const init_db_usage[] = {
414470
int cmd_init_db(int argc, const char **argv, const char *prefix)
415471
{
416472
const char *git_dir;
473+
const char *real_git_dir = NULL;
417474
const char *work_tree;
418475
const char *template_dir = NULL;
419476
unsigned int flags = 0;
@@ -427,11 +484,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
427484
"specify that the git repository is to be shared amongst several users",
428485
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
429486
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"),
430489
OPT_END()
431490
};
432491

433492
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
434493

494+
if (real_git_dir && !is_absolute_path(real_git_dir))
495+
real_git_dir = xstrdup(real_path(real_git_dir));
496+
435497
if (argc == 1) {
436498
int mkdir_tried = 0;
437499
retry:
@@ -522,7 +584,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
522584
set_git_work_tree(real_path(work_tree));
523585
}
524586

525-
set_git_dir(real_path(git_dir));
587+
set_git_dir_init(git_dir, real_git_dir, 1);
526588

527589
return init_db(template_dir, flags);
528590
}

cache.h

+1
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
436436

437437
#define INIT_DB_QUIET 0x0001
438438

439+
extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
439440
extern int init_db(const char *template_dir, unsigned int flags);
440441

441442
#define alloc_nr(x) (((x)+16)*3/2)

t/t0001-init.sh

+46
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,50 @@ test_expect_success 'init prefers command line to GIT_DIR' '
374374
! test -d otherdir/refs
375375
'
376376

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+
377423
test_done

t/t5601-clone.sh

+13
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,17 @@ test_expect_success 'do not respect url-encoding of non-url path' '
192192
git clone x+y xy-regular
193193
'
194194

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+
195208
test_done

0 commit comments

Comments
 (0)