执行“git export”(类似于“svn export”)操作?

2559

我一直在思考是否有一个好的“git导出”解决方案,可以创建一个没有.git仓库目录的树的副本。我至少知道三种方法:

  1. git clone后删除.git仓库目录。
  2. git checkout-index提到了这个功能,但是它以“只需将所需的树读入索引...”开始,我不太确定如何做。
  3. git-export是一个第三方脚本,基本上做了一个git clone到临时位置,然后rsync --exclude='.git'到最终目标。

这些解决方案都不是特别令我满意。与svn export最接近的可能是选项1,因为两者都需要首先将目标目录清空。但是选项2似乎更好,只要我能弄清楚将树读入索引意味着什么。


1
@rnrTom:看看Somov的回答。(tar归档中没有任何“压缩”内容)。 - etarion
34
git archive --format zip --output "output.zip" master -0 这个命令会生成一个未压缩的存档文件(-0 是未压缩的标志)。了解更多信息可以查看 http://git-scm.com/docs/git-archive。 - user456814
9
我同意@mrTom的观点,我认为存档是压缩还是未压缩不是主要问题。使用SVN,我可以直接从远程仓库“导出”一个250kB的子目录(否则可能会有200MB的大小,不包括修订版本),那么我只会通过网络下载传输250kB左右。使用git,必须在服务器上启用archive(所以我无法尝试)-从服务器进行“clone --depth 1”的操作仍然可能检索到大小为25 MB的仓库,其中.git子文件夹单独占用了15MB。因此,我的答案仍然是“不行”。 - sdaau
@mrTom,事实上答案是是的。请查看原帖中的回答 - 命令是git checkout-index - nocache
2
这里有一个简单而不错的方法:git archive -o latest.zip HEAD - Evgeni Sergeev
9
我已经将这个问题作为“git export”命令的手册使用多年了。 - orion elenzil
31个回答

2576
可能最简单的方法是使用 git archive。如果你只需要扩展的树,你可以这样做。
git archive master | tar -x -C /somewhere/else

大多数时候,当我需要从git中“导出”某些内容时,无论如何我都希望得到一个压缩的归档文件,所以我会这样做。
git archive master | bzip2 >source-tree.tar.bz2

ZIP压缩文件:
git archive --format zip --output /full/path/to/zipfile.zip master 

git help archive 了解更多细节,它非常灵活。


请注意,尽管存档中不包含.git目录,但它将包含其他隐藏的git特定文件,如.gitignore、.gitattributes等。如果您不希望它们出现在存档中,请确保在进行存档之前使用.export-ignore属性在.gitattributes文件中,并提交此更改。阅读更多...
注意:如果您有兴趣导出索引,请使用以下命令。
git checkout-index -a -f --prefix=/destination/path/

(更多细节请参见Greg的回答
这是一个在Linux上使用libchrony的真实世界示例:
mkdir $HOME/dev
cd $HOME/dev
pushd /tmp
git clone https://gitlab.com/chrony/libchrony.git
cd libchrony
BRANCH=$(git rev-parse --abbrev-ref HEAD)
git archive -o ../libchrony.zip --prefix="libchrony/" $BRANCH
popd
unzip /tmp/libchrony.zip

这些命令会生成一个zip文件,并将其解压到$HOME/dev/libchrony目录中。我们可以使用以下命令查看存档内容:
$ unzip -v /tmp/libchrony
Archive:  /tmp/libchrony.zip
e0a3807f770b56f6b0e9833254baa7c4fc13564b
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
       0  Stored        0   0% 2023-07-20 09:37 00000000  libchrony/
      49  Defl:N       47   4% 2023-07-20 09:37 37c3f2e2  libchrony/.gitignore
   26530  Defl:N     9350  65% 2023-07-20 09:37 5622583e  libchrony/COPYING
     961  Defl:N      467  51% 2023-07-20 09:37 da9221e3  libchrony/Makefile
     475  Defl:N      304  36% 2023-07-20 09:37 cae27f70  libchrony/README.adoc
    3313  Defl:N     1119  66% 2023-07-20 09:37 37eb110f  libchrony/chrony.h
    7673  Defl:N     2261  71% 2023-07-20 09:37 5d455a52  libchrony/client.c
    6190  Defl:N     2093  66% 2023-07-20 09:37 7ea9d81b  libchrony/example-reports.c
   16348  Defl:N     3855  76% 2023-07-20 09:37 e82f5fe3  libchrony/message.c
    2946  Defl:N     1099  63% 2023-07-20 09:37 945ee82b  libchrony/message.h
--------          -------  ---                            -------
   64485            20595  68%                            10 files

209
ZIP归档文件:git archive --format zip --output /full/path master - Vadim
227
请注意,归档文件不会包含.git目录,但会包含其他类似.gitignore、.gitattributes等隐藏的git专用文件。因此,如果您不想将它们包含在内,请确保在.gitattributes文件中使用export-ignore属性,并在进行归档之前提交该文件。请参阅http://feeding.cloud.geek.nz/2010/02/excluding-files-from-git-archive.html。 - mj1531
58
跟进Streams的提示:你可以在命令中添加一个“--prefix=something/”字符串来控制压缩包中目录的名称。例如,如果你使用 git archive --format zip --output /path/to/file.zip --prefix=newdir/ master,输出文件将被称为“file.zip”,但是当你解压它时,顶层目录将变成“newdir”。(如果你省略了--prefix属性,则顶层目录将会是“file”。) - Alan W. Smith
93
最简单的方法是:git archive -o latest.zip HEAD。它会创建一个 Zip 压缩包,其中包含当前分支上最新提交的内容。请注意,输出格式是由输出文件的扩展名推断出来的。 - nacho4d
37
不支持Git子模块 :( - umpirsky
显示剩余19条评论

347

我发现了选项2的含义。从仓库可以进行以下操作:

git checkout-index -a -f --prefix=/destination/path/

路径末尾的斜线很重要,否则文件会在目标文件夹中以"path"为前缀的形式出现。

由于在正常情况下索引包含仓库的内容,因此“将所需的树读入索引”并不需要特殊操作。它已经在那里了。

-a标志是必需的,以检出索引中的所有文件(我不确定在这种情况下省略此标志意味着什么,因为它不会执行我想要的操作)。-f标志强制覆盖输出中的任何现有文件,而这个命令通常不会这样做。

这似乎就是我正在寻找的“git导出”。


77
不要忘记在结尾处加上斜杠,否则你将无法达到预期效果 ;) - conny
7
@conny:看了你的评论,但忘记了,在运行命令时没有加上斜杠。提示:要遵循conny的建议 -.- - Markus Hedlund
36
同意Conny的建议。另外,不要尝试创建“/dest/”文件夹,因为这会在你的工作目录中创建一个称为“”的目录,而非你真正想要的目录。当你毫无思考地键入rm -rf ~时,猜猜会发生什么。 - Kyle Heironimus
5
如果你在前缀路径周围使用引号告诉 shell 不执行 tilde expansion,那么 @KyleHeironimus 关于使用 '/dest/' 的警告是正确的。这将在工作目录中创建一个名为 `(不是 ''!)的目录。关于 git checkout-index没有什么特别之处:同样适用于mkdir '/dest'`(*不要这样做!*)。避免需要引用的文件名(例如其中包含空格)是另一个很好的理由。 - Matt Wallis
1
@takeshin 这并不完全与 git archive HEAD 相同:git checkout-index ... 会复制所有文件 _从索引中_,而 archive 会归档文件 _从本地存储库_。如果索引为空,它们将巧合地执行相同的操作。 - Alberto
显示剩余12条评论

270

git archive 命令也可以用于远程代码库。

git archive --format=tar \
--remote=ssh://remote_server/remote_repository master | tar -xf -

要导出仓库内特定路径,作为git的最后一个参数添加你想要的所有路径即可,例如:

git archive --format=tar \
--remote=ssh://remote_server/remote_repository master path1/ path2/ | tar -xv

6
这个选项是我最喜欢的。它还有一个额外的好处,可以在裸库上使用。 - innaM
7
改进版为: git archive --format=tar --prefix=PROJECT_NAME/ --remote=USER@SERVER:PROJECT_NAME.git master | tar -xf -(确保您的归档文件在一个文件夹中) - Nick
12
注意:服务器必须启用此功能。 - Jakub Narębski
14
我尝试了以下命令:git archive --format=zip --output foo.zip --remote=https://github.com/xxx.git master,但是出现了致命错误:Operation not supported by protocol. Unexpected end of command stream。 - andyf
10
根据GitHub的文档(https://developer.github.com/v3/repos/contents/#get-archive-link),使用以下命令进行操作:`curl -L https://api.github.com/repos/VENDOR/PROJECT/tarball | tar xzf -`。 - bishop
显示剩余8条评论

79

enter image description here

如果代码库托管在GitHub上,这是一个特殊情况的解答。

只需使用svn export命令即可。

据我所知,GitHub不允许使用archive --remote命令。虽然GitHub与svn兼容,并且所有git代码库都可以通过svn访问,因此您可以像通常使用svn export一样对GitHub url进行一些调整。

例如,要导出整个代码库,请注意URL中的trunk替换了master(或者替换为项目HEAD分支设置为):

svn export https://github.com/username/repo-name/trunk/

您甚至可以导出单个文件或特定路径或文件夹:

svn export https://github.com/username/repo-name/trunk/src/lib/folder

使用jQuery JavaScript库的示例

HEAD分支或master分支将可使用trunk

svn ls https://github.com/jquery/jquery/trunk

HEAD分支将在/branches/下可访问:
svn ls https://github.com/jquery/jquery/branches/2.1-stable

在同样的方式下,/tags/ 下的所有标签
svn ls https://github.com/jquery/jquery/tags/2.1.3

2
只要使用git协议,git archive在GitHub上运行良好。只需将URL中的https://替换为git://即可。我不知道为什么GitHub不宣传这个隐藏功能。 - Neil Mayhew
1
@NeilMayhew,对我来说不起作用,我得到了“fatal: The remote end hung up unexpectedly”的错误。在两个不同的服务器上尝试了jQuery github存储库。 - Anthony Hatzopoulos
1
你说得对,我忘了我正在使用git config url.<base>.insteadOf来缓存远程仓库。实际上,我在使用file:// URL。我怀疑git archive永远无法使用git:// URL工作,因为它需要能够在远程端运行git-upload-archive。使用ssh协议应该是可能的,除了github不允许它(“Invalid command: 'git-upload-archive'”)。 - Neil Mayhew
有没有一种使用本地服务器工具的方式,就像 GitHub 一样的行为,如果我想在内部托管的 git 存储库上执行它? - kriss
3
点赞 -- Git 没有这个功能,我们不得不使用 svn,这完全是不可思议的。 - Jason S
显示剩余3条评论

45

来自Git手册:

使用git-checkout-index导出整个树

前缀功能基本上使得将git-checkout-index用作“导出为树”函数变得轻而易举。只需将所需的树读入索引,然后执行以下操作:

$ git checkout-index --prefix=git-export-dir/ -a


23
我会尽力进行翻译:我觉得困惑在于“将所需的树读入索引”这个短语。 - davetron5000
4
如果您想在分支bar中导出目录foo,则应执行以下操作: git read-tree bar:foo 然后执行 git checkout-index --prefix=export_dir/ -a。之后,您可能需要执行 git update-index master - Pascal Rosin
3
@JohnWeldon 这需要你先克隆repo吗?如果是这样的话,我就不会接受它,因为“svn export”子目录的整个意义是直接获取该子目录的副本;如果有人有一个1GB的Git repo,而我只想要一个10kB的子目录,那就要求我克隆整个repo太荒谬了。 - Jason S
6
我会尽力进行翻译,请问原文中的“desired tree”是指什么?【如果无法确认 "desired tree" 的具体含义】抱歉,我无法确认“desired tree”的具体含义,因此无法准确地翻译该句话。请提供更多上下文或解释以便我能够更好地帮助您。【如果可以确认 "desired tree" 的具体含义】我支持@davetron5000的评论,即“将所需的树读入索引”,但我不知道它的意思。 - Jason S

40

我已经写了一个简单的包装器,围绕着git-checkout-index,你可以像这样使用:

git export ~/the/destination/dir
如果目标目录已经存在,您需要添加-f--force。 安装很简单,只需在您的PATH中的某个位置放置脚本,并确保它是可执行的。 git-export的Github仓库

15
这个包装器不是跨平台的,它依赖于 /bin/sh。所以如果你在 Windows 上,这个解决方案可能就不适用于你。 - shovavnik
20
这个脚本总共有57行,其中包括文档、空格、设置、参数解析,只有一行代码实际上执行了操作。 - Vladimir Panteleev

36

看起来这个问题在Git中比SVN少一些。Git只在仓库根目录中放置一个.git文件夹,而SVN在每个子目录中都放置了一个.svn文件夹。因此,“svn export”避免了递归命令行魔法,而对于Git来说递归并不是必须的。


27
从 SVN 1.7 开始,只有一个 .svn 文件夹:https://subversion.apache.org/docs/release-notes/1.7.html#single-db - kostmo
这不会清除svn export删除的任何其他构建文件。所以这绝对不是答案。 - ygoe

30

等同于

svn export . otherpath

一个现有仓库的内部

git archive branchname | (cd otherpath; tar x)

的等效于

svn export url otherpath

是什么意思?

git archive --remote=url branchname | (cd otherpath; tar x)

1
谢谢,这正是我所缺少的...另外,要检查导出文件的时间戳(它们不会像文件上那样被保留),请使用 git archive --format=tar --prefix=junk/ HEAD | (tar -t -v --full-time -f -) ... 然而,带有时间戳的归档并不是很容易,因此我在下面发布了一个示例 - sdaau
2
你可以使用 tar 的 C 选项而不是子 shell,像这样:git archive branchname | tar xC otherpath - James Moore
请注意,tar 的 C 选项仅适用于 GNU Tar。 - aredridel

25

如果你没有用.gitattributesexport-ignore来排除文件,则尝试使用git checkout

mkdir /path/to/checkout/
git --git-dir=/path/to/repo/.git --work-tree=/path/to/checkout/ checkout -f -q

-f
当从索引检出路径时,不要在未合并的条目上失败;而是忽略未合并的条目。

-q
避免冗长

此外,您可以获取任何分支、标签或特定提交版本,就像在SVN中添加SHA1一样(在Git中,SHA1相当于SVN中的修订号)。

mkdir /path/to/checkout/
git --git-dir=/path/to/repo/.git --work-tree=/path/to/checkout/ checkout 2ef2e1f2de5f3d4f5e87df7d8 -f -q -- ./

/path/to/checkout/ 目录必须为空,Git 不会删除任何文件,但会覆盖同名文件而不发出任何警告。

更新: 为避免在使用标签、分支或 SHA1 导出的 checkout 中出现问题,或在保留工作目录时出现问题,需要在结尾处添加 -- ./

双破折号 -- 告诉 Git 破折号后面的一切都是路径或文件,并且在这种情况下还告诉 git checkout 不要改变 HEAD

示例:

此命令将仅获取 libs 目录以及该提交中的 readme.txt 文件。

git --git-dir=/path/to/repo/.git --work-tree=/path/to/checkout/ checkout fef2e1f2de5f3d4f5e87df7d8 -f -q -- ./libs ./docs/readme.txt

这将在HEAD^2的前两个提交处创建(覆盖)my_file_2_behind_HEAD.txt

git --git-dir=/path/to/repo/.git --work-tree=/path/to/checkout/ checkout HEAD^2 -f -q -- ./my_file_2_behind_HEAD.txt

获取另一个分支的导出

git --git-dir=/path/to/repo/.git --work-tree=/path/to/checkout/ checkout myotherbranch -f -q -- ./

请注意,./ 是相对于代码库根目录的。


1
请注意,SHA1检出将在存储库中创建“behead”问题。 - user5286776117878
实际上,@ITGabs,这并没有下载“.git”文件夹。因此,下载的文件夹不是一个git存储库,因此它在技术上并没有被“斩首”。 - Fabio Marreco
@FabioMarreco 割头问题出现在代码库中而非导出/下载的文件中,我正在更新答案以提供更多细节。 - user5286776117878
3
这对我非常有效。但一开始我收到了“Not a git repository”的错误信息。然后我发现“/path/to/repo/”必须指向.git文件夹。所以应该这样写:--git-dir=/path/to/repo/.git - philburk
如果我想导出一个早期的提交怎么办?首先,使用git checkout <sha1>切换到早期的提交,然后运行命令git --git-dir=/path/to/repo/ --work-tree=/path/to/checkout/ checkout -f -q - horse
显示剩余2条评论

22

我广泛地使用git子模块。 这是我的解决方案:

rsync -a ./FROM/ ./TO --exclude='.*'

1
这样做会不会漏掉以点开头的文件,比如 .htaccess - Greg Hewgill
8
好的解决方案,我会将--exclude='.'更改为--exclude='.git',以达到排除.git文件夹的目的。 - schmunk
19
如果你打算采取这种策略,请使用“--exclude-vcs”。 - plod
./FROM/可以是远程仓库吗? - Resist Design
我会将 --exclude='.' 替换为 --exclude='.git'。 - haknick
2
顺便提一下,我的 rsync 副本将参数列为 --cvs-exclude。此外,它仍会复制 .gitattributes.gitignore - Ryan Ransford

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