做一个“git导出”(如“svn导出”)?

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

  1. git clone,然后删除.git存储库目录。
  2. #0暗示了这个功能,但以“只需将所需的树读入索引…”开头,我不完全确定如何做到这一点。
  3. #0是一个第三方脚本,它基本上将git clone执行到临时位置,然后将rsync --exclude='.git'执行到最终目的地。

这些解决方案中没有一个让我感到满意。最接近svn export的可能是选项1,因为两者都要求目标目录首先为空。但选项2似乎更好,假设我能弄清楚将树读取到索引中意味着什么。

722333 次浏览

git手册

使用git-check out-index“导出整个树”

前缀功能基本上使使用git-check out-index作为“导出为树”函数变得微不足道。只需将所需的树读取到索引中,然后执行:

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

我发现了选项2的含义。从存储库中,您可以执行:

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

路径末尾的斜杠很重要,否则会导致文件 /destination前缀为“path”。

由于在正常情况下索引包含存储库的内容,因此“将所需的树读入索引”没有什么特别的事情要做。它已经在那里了。

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

这似乎是我一直在寻找的那种“git导出”。

我的偏好实际上是在您的Makefile(或其他构建系统)中拥有一个dist目标,该目标导出您的代码的可分发存档(.tar.bz2、. zip、. jar或任何合适的文件)。如果您碰巧使用GNU自动工具或Perl的MakeMaker系统,我认为这是自动存在的。如果没有,我强烈建议添加它。

ETA(2012-09-06):哇,强烈的反对票。我仍然相信用构建工具而不是源代码控制工具构建您的发行版更好。我相信用构建工具构建工件。在我目前的工作中,我们的主要产品是用ant目标构建的。我们正在切换源代码控制系统,这个ant目标的存在意味着迁移的麻烦减少了一个。

可能最简单的实现方法是使用#0。如果你真的只需要扩展树,你可以这样做。

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

#0了解更多细节,它非常灵活。


请注意,即使存档不包含. git目录,它也会包含其他隐藏的特定于git的文件,如. gitignore、. git属性等。如果您不想在存档中使用它们,请确保您在. git属性文件中使用export-忽略属性,并在执行存档之前提交此属性。阅读更多…


注意:如果您有兴趣导出索引,则命令是

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

(更多细节见格雷格的回答

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

git export ~/the/destination/dir

如果目标目录已经存在,则需要添加-f--force

安装很简单;只需将脚本放在PATH中的某个地方,并确保它是可执行的。

#0的github存储库

git archive也适用于远程存储库。

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

要在repo中导出特定路径,请添加尽可能多的路径作为git的最后一个参数,例如:

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

看起来Git的问题比SVN的问题要小。Git只在存储库根目录中放置一个. git文件夹,而SVN在每个子目录中放置一个. svn文件夹。所以“svn导出”避免了递归命令行魔法,而Git递归不是必要的。

我需要这个用于部署脚本,但我不能使用上述任何一种方法。相反,我想出了一个不同的解决方案:

#!/bin/sh[ $# -eq 2 ] || echo "USAGE $0 REPOSITORY DESTINATION" && exit 1REPOSITORY=$1DESTINATION=$2TMPNAME="/tmp/$(basename $REPOSITORY).$$"git clone $REPOSITORY $TMPNAMErm -rf $TMPNAME/.gitmkdir -p $DESTINATIONcp -r $TMPNAME/* $DESTINATIONrm -rf $TMPNAME

使用简单的方法,这是.bash_profile的一个函数,它直接在当前位置解压缩存档,首先配置您常用的[url: path]。注意:使用此函数,您可以避免克隆操作,它直接从远程存储库获取。

gitss() {URL=[url:path]
TMPFILE="`/bin/tempfile`"if [ "$1" = "" ]; thenecho -e "Use: gitss repo [tree/commit]\n"returnfiif [ "$2" = "" ]; thenTREEISH="HEAD"elseTREEISH="$2"fiecho "Getting $1/$TREEISH..."git archive --format=zip --remote=$URL/$1 $TREEISH > $TMPFILE && unzip $TMPFILE && echo -e "\nDone\n"rm $TMPFILE}

. gitconfig的别名,需要相同的配置(小心在. git项目中执行命令,它总是跳转到之前的基本目录正如这里所说,直到这个固定我个人更喜欢这个函数

ss = !env GIT_TMPFILE="`/bin/tempfile`" sh -c 'git archive --format=zip --remote=[url:path]/$1 $2 \ > $GIT_TMPFILE && unzip $GIT_TMPFILE && rm $GIT_TMPFILE' -

我只是想指出在这种情况下

  1. 导出存储库的子文件夹(这就是我使用SVN导出功能的方式)
  2. 可以将该文件夹中的所有内容复制到部署目标
  3. 而且由于您已经拥有整个存储库的副本。

然后你可以使用cp foo [destination]而不是上面提到的git-archive master foo | -x -C [destination]

这将复制所有内容,减去. dot文件。我用它来将git克隆的项目导出到我的Web应用程序的git存储库中,而不包含. git内容。

cp-R./path-to-git-repo /path/to/destination/

简单的旧bash工作只是伟大的:)

我广泛使用git子模块。这是我的工作:

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

git导出的bash实现。

我已经根据它们自己的函数对.空文件的创建和删除过程进行了分段,目的是在“git存档”实现中重用它们(稍后将发布)。

我还将“. git属性”文件添加到进程中,以便从目标导出文件夹中删除不需要的文件。包括对流程的冗长,同时使“git-port”功能更高效。

EMPTY_FILE=".空";

function create_empty () {## Processing path (target-dir):TRG_PATH="${1}";## Component(s):EXCLUDE_DIR=".git";echo -en "\nAdding '${EMPTY_FILE}' files to empty folder(s): ...";find ${TRG_PATH} -not -path "*/${EXCLUDE_DIR}/*" -type d -empty -exec touch {}/${EMPTY_FILE} \;#echo "done.";## Purging SRC/TRG_DIRs variable(s):unset TRG_PATH EMPTY_FILE EXCLUDE_DIR;return 0;}
declare -a GIT_EXCLUDE;function load_exclude () {SRC_PATH="${1}";ITEMS=0; while read LINE; do#      echo -e "Line [${ITEMS}]: '${LINE%%\ *}'";GIT_EXCLUDE[((ITEMS++))]=${LINE%%\ *};done < ${SRC_PATH}/.gitattributes;GIT_EXCLUDE[${ITEMS}]="${EMPTY_FILE}";## Purging variable(s):unset SRC_PATH ITEMS;return 0;}
function purge_empty () {## Processing path (Source/Target-dir):SRC_PATH="${1}";TRG_PATH="${2}";echo -e "\nPurging Git-Specific component(s): ... ";find ${SRC_PATH} -type f -name ${EMPTY_FILE} -exec /bin/rm '{}' \;for xRULE in ${GIT_EXCLUDE[@]}; doecho -en "    '${TRG_PATH}/{${xRULE}}' files ... ";find ${TRG_PATH} -type f -name "${xRULE}" -exec /bin/rm -rf '{}' \;echo "done.'";done;echo -e "done.\n"## Purging SRC/TRG_PATHs variable(s):unset SRC_PATH; unset TRG_PATH;return 0;}
function git-export () {TRG_DIR="${1}"; SRC_DIR="${2}";if [ -z "${SRC_DIR}" ]; then SRC_DIR="${PWD}"; fiload_exclude "${SRC_DIR}";## Dynamically added '.empty' files to the Git-Structure:create_empty "${SRC_DIR}";GIT_COMMIT="Including '${EMPTY_FILE}' files into Git-Index container."; #echo -e "\n${GIT_COMMIT}";git add .; git commit --quiet --all --verbose --message "${GIT_COMMIT}";if [ "${?}" -eq 0 ]; then echo " done."; fi/bin/rm -rf ${TRG_DIR} && mkdir -p "${TRG_DIR}";echo -en "\nChecking-Out Index component(s): ... ";git checkout-index --prefix=${TRG_DIR}/ -q -f -a## Reset: --mixed = reset HEAD and index:if [ "${?}" -eq 0 ]; thenecho "done."; echo -en "Resetting HEAD and Index: ... ";git reset --soft HEAD^;if [ "${?}" -eq 0 ]; thenecho "done.";## Purging Git-specific components and '.empty' files from Target-Dir:purge_empty "${SRC_DIR}" "${TRG_DIR}"else echo "failed.";fi## Archiving exported-content:echo -en "Archiving Checked-Out component(s): ... ";if [ -f "${TRG_DIR}.tgz" ]; then /bin/rm ${TRG_DIR}.tgz; ficd ${TRG_DIR} && tar -czf ${TRG_DIR}.tgz ./; cd ${SRC_DIR}echo "done.";## Listing *.tgz file attributes:## Warning: Un-TAR this file to a specific directory:ls -al ${TRG_DIR}.tgzelse echo "failed.";fi## Purgin all references to Un-Staged File(s):git reset HEAD;## Purging SRC/TRG_DIRs variable(s):unset SRC_DIR; unset TRG_DIR;echo "";return 0;}

输出:

$git导出 /tmp/rel-1.0.0

将“.空”文件添加到空文件夹:…完成。

签出索引组件:…完成。

重置头部和索引:…完成。

清除Git特定组件:…

'/tmp/rel-1.0.0/{. Buildpath}'文件…完成。'

'/tmp/rel-1.0.0/{. project}'文件…完成。'

'/tmp/rel-1.0.0/{. gitignore}'文件…完成。'

'/tmp/rel-1.0.0/{. git}'文件…完成。'

'/tmp/rel-1.0.0/{. git属性}'文件…完成。'

'/tmp/rel-1.0.0/{*. mno}'文件…完成。'

'/tmp/rel-1.0.0/{*~}' 文件…完成。'

'/tmp/rel-1.0.0/{.*~}' 文件…完成。'

'/tmp/rel-1.0.0/{*. swp}'文件…完成。'

'/tmp/rel-1.0.0/{*. swo}'文件…完成。'

'/tmp/rel-1.0.0/{.DS_Store}'文件…完成。'

'/tmp/rel-1.0.0/{.设置}'文件…完成。'

'/tmp/rel-1.0.0/{.空}'文件…完成。'

完成。

归档签出组件:…完成。

-rw-r--r--1管理轮25445901 11月3日12:57 /tmp/rel-1.0.0.tgz

我现在已经将“git归档”功能合并到一个使用“create_empty”功能和其他功能的进程中。

function git-archive () {PREFIX="${1}"; ## sudo mkdir -p ${PREFIX}REPO_PATH="`echo "${2}"|awk -F: '{print $1}'`";RELEASE="`echo "${2}"|awk -F: '{print $2}'`";USER_PATH="${PWD}";echo "$PREFIX $REPO_PATH $RELEASE $USER_PATH";## Dynamically added '.empty' files to the Git-Structure:cd "${REPO_PATH}"; populate_empty .; echo -en "\n";#    git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0# e.g.: git-archive /var/www/htdocs /repos/domain.name/website:rel-1.0.0 --explodeOUTPUT_FILE="${USER_PATH}/${RELEASE}.tar.gz";git archive --verbose --prefix=${PREFIX}/ -o ${OUTPUT_FILE} ${RELEASE}cd "${USER_PATH}";if [[ "${3}" =~ [--explode] ]]; thenif [ -d "./${RELEASE}" ]; then /bin/rm -rf "./${RELEASE}"; fimkdir -p ./${RELEASE}; tar -xzf "${OUTPUT_FILE}" -C ./${RELEASE}fi## Purging SRC/TRG_DIRs variable(s):unset PREFIX REPO_PATH RELEASE USER_PATH OUTPUT_FILE;return 0;}

在寻找导出git存储库的方法时,我经常点击此页面。我对这个问题的回答考虑了svn导出与git相比在设计上具有的三个属性,因为svn遵循集中式存储库方法:

  • 它通过不导出所有版本来最小化远程存储库位置的流量

  • 它不包括导出目录中的元信息

  • 使用svn导出某个分支是通过指定适当的路径来完成的

      git clone --depth 1 --branch main git://git.somewhere destination_pathrm -rf destination_path/.git

在构建某个版本时,克隆一个稳定的分支很有用,例如--branch stable--branch release/0.9

相当于

svn export . otherpath

在现有的repo中

git archive branchname | (cd otherpath; tar x)

相当于

svn export url otherpath

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

如果你想要一些与子模块一起工作的东西,这可能值得一试。

备注:

  • MASTER_DIR=你的子模块也签出
  • DEST_DIR=此导出将结束的位置
  • 如果你有rsync,我认为你可以做同样的事情,甚至更少的球疼痛。

假设:

  • 您需要从MASTER_DIR的父目录(即从MASTER_DIRcd…)
  • DEST_DIR被假定为已经创建。如果您愿意,这很容易修改以包括DEST_DIR的创建

cdMASTER_DIR&&tar-zcvf… /DEST_DIR/export.tar.gz.&&cd… /DEST_DIR/&&tar xvfzexport.tar.gz&&rmexport.tar.gz

如果你也需要子模块,这应该可以做到:https://github.com/meitar/git-archive-all.sh/wiki

您可以在任何提交时将远程存储库存档为zip文件。

git archive --format=zip --output=archive.zip --remote=USERNAME@HOSTNAME:PROJECTNAME.git HASHOFGITCOMMIT

像克隆一样简单,然后删除. git文件夹:

git cloneurl_of_your_repopath_to_export&&rm-rfpath_to_export

输入图片描述

如果存储库托管在GitHub上,则为特例答案。

只需使用svn export

据我所知,Github不允许archive --remote。尽管GitHub是svn兼容,并且他们确实可以访问所有git repossvn,所以你可以像往常一样使用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分支或大师分支将使用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

如果您不排除.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的结帐导出时保持工作存储库完好无损,您需要在末尾添加-- ./

双破折号--告诉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 -- ./

请注意,./是相对于存储库的根的

这将把提交范围(C到G)中的文件复制到tar文件中。注意:这只会得到提交的文件。不是整个存储库。从这里略有修改

提交历史示例

A-->B-->C-->D-->E-->F-->G-->H-->I

git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT C~..G | xargs tar -rf myTarFile.tar

git-diff-tree手册页

-r-->递归到子树

--no-ink-id-->git diff-tree在适用时输出包含提交ID的行。此标志抑制了提交ID输出。

--name-Only-->仅显示已更改文件的名称。

--diff-filter=ACMRT-->仅选择这些文件。在这里查看完整的文件列表

C… G-->此提交范围内的文件

C~-->包括来自Commit C的文件。

|xargs tar-rf myTarFile-->输出到tar

我的. bashrc文件中有以下实用函数:它在git存储库中创建当前分支的存档。

function garchive(){if [[ "x$1" == "x-h" || "x$1" == "x" ]]; thencat <<EOFUsage: garchive <archive-name>create zip archive of the current branch into <archive-name>EOFelselocal oname=$1set -xlocal bname=$(git branch | grep -F "*" | sed -e 's#^*##')git archive --format zip --output ${oname} ${bname}set +xfi}

我认为@陈志立的帖子是最接近的,但还有更多-所以我将在这里添加这个;问题是,在svn中,如果您在repo的子文件夹中,并且您这样做:

/media/disk/repo_svn/subdir$ svn export . /media/disk2/repo_svn_B/subdir

然后svn将导出所有受版本控制的文件(它们也可以是新添加的;或修改状态)-如果您在该目录中有其他“垃圾”(我在这里不计算.svn子文件夹,但像.o文件这样的可见内容),它将没有被导出;只有那些由SVN存储库注册的文件才会被导出。对我来说,一件好事是此导出还包括尚未提交没有的本地更改的文件;另一件好事是导出文件的时间戳与原始文件相同。或者,正如svn help export所说:

  1. 从指定的工作副本导出干净的目录树PATH1,在修订REV,如果它是给定的,否则在工作,到PATH2…如果未指定REV,则所有本地更改将被保留。不受版本控制的文件将无法复制。

要了解git不会保留时间戳,请比较这些命令的输出(在您选择的git存储库的子文件夹中):

/media/disk/git_svn/subdir$ ls -la .

.和:

/media/disk/git_svn/subdir$ git archive --format=tar --prefix=junk/ HEAD | (tar -t -v --full-time -f -)

…我,无论如何,注意到git archive导致存档文件的所有时间戳都是相同的!git help archive说:

给定树ID与给定提交ID或标记ID时,git存档的行为不同。在第一种情况下当前时间用作存档中每个文件的修改时间。在后一种情况下,记录的提交时间在引用的提交对象中改为使用。

…但显然这两种情况都设置了“每个文件的修改时间”;从而没有保留了这些文件的实际时间戳!

因此,为了保留时间戳,这里有一个bash脚本,它实际上是一个“单行”,尽管有点复杂-所以下面它以多行形式发布:

/media/disk/git_svn/subdir$ git archive --format=tar master | (tar tf -) | (\DEST="/media/diskC/tmp/subdirB"; \CWD="$PWD"; \while read line; do \DN=$(dirname "$line"); BN=$(basename "$line"); \SRD="$CWD"; TGD="$DEST"; \if [ "$DN" != "." ]; then \SRD="$SRD/$DN" ; TGD="$TGD/$DN" ; \if [ ! -d "$TGD" ] ; then \CMD="mkdir \"$TGD\"; touch -r \"$SRD\" \"$TGD\""; \echo "$CMD"; \eval "$CMD"; \fi; \fi; \CMD="cp -a \"$SRD/$BN\" \"$TGD/\""; \echo "$CMD"; \eval "$CMD"; \done \)

请注意,假设您正在导出“当前”目录(上面的/media/disk/git_svn/subdir)中的内容-您要导出到的目标位置有点不方便,但它位于DEST环境变量中。请注意,使用此脚本;在运行上述脚本之前,您必须自己手动创建DEST目录。

运行脚本后,您应该能够比较:

ls -la /media/disk/git_svn/subdirls -la /media/diskC/tmp/subdirB   # DEST

…并希望看到相同的时间戳(对于那些受版本控制的文件)。

希望对大家有所帮助
干杯!

对于GitHub用户,git archive --remote方法不能直接工作,如导出URL是短暂的。您必须向GitHub询问URL,然后下载该URL。curl让这变得容易:

curl -L https://api.github.com/repos/VENDOR/PROJECT/tarball | tar xzf -

这将为您提供本地目录中导出的代码。示例:

$ curl -L https://api.github.com/repos/jpic/bashworks/tarball | tar xzf -$ ls jpic-bashworks-34f4441/break  conf  docs  hack  LICENSE  mlog  module  mpd  mtests  os  README.rst  remote  todo  vcs  vps  wepcrack

编辑
如果您想将代码放入特定的现有目录(而不是来自github的随机目录):

curl -L https://api.github.com/repos/VENDOR/PROJECT/tarball | \tar xzC /path/you/want --strip 1

是的,这个是一个干净整洁的命令,用于归档您的代码,而不会在归档中包含任何git,并且可以很好地传递而不用担心任何git提交历史记录。

git archive --format zip --output /full/path/to/zipfile.zip master

到目前为止,我见过的最简单的方法(也适用于Windows)是git bundle

git bundle create /some/bundle/path.bundle --all

查看更多详细信息:如何通过usb驱动器将我的git存储库从windows机器复制到linux机器?

选项1听起来不太有效。如果客户端中没有空间进行克隆并然后删除.git文件夹怎么办?

今天我发现自己正在尝试这样做,其中客户端是一个几乎没有空间的树莓派。此外,我还想从存储库中排除一些重文件夹。

选项2和这里的其他答案在这种情况下没有帮助。git archive也没有(因为需要提交.gitattributes文件,并且我不想将此排除保存在存储库中)。

在这里,我分享我的解决方案,类似于选项3,但不需要git clone

tmp=`mktemp`git ls-tree --name-only -r HEAD > $tmprsync -avz --files-from=$tmp --exclude='fonts/*' . raspberry:

rsync行更改为compress的等效行也将作为git archive工作,但具有某种排除选项(如所问这里)。

如果您在要创建导出的机器上有存储库的本地副本,我有另一个解决方案可以正常工作。在这种情况下,移动到此存储库目录,并输入以下命令:

GIT_WORK_TREE=outputdirectory git checkout -f

如果您使用git存储库管理网站并希望在/var/www/中签出干净版本,这将特别有用。在这种情况下,在.git/hooks/post-receive脚本中添加此命令(在裸存储库上的hooks/post-receive,在这种情况下更合适)

根据我对这个问题的理解,它更多的是从服务器上下载特定的状态,没有历史记录,没有其他分支的数据,而不是从本地存储库中提取状态(就像这里的许多浏览器一样)。

这可以这样做:

git clone -b someBranch --depth 1 --single-branch git://somewhere.com/repo.git \&& rm -rf repo/.git/
  • --single-branch从Git 1.7.10(2012年4月)开始可用。
  • --depth是(是?)据报道错误,但对于导出的情况,所提到的问题应该无关紧要。

添加前缀(例如目录名)时将git导出到zip存档:

git archive master --prefix=directoryWithinZip/  --format=zip -o out.zip