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>]
+         [--separate-git-dir|-L <git dir>]
          [--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)
 
+-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
index 0a4a20e1857cde9823a6933d324fff45d33525f8..58cd01145ab8605d30a9c670ee70391b9d974a32 100644 (file)
@@ -8,7 +8,9 @@ git-init - Create an empty git repository or reinitialize an existing one
 
 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
@@ -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
-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
 -------
@@ -51,6 +54,16 @@ current working directory.
 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
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 const char *real_git_dir;
 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"),
+       OPT_STRING('L', "separate-git-dir", &real_git_dir, "gitdir",
+                  "separate git dir from working tree"),
 
        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);
-       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",
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 const char *git_link;
 
 static void safe_create_dir(const char *dir, int share)
 {
@@ -311,11 +312,67 @@ static void create_object_directory(void)
        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;
+       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();
 
@@ -352,7 +409,6 @@ int init_db(const char *template_dir, unsigned int flags)
        }
 
        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",
@@ -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;
+       const char *real_git_dir = NULL;
        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),
+               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);
 
+       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:
@@ -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_dir(real_path(git_dir));
+       set_git_dir_init(git_dir, real_git_dir, 1);
 
        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
 
+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)
index f6849932112f1bbea1dafad09150857a707b64e4..b2e6919da1477e6a1b53182c3061abde844ffc67 100755 (executable)
@@ -374,4 +374,50 @@ test_expect_success 'init prefers command line to GIT_DIR' '
        ! 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
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
 '
 
+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