Merge branch 'nd/init-gitdir'
authorJunio C Hamano <gitster@pobox.com>
Sat, 2 Apr 2011 00:57:37 +0000 (17:57 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 2 Apr 2011 00:57:37 +0000 (17:57 -0700)
* nd/init-gitdir:
init, clone: support --separate-git-dir for .git file
git-init.txt: move description section up

Conflicts:
builtin/clone.c

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 593192555e3b407536c81e4cd7b1bcc0dbe8f519..58cd01145ab8605d30a9c670ee70391b9d974a32 100644 (file)
@@ -8,9 +8,32 @@ 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
+-----------
+
+This command creates an empty git repository - basically a `.git`
+directory with subdirectories for `objects`, `refs/heads`,
+`refs/tags`, and template files.  An initial `HEAD` file that
+references the HEAD of the master branch is also created.
+
+If the `$GIT_DIR` environment variable is set then it specifies a path
+to use instead of `./.git` for the base of the repository.
+
+If the object storage directory is specified via the
+`$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories
+are created underneath - otherwise the default `$GIT_DIR/objects`
+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 (or to move
+the repository to another place if --separate-git-dir is given).
+
 OPTIONS
 -------
 
@@ -31,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
@@ -74,32 +107,6 @@ line, the command is run inside the directory (possibly after creating it).
 --
 
 
-DESCRIPTION
------------
-This command creates an empty git repository - basically a `.git` directory
-with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
-template files.
-An initial `HEAD` file that references the HEAD of the master branch
-is also created.
-
-If the `$GIT_DIR` environment variable is set then it specifies a path
-to use instead of `./.git` for the base of the repository.
-
-If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
-environment variable then the sha1 directories are created underneath -
-otherwise the default `$GIT_DIR/objects` 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.
-
-Note that 'git init' is the same as 'git init-db'.  The command
-was primarily meant to initialize the object database, but over
-time it has become responsible for setting up the other aspects
-of the repository, such as installing the default hooks and
-setting the configuration variables.  The old name is retained
-for backward compatibility reasons.
-
 TEMPLATE DIRECTORY
 ------------------
 
index 0b5601a9b080e079ec077a8a765be24f2bf9bd29..4144bcf5ca320057b5ef1f5dac9c2df19aba250b 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()
 };
@@ -467,7 +470,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) {
                if (option_bare)
index 6621e5671cbc4cbe5631a40f918aa4f0f05bb62e..b7370d9bb8f235f1cf39b1ffa768498ca1e88654 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();
+
+       if (git_link)
+               separate_git_dir(git_dir);
 
-       safe_create_dir(get_git_dir(), 0);
+       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);
 
                /*
@@ -420,6 +476,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;
@@ -433,11 +490,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:
@@ -528,7 +590,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 9f06d215790ef98c2138ab26c1a9b4ddafd33c55..2674f4cf5a74aaa8e6137985d552d7b6123a432c 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -437,6 +437,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 0ee9f179939f073ea8e918a6a94328daf88e8d8a..a5816d0e4f3744f8ba30666d8bb5828abf6cda51 100755 (executable)
@@ -371,4 +371,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 a92d145be6e168c44e1cd09e6629e87cde0d156f..5a068b21e4aeac79863df657d6388ec4c2732d55 100755 (executable)
@@ -191,4 +191,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