init, clone: support --separate-git-dir for .git file
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>
Sat, 19 Mar 2011 15:16:56 +0000 (22:16 +0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 20 Mar 2011 04:48:19 +0000 (21:48 -0700)
--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>
Documentation/git-clone.txt
Documentation/git-init.txt
builtin/clone.c
builtin/init-db.c
cache.h
t/t0001-init.sh
t/t5601-clone.sh
index 8577480074d84fe213cc6fd16486a1fa92c3e08a..86eb4c93682aa3d92fe001ae871913fc0dec8607 100644 (file)
@@ -12,6 +12,7 @@ SYNOPSIS
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
+         [--separate-git-dir|-L <git dir>]
          [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
          [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
@@ -176,6 +177,15 @@ objects from the source repository into a pack in the cloned repository.
        repository does not have a worktree/checkout (i.e. if any of
        `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
 
        repository does not have a worktree/checkout (i.e. if any of
        `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
 
+-L=<git dir>::
+--separate-git-dir=<git dir>::
+       Instead of placing the cloned repository where it is supposed
+       to be, place the cloned repository at the specified directory,
+       then make a filesytem-agnostic git symbolic link to there.
+       The result is git repository can be separated from working
+       tree.
+
+
 <repository>::
        The (possibly remote) repository to clone from.  See the
        <<URLS,URLS>> section below for more information on specifying
 <repository>::
        The (possibly remote) repository to clone from.  See the
        <<URLS,URLS>> section below for more information on specifying
index 0a4a20e1857cde9823a6933d324fff45d33525f8..58cd01145ab8605d30a9c670ee70391b9d974a32 100644 (file)
@@ -8,7 +8,9 @@ git-init - Create an empty git repository or reinitialize an existing one
 
 SYNOPSIS
 --------
 
 SYNOPSIS
 --------
-'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory]
+'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
+         [--separate-git-dir|-L <git dir>]
+         [--shared[=<permissions>]] [directory]
 
 
 DESCRIPTION
 
 
 DESCRIPTION
@@ -29,7 +31,8 @@ directory is used.
 
 Running 'git init' in an existing repository is safe. It will not
 overwrite things that are already there. The primary reason for
 
 Running 'git init' in an existing repository is safe. It will not
 overwrite things that are already there. The primary reason for
-rerunning 'git init' is to pick up newly added templates.
+rerunning 'git init' is to pick up newly added templates (or to move
+the repository to another place if --separate-git-dir is given).
 
 OPTIONS
 -------
 
 OPTIONS
 -------
@@ -51,6 +54,16 @@ current working directory.
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
 
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
 
+-L=<git dir>::
+--separate-git-dir=<git dir>::
+
+Instead of initializing the repository where it is supposed to be,
+place a filesytem-agnostic git symbolic link there, pointing to the
+specified git path, and initialize a git repository at the path. The
+result is git repository can be separated from working tree. If this
+is reinitialization, the repository will be moved to the specified
+path.
+
 --shared[=(false|true|umask|group|all|world|everybody|0xxx)]::
 
 Specify that the git repository is to be shared amongst several users.  This
 --shared[=(false|true|umask|group|all|world|everybody|0xxx)]::
 
 Specify that the git repository is to be shared amongst several users.  This
index 404f589680151c69a57f746eb5b225e54aa8d8a0..097beca3ab4675be9fb324d6624be92b5330c816 100644 (file)
@@ -42,6 +42,7 @@ static int option_local, option_no_hardlinks, option_shared, option_recursive;
 static char *option_template, *option_reference, *option_depth;
 static char *option_origin = NULL;
 static char *option_branch = NULL;
 static char *option_template, *option_reference, *option_depth;
 static char *option_origin = NULL;
 static char *option_branch = NULL;
+static const char *real_git_dir;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress;
@@ -80,6 +81,8 @@ static struct option builtin_clone_options[] = {
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
+       OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
+                  "separate git dir from working tree"),
 
        OPT_END()
 };
 
        OPT_END()
 };
@@ -466,7 +469,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (safe_create_leading_directories_const(git_dir) < 0)
                die("could not create leading directories of '%s'", git_dir);
 
        if (safe_create_leading_directories_const(git_dir) < 0)
                die("could not create leading directories of '%s'", git_dir);
-       set_git_dir(real_path(git_dir));
+
+       set_git_dir_init(git_dir, real_git_dir, 0);
+       if (real_git_dir)
+               git_dir = real_git_dir;
 
        if (0 <= option_verbosity)
                printf("Cloning into %s%s...\n",
 
        if (0 <= option_verbosity)
                printf("Cloning into %s%s...\n",
index 8f5cfd712243975dacd08cb51c69c2e174e470b1..887939923236e65eda305c3c633879bf7084dc3f 100644 (file)
@@ -21,6 +21,7 @@
 static int init_is_bare_repository = 0;
 static int init_shared_repository = -1;
 static const char *init_db_template_dir;
 static int init_is_bare_repository = 0;
 static int init_shared_repository = -1;
 static const char *init_db_template_dir;
+static const char *git_link;
 
 static void safe_create_dir(const char *dir, int share)
 {
 
 static void safe_create_dir(const char *dir, int share)
 {
@@ -311,11 +312,67 @@ static void create_object_directory(void)
        free(path);
 }
 
        free(path);
 }
 
+int set_git_dir_init(const char *git_dir, const char *real_git_dir,
+                    int exist_ok)
+{
+       if (real_git_dir) {
+               struct stat st;
+
+               if (!exist_ok && !stat(git_dir, &st))
+                       die("%s already exists", git_dir);
+
+               if (!exist_ok && !stat(real_git_dir, &st))
+                       die("%s already exists", real_git_dir);
+
+               /*
+                * make sure symlinks are resolved because we'll be
+                * moving the target repo later on in separate_git_dir()
+                */
+               git_link = xstrdup(real_path(git_dir));
+       }
+       else {
+               real_git_dir = real_path(git_dir);
+               git_link = NULL;
+       }
+       set_git_dir(real_path(real_git_dir));
+       return 0;
+}
+
+static void separate_git_dir(const char *git_dir)
+{
+       struct stat st;
+       FILE *fp;
+
+       if (!stat(git_link, &st)) {
+               const char *src;
+
+               if (S_ISREG(st.st_mode))
+                       src = read_gitfile_gently(git_link);
+               else if (S_ISDIR(st.st_mode))
+                       src = git_link;
+               else
+                       die("unable to handle file type %d", st.st_mode);
+
+               if (rename(src, git_dir))
+                       die_errno("unable to move %s to %s", src, git_dir);
+       }
+
+       fp = fopen(git_link, "w");
+       if (!fp)
+               die("Could not create git link %s", git_link);
+       fprintf(fp, "gitdir: %s\n", git_dir);
+       fclose(fp);
+}
+
 int init_db(const char *template_dir, unsigned int flags)
 {
        int reinit;
 int init_db(const char *template_dir, unsigned int flags)
 {
        int reinit;
+       const char *git_dir = get_git_dir();
 
 
-       safe_create_dir(get_git_dir(), 0);
+       if (git_link)
+               separate_git_dir(git_dir);
+
+       safe_create_dir(git_dir, 0);
 
        init_is_bare_repository = is_bare_repository();
 
 
        init_is_bare_repository = is_bare_repository();
 
@@ -352,7 +409,6 @@ int init_db(const char *template_dir, unsigned int flags)
        }
 
        if (!(flags & INIT_DB_QUIET)) {
        }
 
        if (!(flags & INIT_DB_QUIET)) {
-               const char *git_dir = get_git_dir();
                int len = strlen(git_dir);
                printf("%s%s Git repository in %s%s\n",
                       reinit ? "Reinitialized existing" : "Initialized empty",
                int len = strlen(git_dir);
                printf("%s%s Git repository in %s%s\n",
                       reinit ? "Reinitialized existing" : "Initialized empty",
@@ -414,6 +470,7 @@ static const char *const init_db_usage[] = {
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
        const char *git_dir;
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
        const char *git_dir;
+       const char *real_git_dir = NULL;
        const char *work_tree;
        const char *template_dir = NULL;
        unsigned int flags = 0;
        const char *work_tree;
        const char *template_dir = NULL;
        unsigned int flags = 0;
@@ -427,11 +484,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        "specify that the git repository is to be shared amongst several users",
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
                OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
                        "specify that the git repository is to be shared amongst several users",
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
                OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
+               OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
+                          "separate git dir from working tree"),
                OPT_END()
        };
 
        argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
                OPT_END()
        };
 
        argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
+       if (real_git_dir && !is_absolute_path(real_git_dir))
+               real_git_dir = xstrdup(real_path(real_git_dir));
+
        if (argc == 1) {
                int mkdir_tried = 0;
        retry:
        if (argc == 1) {
                int mkdir_tried = 0;
        retry:
@@ -522,7 +584,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        set_git_work_tree(real_path(work_tree));
        }
 
                        set_git_work_tree(real_path(work_tree));
        }
 
-       set_git_dir(real_path(git_dir));
+       set_git_dir_init(git_dir, real_git_dir, 1);
 
        return init_db(template_dir, flags);
 }
 
        return init_db(template_dir, flags);
 }
diff --git a/cache.h b/cache.h
index a99fd560e97193a1750433be8c674cbc4eb73e19..0b99487caffa5d663839329b3d168012cf1c7738 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -436,6 +436,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
 
 #define INIT_DB_QUIET 0x0001
 
 
 #define INIT_DB_QUIET 0x0001
 
+extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
 extern int init_db(const char *template_dir, unsigned int flags);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 extern int init_db(const char *template_dir, unsigned int flags);
 
 #define alloc_nr(x) (((x)+16)*3/2)
index f6849932112f1bbea1dafad09150857a707b64e4..b2e6919da1477e6a1b53182c3061abde844ffc67 100755 (executable)
@@ -374,4 +374,50 @@ test_expect_success 'init prefers command line to GIT_DIR' '
        ! test -d otherdir/refs
 '
 
        ! test -d otherdir/refs
 '
 
+test_expect_success 'init with separate gitdir' '
+       rm -rf newdir &&
+       git init --separate-git-dir realgitdir newdir &&
+       echo "gitdir: `pwd`/realgitdir" >expected &&
+       test_cmp expected newdir/.git &&
+       test -d realgitdir/refs
+'
+
+test_expect_success 're-init to update git link' '
+       (
+       cd newdir &&
+       git init --separate-git-dir ../surrealgitdir
+       ) &&
+       echo "gitdir: `pwd`/surrealgitdir" >expected &&
+       test_cmp expected newdir/.git &&
+       test -d surrealgitdir/refs &&
+       ! test -d realgitdir/refs
+'
+
+test_expect_success 're-init to move gitdir' '
+       rm -rf newdir realgitdir surrealgitdir &&
+       git init newdir &&
+       (
+       cd newdir &&
+       git init --separate-git-dir ../realgitdir
+       ) &&
+       echo "gitdir: `pwd`/realgitdir" >expected &&
+       test_cmp expected newdir/.git &&
+       test -d realgitdir/refs
+'
+
+test_expect_success 're-init to move gitdir symlink' '
+       rm -rf newdir realgitdir &&
+       git init newdir &&
+       (
+       cd newdir &&
+       mv .git here &&
+       ln -s here .git &&
+       git init -L ../realgitdir
+       ) &&
+       echo "gitdir: `pwd`/realgitdir" >expected &&
+       test_cmp expected newdir/.git &&
+       test -d realgitdir/refs &&
+       ! test -d newdir/here
+'
+
 test_done
 test_done
index 987e0c846309a8a49dbe31ce292edd55342217b3..c467b6797e61fc65c1780d200a0465f13e5fb74d 100755 (executable)
@@ -192,4 +192,17 @@ test_expect_success 'do not respect url-encoding of non-url path' '
        git clone x+y xy-regular
 '
 
        git clone x+y xy-regular
 '
 
+test_expect_success 'clone separate gitdir' '
+       rm -rf dst &&
+       git clone --separate-git-dir realgitdir src dst &&
+       echo "gitdir: `pwd`/realgitdir" >expected &&
+       test_cmp expected dst/.git &&
+       test -d realgitdir/refs
+'
+
+test_expect_success 'clone separate gitdir where target already exists' '
+       rm -rf dst &&
+       test_must_fail git clone --separate-git-dir realgitdir src dst
+'
+
 test_done
 test_done