我可以将 .git 文件夹存储在我想要跟踪的文件之外吗?

175

我有一个不同寻常的想法,希望使用git作为备份系统。假设我有一个目录./backup/myfiles,并希望使用git进行备份。为了保持整洁,我不想在myfiles文件夹中有.git目录,因此我考虑创建./backup/git_repos/myfiles。从查看git文档来看,我尝试过执行以下操作:

$ cd backup/myfiles
$ mkdir ../git_repos/myfiles
$ git --git-dir=../git_repos/myfiles init
Initialized empty Git repository in backup/git_repos/myfiles/
$ git --git-dir="../git_repos/myfiles/" add foo
fatal: pathspec 'foo' did not match any files

您可以在那里看到我收到的错误消息。我做错了什么吗?


10
除了您的备用方案外,这也可以用来将您的“dotfiles”(.bashrc,.vimrc等)保留在主目录中,同时将.git文件夹放在其他地方。 - Philip
6
最简单的答案:https://dev59.com/4nRB5IYBdhLWcg3wz6QK#19548676(因为旧的赞而被埋藏) - Brandon Bertelsen
1
如果您没有写入权限或不想对工作目录进行任何更改(如添加.git/等),则下面这个由Leo提供的答案是最好的选择(也被旧的赞同所掩盖)。链接 - KobeJohn
1
@Philip,除非您的dotfiles存储库还包含Git子模块。否则,Git不支持将子模块与外部工作树结合使用。 - maxschlepzig
10个回答

202

您只需要确保代码库知道 工作树(work tree)的位置以及反之。

要让代码库知道工作树位置,设置配置值core.worktree。要让工作树知道其git目录的位置,添加一个名为 .git 的文件(不是文件夹!)并添加一行类似于:

gitdir: /path/to/repo.git

从git 1.7.5开始,init命令增加了一个额外的选项。

您可以使用以下命令初始化一个新的独立存储库

git init --separate-git-dir /path/to/repo.git

这将在单独的目录中初始化git仓库,并在当前目录中添加.git文件,该目录是新仓库的工作目录。

在1.7.5之前,您需要使用略有不同的参数并手动添加.git文件。

要初始化一个单独的仓库,请使用以下命令将工作树与存储库链接:

git --git-dir=/path/to/repo.git --work-tree=. init && echo "gitdir: /path/to/repo.git" > .git

你当前的目录将成为工作树, git将使用位于 /path/to/repo.git 的仓库。init命令将自动设置--git-dir参数指定的 core.worktree 值。

你甚至可以为此添加一个别名:

[alias]
    initexternal = !"f() { git --work-tree=. --git-dir=\"$1\" init && echo \"gitdir: $1\" >> .git; }; f"

在只读工作目录上使用git版本控制

有了上面的知识,即使没有写权限,您也可以设置git版本控制来管理工作目录。如果您在每个git命令中使用--git-dir或从存储库内执行每个命令(而不是从工作目录),则可以省略.git文件,因此无需在工作目录中创建任何文件。详见Leos答案


9
你可以对现有的存储库执行此操作:将.git文件夹移动到任何你想要的位置,添加.git文件以指向它,然后就可以像平常一样使用存储库了。 - joachim
2
解决方法是在实际的git配置文件中指定'core.worktree',即在.git文件夹中指向的那个文件。 - joachim
2
是的,这就是我在回答的第二句话中所描述的。core.worktree配置值当然存储在.git文件夹的配置文件中,而.git文件指向该文件夹。 - niks
1
我发现当我使用这些指令时,生成的repo变成了一个裸库,并且出现了“fatal: core.worktree和core.bare不合理”的错误。看起来只需更改配置使其不是裸库即可解决该问题。 - Steven Lu
1
今天我尝试了2.29.2版本,发现在执行命令前不需要移动现有的.git目录。该命令会自动在瞬间移动目录,无需复制,只需重命名文件夹。这帮助我将880 MB的工作目录减少到220 MB(即本地Git存储库大小为660 MB),完美适配512 GB RAM磁盘,并将构建速度提高了20%,而且不会丢失任何本地提交。我的IDE(IntelliJ IDEA)也可以毫无问题地接受它。 - kriegaex
显示剩余9条评论

112
git --git-dir=../repo --work-tree=. add foo

这个方法可以达到你想要的效果,但是显然每次使用git命令时都要指定很麻烦。

你可以导出GIT_WORK_TREE=.GIT_DIR=../backup,Git会在每个命令上使用它们。不过这种方法只能让你在单个shell中舒适地工作一个仓库。

我更倾向于将.git目录建立符号链接到其他位置,或者从主备份目录创建一个指向.git目录的符号链接。


1
你可以在每个命令中不指定git-dir和work-tree,而且无需任何符号链接来实现相同的存档。请查看我的回答。 - niks
1
符号链接的缺点在于它存在于工作树中,如果其他进程清除了工作树,那么你就失去了符号链接。 - Jeff
2
此外,如果发帖人不想在他的工作树中有一个 .git 子目录,那么他为什么要想要一个符号链接呢? - Jeff
@Jeff:为了权衡利弊。(在他的备份情况下,清除工作树对他来说可能并不比清除任何其他目录(如存储库本身)更重要。) - Sz.
我创建类似的脚本或别名 - Krazy Glew
1
我正在使用direnv来管理我的笔记。我的工作目录在Dropbox的一个文件夹中,而我的git文件夹则在其他地方。这样,我就可以轻松地在所有电脑上进行更改,但仍然能够检查发生了什么变化,并且只有在发生重大变化时才提交。谢谢。 - Paulo Phagula

70

--separate-git-dir选项适用于我的Git版本(1.7.11.3)的git init(和git clone),可用于将git存储库与工作树分离,并在工作树的根目录中创建一个文件系统不可知的git符号链接(以一个名为.git的文件形式)。我认为这个结果与niks的回答是一样的。

git init --separate-git-dir path/to/repo.git path/to/worktree

3
使用 init 命令本身的命令行选项会更加清晰简洁。 - loopbackbee
在Windows中,repo.git被创建时其隐藏属性已设置。然后我手动更改了它。您是否知道这样做是否安全? - PA.
+1 是的,git在1.7.5版本中学习了这个命令行选项,当时还没有这个选项(如果我没记错的话)。我已经更新了我的答案,建议使用这个参数。 - niks

27

我发现倒转niks回答中使用的--work-tree--git-dir目录更简单:

$ cd read_only_repos
$ git --work-tree=/some/readonly/location/foo/ --git-dir=foo init
$ cd foo
$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .file_foo
        bar
        ...

这种方法有两个优点:

  • 它不需要任何命令行选项或者 .git 文件,你只需要在仓库的根目录下正常操作即可。
  • 即使你没有文件系统的所有权也可以将其纳入版本控制,Git 只会向仓库位置写入内容。

我遇到的唯一一个需要注意的地方是,你需要使用 info/exclude 文件来代替 .gitignore 文件。

这样你就可以使用仓库 read_only_repos/foo 作为你自己仓库中的远程仓库,即使原始文件没有被纳入版本控制。


这正是相同的命令。我看到的唯一区别是你交换了 --work-tree 和 --git-dir 参数的顺序。当然,由于你没有写入权限,所以不会在工作目录中创建 .git 文件。尽管如此,在没有写入权限的目录中使用版本控制是一个很好的用例。 :-) - niks
3
明白了,关键是在工作目录之外创建仓库。 - Leo
4
我喜欢这个工具——它让我在与其他人共享的Dropbox目录上使用Git,而其他参与者甚至都不知道Git正在被使用。 - Quentin Stafford-Fraser

20

在 Git 中,如果工作树的位置不同寻常,命名一个作为 Git 存储库的目录时,习惯上会使用 '.git' 扩展名,就像裸仓库一样。

mkdir ../git_repos/myfiles.git
如果您在初始化时提供了--work-tree选项,则这将自动设置core.worktree配置变量,这意味着一旦指定了git目录,git将知道在哪里找到工作树。
git --git-dir=../git_repos/myfiles.git --work-tree=. init

但你也可以在事后设置这个变量。

git --git-dir=../git_repos/myfiles.git config core.worktree "$(pwd)"

完成这些步骤后,add命令应该按预期工作。

git --git-dir=../git_repos/myfiles.git add foo

我发现如果你首先CD到../git_repos/myfiles.git,而不是在实际的工作树中,'git add foo'就会起作用,而且你不需要一直指定--git-dir。 - Steve Folly
1
没错,但我认为大多数人倾向于在他们的工作树中工作,而不是他们的代码库。当然,如果你正在使用一个分离的工作树,你可能正在做一些“特殊”的事情,并且可能会使用一些宏来帮助。 - CB Bailey

8

在 repo 内使用 git

cd ./backup/git_repos/myfiles
git init --bare
git config core.worktree ../myfiles
git config core.bare false

从现在开始,您可以在./backup/git_repos/myfiles目录中使用git,无需设置任何环境变量或其他参数。


这似乎是最好的答案,但我收到了“警告:core.bare和core.worktree无意义”的消息,这是否意味着它没有起作用? - hazrpg
1
我仍然认为它可以工作,但在设置工作树时会抱怨它是裸的。这就是为什么我之后将 core.bare 设置为 false - To1ne
啊!现在更有意义了。谢谢你。 - hazrpg

3
为了澄清以下两个选项之间的区别,您可以告诉git仓库跟踪其他地方的文件(core.worktree),或者在主工作树中放置一个指向其他地方的git仓库文件夹的面包屑(--separate-git-dir)。core.worktree选项不需要面包屑文件,并且自第一个版本以来就是git的一个特性。第二个选项是较新的选项,需要一个面包屑文件,但不需要更改存储库配置文件。

选项1:core.worktree

在要跟踪的路径之外初始化非裸仓库,并将core.worktree设置为要跟踪的路径。您可以使用终端命令设置此值,也可以直接编辑存储库配置文件以添加:

worktree = <path to files to backup>

不要将存储库文件夹设为此路径的子文件夹;这将是递归的。您可能可以尝试这样做,并简单地忽略存储库文件夹,但我认为git不会允许这种情况。

在您的情况下,您将转到backup/git_repos/以运行init命令,并可以使用--git-dir=./myfiles选项来覆盖默认存储库文件夹名称。命令将如下所示:

cd backup/git_repos 
git init --git-dir=./myfiles
git config core.worktree backup/myfiles

注意1: 这与git的较新版本ADDITIONAL工作树功能不同。这是您的主要工作树,所有非裸仓库都使用它。那些额外的工作树使用面包屑导航到git仓库文件夹下的子文件夹中,就像下面的选项2。

注意2: 我最近测试了许多用于Windows的git GUI,只有Git Extensions支持使用core.worktree移动主工作树。

不支持core.worktree的GUI

SourceTree、Fork、Tower、GitKraken、GitHub Desktop、GitAhead、SmartGit*和Git-Cola。当使用core.worktree时,您将希望坚持使用终端。

* SmartGit将此功能与Option 2混淆,并要求一个.git文件。但对于core.worktree并不需要此文件。

选项2:--separate-git-dir

使用--separate-git-dir=<用于保存存储库数据的路径>在要备份的路径处初始化存储库。这将使用指定的路径来保存存储库数据,并在初始化位置创建一个.git文件,其中包含以下行:

gitdir:<用于保存存储库数据的路径>

对于您而言,命令应如下所示:

cd backup/myfiles
git init --separate-git-dir=backup/git_repos/myfiles/

backup/myfiles/目录下,您的.git文件将包含gitdir: backup/git_repos/myfiles/

现在,您可以将.git文件的位置视为存储库位置,以此操作git。


在选项1中,您使用git init --git-dir=,但我认为应该改为git init --separate-git-dir=。已在GIT 2.36上进行了测试。 - NickSdot
@NickSdot:不,选项1不使用--separate-git-dir,这是选项2的作用,而有两个选项的整个目的就在于此。 --git-dir用于将.git文件夹重命名为其他名称,这是OP想要的另一个独立部分。 - CapinWinky

2
您可以创建一个名为“nodgit”(无点GIT)的脚本,内容如下:
#!/bin/sh
gits=/usr/local/gits
    x=`pwd`
    testdir() {( cd $1; pwd; )}
    while [ "$x" != "/" ]; do
      y=`echo $x|sed -e "s/\//__/g"`
      if ([ -d "$gits/$y" ]); then
        export GIT_DIR="$gits/$y"
        export GIT_WORK_TREE="$x"
        if ([ "$1" = "nodinit" ]); then
          mkdir -p "$GIT_DIR"
          git init --bare; exit $?
        elif ([ "$1" = "shell" ]); then
          bash; exit $?
        else
          exec git "$@"
        fi
      fi
      x=`testdir "$x/.."`
    done

您可以使用nodgit代替git,nodgit会查找git仓库并设置必要的变量。例如,假设您在/usr/local/gits/__home__foo_wibbles中有一个(bare)仓库,并且您在/home/foo/wibbles/one中,则它将找到正确的工作目录(/home/foo/wibbles)和仓库。
另外,您还可以使用"nodgit shell"获取带有正确变量设置的shell,以便使用普通的git命令。

1
假设您的myfiles目录已经存在并且有一些内容,您能接受这种情况吗?
cd ~/backup
git init
git add myfiles

.git 目录将在 backup 中,而不是 myfiles 中。


虽然那样做可以解决我的问题,但我宁愿只将myfiles文件夹存储在git下,而不是其他任何东西。 - Amandasaurus
您可以使用.gitignore文件来保留您想要git跟踪的文件选择。如果您将*!myfiles添加到其中,那么只有该目录将被跟踪。但是,如果您想为另一个目录创建单独的存储库,则会遇到问题... - To1ne

0
我创建的脚本看起来像这样:
~/bin/git-slash:
#!/usr/bin/sh

export GIT_DIR=/home/Version-Control/cygwin-root.git/
export GIT_WORK_TREE=/

git --git-dir=$GIT_DIR --work-tree=$GIT_WORK_TREE "$@"

exit $?

使用--git_dir=$GIT_DIR是多余的,但提醒我也可以在脚本外设置环境变量。

上面的示例用于跟踪对cygwin系统文件的本地更改。

可以为任何需要此功能的重要项目制作这样的脚本 - 但/或不带/.git是我的主要用途。

如果消除冗余,上述内容足够小,可以制作一个shell别名或函数。

如果我经常这样做,我将恢复工作区到存储库的映射

"Boxes, Links, and Parallel Trees: Elements of a Configuration Management System", 
in Workshop Proceedings of the Software Management Conference. 1989.

最接近的现代对应物是Perforce mappings or views,支持部分检出以及工作区和仓库的非共位。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接