在Git中签出子目录?


160

是否可以在Git中检出存储库的子目录?

想象一下,我正在设置一个新的WordPress安装。我将为插件和主题自定义创建两个新目录:

  • wordpress/wp-content/plugins/myplugins/
  • wordpress/wp-content/themes/mytheme/

我想通过Git维护这些目录。在Subversion中,我将通过具有trunk/myplugins/trunk/mytheme/目录并签出子目录来实现此目的。Git是否可以使用单个存储库来完成相同任务?

作为长期使用SVN的用户,很少接触Git,我可能会错过某些Git范例。

编辑: 存储不同内容的多个分支是处理此问题的一种有趣方法。


2
您为什么不签出整个存储库,并建立指向要使用的子目录的符号链接?
randomness2077



是否可以进行稀疏签出并引用Git存储库?
luka5z

Answers:


121

稀疏签出现在在Git 1.7中

另请参阅问题“ 是否可以在不先检出整个存储库的情况下进行稀疏检出?”。

请注意,即使Git下载的某些文件不会出现在您的工作树中,但稀疏签出仍然需要您下载整个存储库。


1
哪里的git clone简单命令?好吧,我用这个答案,正在工作!
彼得·克劳斯

4
有没有一种方法可以重命名这些文件夹?如果我的结帐稀疏/foo/bar/foobar,是否只能/foobar在本地存储库中看到?
graywolf

17

在git中没有真正的方法。而且,如果您不会做一个会同时影响两个树的更改,那么就没有充分的理由同时使用两个存储库了。我以为我会错过Subversion的这一功能,但是我发现创建存储库几乎没有管理上的精神开销(这是由于存储库存储在其工作副本的旁边,而不是要求我在存储库之外明确选择某个位置)工作副本),我习惯于制作许多小型的单一用途存储库。

但是,如果您坚持(或确实需要),则可以创建一个带有just mythememypluginsdirectory 的git存储库,并在WordPress安装中将它们符号链接。


MDCore写道:

提交,例如,mytheme将增加myplugin的修订号

请注意,如果您确实决定将两个目录都放在一个存储库中,则这与git无关,因为git完全摒弃了单调增加任何形式的修订号的概念。

将什么东西放到git的单个存储库中的唯一标准是它是否构成单个单元,即。在您的情况下,是否存在一些更改,因此没有必要单独查看每个目录中的编辑。如果您需要一次修改两个目录中的文件的更改,并且这些修改属于同一类,则它们应该是一个存储库。如果不是,则不要将它们粘在一起。

Git确实真的希望您对单独的实体使用单独的存储库。

子模块

子模块不能满足将两个目录都保留在一个存储库中的需求,因为子模块实际上会为每个目录强制建立一个单独的存储库,然后使用子模块将其合并到另一个存储库中。更糟糕的是,由于WordPress安装中的目录不是同一目录的直接子目录,并且是具有许多其他文件的层次结构的一部分,因此将每个目录存储库用作统一存储库中的子模块将不会带来任何好处,因为统一存储库不会反映任何用例/需求。


git clone简单的命令序列在哪里?好吧,我用这个答案,正在工作!
彼得·克劳斯

16

我不喜欢稀疏检出的一件事是,如果您要检出一个深几个目录的子目录,则目录结构必须包含所有导致该目录的目录。

我要解决的方法是在不是我的工作空间的地方克隆存储库,然后在我的工作空间目录中创建一个指向存储库中子目录的符号链接。Git的工作方式非常好,因为git status之类的东西会显示相对于您当前工作目录的更改文件。


仅在支持符号链接的OS中有效。他们需要更改稀疏签出的工作方式。
安德斯·林登

1
对于已签出目录中带有符号链接的创意+1。但是,稀疏签出和符号链接并不互斥:您不需要完整的克隆。
apitsch

10

实际上,对于Git来说,“狭窄”,“部分”或“稀疏”的结帐正在大量发展中。请注意,您仍将拥有完整的存储库.git。因此,其他两个职位是Git当前状态的最新职位,但看起来我们最终将能够进行稀疏签出。 如果您对更多详细信息感兴趣,请查看邮件列表 - 邮件列表正在迅速变化。


很高兴知道!我喜欢在一个存储库下拥有如此紧密相关的目录,并且在可能的情况下会这样做。
安妮卡·贝克斯特伦

5

git clone --filter 从Git 2.19

该选项实际上将跳过从服务器获取不需要的对象的操作:

git clone --depth 1 --no-checkout --filter=blob:none \
  "file://$(pwd)/server_repo" local_repo
cd local_repo
git checkout master -- mdir/

服务器应配置为:

git config --local uploadpack.allowfilter 1
git config --local uploadpack.allowanysha1inwant 1

从v2.19.0开始,没有服务器支持,但是已经在本地进行了测试。

file://$(path)需要克服git clone协议的技巧:如何使用相对路径浅克隆本地git存储库?

请记住,这--depth 1已经暗示了--single-branch,另请参见:如何在Git中克隆单个分支?

TODO:--filter=blob:none跳过所有blob,但仍获取所有树对象。但是在正常的仓库中,与文件本身相比,它应该很小,所以已经足够了。在以下位置进行询问:https: //www.spinics.net/lists/git/msg342006.html开发人员回答说,--filter=tree:0正在为此做准备。

的格式--filter记录在上man git-rev-list

对Git远程协议进行了扩展以支持此功能。

Git树上的文档:

测试一下

#!/usr/bin/env bash
set -eu

list-objects() (
  git rev-list --all --objects
  echo "master commit SHA: $(git log -1 --format="%H")"
  echo "mybranch commit SHA: $(git log -1 --format="%H")"
  git ls-tree master
  git ls-tree mybranch | grep mybranch
  git ls-tree master~ | grep root
)

# Reproducibility.
export GIT_COMMITTER_NAME='a'
export GIT_COMMITTER_EMAIL='a'
export GIT_AUTHOR_NAME='a'
export GIT_AUTHOR_EMAIL='a'
export GIT_COMMITTER_DATE='2000-01-01T00:00:00+0000'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'

rm -rf server_repo local_repo
mkdir server_repo
cd server_repo

# Create repo.
git init --quiet
git config --local uploadpack.allowfilter 1
git config --local uploadpack.allowanysha1inwant 1

# First commit.
# Directories present in all branches.
mkdir d1 d2
printf 'd1/a' > ./d1/a
printf 'd1/b' > ./d1/b
printf 'd2/a' > ./d2/a
printf 'd2/b' > ./d2/b
# Present only in root.
mkdir 'root'
printf 'root' > ./root/root
git add .
git commit -m 'root' --quiet

# Second commit only on master.
git rm --quiet -r ./root
mkdir 'master'
printf 'master' > ./master/master
git add .
git commit -m 'master commit' --quiet

# Second commit only on mybranch.
git checkout -b mybranch --quiet master~
git rm --quiet -r ./root
mkdir 'mybranch'
printf 'mybranch' > ./mybranch/mybranch
git add .
git commit -m 'mybranch commit' --quiet

echo "# List and identify all objects"
list-objects
echo

# Restore master.
git checkout --quiet master
cd ..

# Clone. Don't checkout for now, only .git/ dir.
git clone --depth 1 --quiet --no-checkout --filter=blob:none "file://$(pwd)/server_repo" local_repo
cd local_repo

# List missing objects from master.
echo "# Missing objects after --no-checkout"
git rev-list --all --quiet --objects --missing=print
echo

echo "# Git checkout fails without internet"
mv ../server_repo ../server_repo.off
! git checkout master
echo

echo "# Git checkout fetches the missing directory from internet"
mv ../server_repo.off ../server_repo
git checkout master -- d1/
echo

echo "# Missing objects after checking out d1"
git rev-list --all --quiet --objects --missing=print

GitHub上游

Git v2.19中的输出:

# List and identify all objects
c6fcdfaf2b1462f809aecdad83a186eeec00f9c1
fc5e97944480982cfc180a6d6634699921ee63ec
7251a83be9a03161acde7b71a8fda9be19f47128
62d67bce3c672fe2b9065f372726a11e57bade7e
b64bf435a3e54c5208a1b70b7bcb0fc627463a75 d1
308150e8fddde043f3dbbb8573abb6af1df96e63 d1/a
f70a17f51b7b30fec48a32e4f19ac15e261fd1a4 d1/b
84de03c312dc741d0f2a66df7b2f168d823e122a d2
0975df9b39e23c15f63db194df7f45c76528bccb d2/a
41484c13520fcbb6e7243a26fdb1fc9405c08520 d2/b
7d5230379e4652f1b1da7ed1e78e0b8253e03ba3 master
8b25206ff90e9432f6f1a8600f87a7bd695a24af master/master
ef29f15c9a7c5417944cc09711b6a9ee51b01d89
19f7a4ca4a038aff89d803f017f76d2b66063043 mybranch
1b671b190e293aa091239b8b5e8c149411d00523 mybranch/mybranch
c3760bb1a0ece87cdbaf9a563c77a45e30a4e30e
a0234da53ec608b54813b4271fbf00ba5318b99f root
93ca1422a8da0a9effc465eccbcb17e23015542d root/root
master commit SHA: fc5e97944480982cfc180a6d6634699921ee63ec
mybranch commit SHA: fc5e97944480982cfc180a6d6634699921ee63ec
040000 tree b64bf435a3e54c5208a1b70b7bcb0fc627463a75    d1
040000 tree 84de03c312dc741d0f2a66df7b2f168d823e122a    d2
040000 tree 7d5230379e4652f1b1da7ed1e78e0b8253e03ba3    master
040000 tree 19f7a4ca4a038aff89d803f017f76d2b66063043    mybranch
040000 tree a0234da53ec608b54813b4271fbf00ba5318b99f    root

# Missing objects after --no-checkout
?f70a17f51b7b30fec48a32e4f19ac15e261fd1a4
?8b25206ff90e9432f6f1a8600f87a7bd695a24af
?41484c13520fcbb6e7243a26fdb1fc9405c08520
?0975df9b39e23c15f63db194df7f45c76528bccb
?308150e8fddde043f3dbbb8573abb6af1df96e63

# Git checkout fails without internet
fatal: '/home/ciro/bak/git/test-git-web-interface/other-test-repos/partial-clone.tmp/server_repo' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

# Git checkout fetches the missing directory from internet
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1/1), 45 bytes | 45.00 KiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1/1), 45 bytes | 45.00 KiB/s, done.

# Missing objects after checking out d1
?8b25206ff90e9432f6f1a8600f87a7bd695a24af
?41484c13520fcbb6e7243a26fdb1fc9405c08520
?0975df9b39e23c15f63db194df7f45c76528bccb

结论:d1/缺少来自外部的所有斑点。

请注意,root/root并且mybranch/mybranch也丢失了,但是--depth 1将其从丢失的文件列表中隐藏了。如果删除--depth 1,则它们将显示在丢失文件的列表中。


1

正如您的编辑指出的那样,您可以使用两个单独的分支来存储两个单独的目录。这确实将它们都保留在同一个存储库中,但是您仍然不能拥有跨越两个目录树的提交。如果您有一个更改而另一个需要更改,则必须将它们作为两个单独的提交来执行,从而可能会导致两个目录的一对签出不同步。

如果要将这对目录视为一个单元,则可以使用“ wordpress / wp-content”作为存储库的根目录,并在顶层使用.gitignore文件来忽略除两个感兴趣的子目录以外的所有内容。这可能是目前最合理的解决方案。

据称稀疏检出已经有两年了,但是在git开发仓库中仍然没有迹象表明它们,也没有任何迹象表明必要的更改将在那里出现。我不会指望他们。


1

您无法检出存储库的单个目录,因为整个存储库由项目根目录中的单个.git文件夹处理,而不是由Subversion的无数.svn目录处理。

在单个存储库中处理插件的问题是,提交例如mytheme会增加myplugin的修订号,因此即使在Subversion中,也最好使用单独的存储库。

子项目的Subversion范例是svn:externals,它在某种程度上转换为git中的子模块(但是如果您之前使用过svn:externals,则可能不完全是)。


0

这里有启发。只是利用shell regexgit regex

git checkout commit_id */*.bat  # *.bat in 1-depth subdir exclude current dir, shell regex  
git checkout commit_id '*.bat'  # *.bat in all subdir include current dir, git regex

使用引号转义外壳正则表达式解释,并将通配符传递给git。

第一个不是递归的,只有1深度的文件subdir。但是第二个是递归的。

对于您的情况,以下可能就足够了。

git checkout master */*/wp-content/*/*
git checkout master '*/wp-content/*'

只需根据需要修改行即可。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.