【问题标题】:Export only modified and added files with folder structure in Git在 Git 中仅导出具有文件夹结构的修改和添加的文件
【发布时间】:2011-05-31 06:57:18
【问题描述】:

我想获取特定提交中修改和添加的文件的列表,以便我可以导出它们并生成具有文件结构的包。

这个想法是获取包并在服务器上提取它。由于许多原因,我无法创建一个挂钩来自动提取 repo,而我必须保持服务器更新的最简单方法是生成这个包。

【问题讨论】:

    标签: git git-diff


    【解决方案1】:

    我也遇到了同样的问题。 @NicolasDermine 的解决方案不起作用,因为在比较的提交之间更改了太多文件。我收到一个错误,即 shell 参数太长。

    因此,我添加了一个 python 实现。这可以通过pip install gitzip 安装,然后使用

    python -m gitzip export.zip <commit id> <commit id>
    

    在存储库根目录中创建一个 export.zip 文件,其中包含保留目录结构的所有更改文件。

    也许有人也需要它,所以我想在这里分享它。

    免责声明:我是gitzip 模块的作者。

    【讨论】:

    • 完美。它解决了我的问题。
    【解决方案2】:

    以下命令对我有用。

    如果您想要上次提交更改的文件的差异:

    git archive -o update.zip HEAD $(git diff --name-only HEAD HEAD^)
    

    或者如果您想要两个特定提交之间的差异:

    git archive -o update.zip sha1 $(git diff --name-only sha1 sha2)
    

    或者如果您有未提交的文件,请记住 git 方式是提交所有内容,分支很便宜:

    git stash
    git checkout -b feature/new-feature
    git stash apply
    git add --all
    git commit -m 'commit message here'
    git archive -o update.zip HEAD $(git diff --name-only HEAD HEAD^)
    

    【讨论】:

      【解决方案3】:
      git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT <i>$commit_id</i>
      1. git diff-tree -r <i>$commit_id</i>:

        将给定的提交与它的父级(包括所有子目录,而不仅仅是顶级目录)进行比较。

      2. --no-commit-id --name-only:

        不输出提交 SHA1。仅输出受影响文件的名称而不是完整的差异。

      3. --diff-filter=ACMRT:

        仅显示在此提交中添加、复制、修改、重命名或类型更改的文件(例如文件 → 符号链接)。这会忽略已删除的文件。


      评论更新:
      根据问题上下文和下面的 cmets,使用以下命令,您可以获得 ACMRT 文件作为 .tar 文件及其文件夹结构。

      git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $commit_id | tar -czf file.tgz -T -
      

      【讨论】:

      • 现在我只需要找到一种方法将这个命令的结果与 tar 粘合!谢谢!
      • @Taverneiro 您可以通过管道将其发送到tar command given in this answer,例如。对于 tgz 文件:git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $commit_id | tar -czf file.tgz -T -
      【解决方案4】:

      这对我适用于 Unix 和 Windows:

      git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT __1__.. | xargs cp --parents -t __2__
      

      命令中有两个占位符。您需要根据自己的目的更换它们:

      • __1__:在您要导出的所有提交之前替换为 提交的提交 ID(例如 997cc7b6 - 不要忘记保留那个双点在提交 id 之后 - 这意味着“涉及比此提交更新的所有提交”)

      • __2__:替换为您要导出文件的现有路径(例如 ../export_path/)

      因此,您将在文件夹结构中获取文件(没有 zips/tars...),因为有人可能习惯于在 svn 存储库中使用 tortoise svn exports

      例如,当您想要手动部署少数最后一次提交的添加/修改文件时,这非常有用。因此,您可以通过 ftp 客户端复制这些文件。

      【讨论】:

        【解决方案5】:

        我之前也遇到过类似的问题。 我写了一个简单的 Shell 脚本。

        $git log --reverse commit_HashX^..commit_HashY --pretty=format:'%h'
        

        上面的命令会以相反的顺序显示从 commit_HashX 到 commit_HashY 的 Commit Hash(Revision)。

         456d517 (second_hash)
         9362d03
         5362d03
         226x47a
         478bf6b (six_hash)
        

        现在是使用上述命令的主要 Shell 脚本。

        commitHashList=$(git log --reverse $1^..$2 --pretty=format:'%h')
        for hash in $commitHashList
        do
            echo "$hash"
            git archive -o \Path_Where_you_want_store\ChangesMade.zip $hash
        done
        

        将此代码添加到 export_changes.sh。现在将文件和提交哈希传递给脚本。

        确保初始 commit_hash 必须是第一个参数,然后是您要将更改导出到的最后一个 commit_hash。

        例子:

        $sh export_changes.sh hashX hashY

        将此脚本放入git本地目录或在脚本中设置git本地目录路径。 希望对您有所帮助..!

        【讨论】:

          【解决方案6】:

          这是我编写的一个小型 bash(Unix) 脚本,它将复制具有文件夹结构的给定提交哈希的文件:

          ARRAY=($(git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT $1))
          PWD=$(pwd)
          
          if [ -d "$2" ]; then
          
              for i in "${ARRAY[@]}"
              do
                  : 
                  cp --parents "$PWD/$i" $2
              done
          else
              echo "Chosen destination folder does not exist."
          fi
          

          创建一个名为 '~/Scripts/copy-commit.sh' 的文件,然后赋予它执行权限:

          chmod a+x ~/Scripts/copy-commit.sh
          

          然后,从 git 存储库的根目录:

          ~/Scripts/copy-commit.sh COMMIT_KEY ~/Existing/Destination/Folder/
          

          【讨论】:

            【解决方案7】:

            要导出以日期开头的修改文件:

              diff --stat @{2016-11-01} --diff-filter=ACRMRT --name-only | xargs tar -cf 11.tar
            

            快捷方式(使用别名)

              git exportmdf 2016-11-01 11.tar
            

            .gitconfig 中的别名

              [alias]
              exportmdf = "!f() { \
                git diff --stat @{$1} --diff-filter=ACRMRT --name-only | xargs tar -cf $2; \
              }; f"
            

            【讨论】:

              【解决方案8】:

              您可以使用Tortoise Git 将差异导出到 MS Windows:

              我右键单击并选择 TortoiseGit > 显示日志 并且 日志消息 将打开。

              选择两个修订版并进行比较。 之间的差异将被公开。

              选择文件并将选择导出到...到一个文件夹!

              【讨论】:

              • 当然。对此的许多答案都有 linux-only 命令,而且很多没有捕获工作更改,只有已提交的更改。很高兴这是张贴!
              【解决方案9】:

              我需要更新我的测试服务器并添加自 2.1 版以来更改的文件。
              对我来说,与 James Ehly 发布的解决方案类似,但在我的情况下,我想将两个旧标签之间的差异导出到存档包 - tag_ver_2.1 和 tag_ver_2.2 不是唯一的一次提交。

              例如:

              tag_ver_2.1 = 1f72b38ad
              tag_ver_2.2 = c1a546782

              下面是修改示例:

              git diff-tree -r --no-commit-id --name-only c1a546782 1f72b38ad | xargs tar -rf test.tar
              

              【讨论】:

              • 我刚刚检测到一个奇怪的问题:如果我尝试上面的代码,它就像一个魅力。但是我将 tar -rf test.tar 更改为 tar -zcf test.tar.gz,然后生成的文件缺少几个文件。是什么导致了这种结果差异?
              • 在 windows slave 机器上执行此命令时,我收到以下错误“'xargs' 未被识别为内部或外部命令、可运行程序或批处理文件。”
              【解决方案10】:

              如果你的提交哈希是例如 a9359f9,这个命令:

              git archive -o patch.zip a9359f9 $(git diff --name-only a9359f9^..a9359f9)

              将在提交中提取修改的文件并将它们放在patch.zip中,同时保持项目目录结构不变。

              有点冗长,提交哈希被提到了三遍,但它似乎对我有用。

              在这里得到它:http://tosbourn.com/2011/05/git/using-git-to-create-an-archive-of-changed-files/

              【讨论】:

              • 我想我可以在 PowerShell 中使用function 将命令简化为一个函数名,并使用$args 只传递一次提交哈希。在 *nix 中,你大概可以使用 shell-scripting 来达到同样的效果。
              • 伙计...我给你一杯啤酒 :)
              • @BenSewards 获取 commit1 和 commit2 之间的提交范围 (包括后来的提交,不包括早期的提交;顺序无关紧要) 只需 git archive -o patch.zip HEAD /**or a commit**/ $(git diff --name-only commit1 commit2)。传递给archive 命令的提交可以是任意提交(很可能是HEAD 或后来的提交),并且文件被拉出,就好像它们在那个提交阶段被签出一样。传递给diffcommit1commit2 仅用于生成要拉取的文件列表 - 它们不影响拉取文件的版本。
              • 附加提示:如果您想将多个提交范围的更改文件合并到一个存档中,只需使用分号重复diff 命令:git archive -o patch.zip HEAD /**or a commit**/ $(git diff --name-only commit1A commit1B; git diff --name-only commit2A commit2B; git diff --name-only commit3A commit3B)
              • 由于路径名中有空格,我不得不使用管道到 xargs 处理 NUL git diff -z --name-only commit1 commit2 | xargs -0 git archive -o patch.zip HEAD
              【解决方案11】:

              这是一个适用于 Windows 7 的单行命令。从存储库的顶级文件夹中运行它。

              for /f "usebackq tokens=*" %A in (`git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT HEAD~1 HEAD`) do echo FA|xcopy "%~fA" "C:\git_changed_files\%A"

              • echo FA 回答了关于您是在复制文件还是目录(文件)的不可避免的 xcopy 问题,以及关于覆盖文件(覆盖全部)的可能问题
              • usebackq 允许我们使用 git 命令的输出作为 do 子句的输入
              • HEAD~1 HEAD 获取上一次提交和当前 HEAD 之间的所有差异
              • %~fA 将 git 输出转换为完全限定的路径(需要将正斜杠改为反斜杠)
              • C:\git_changed_files\ 是您可以找到所有不同文件的地方

              【讨论】:

              • 当我看到那些乱七八糟的符号时,我没想到它会在没有大量调整的情况下工作......但它在第一次尝试时就工作了,谢谢。
              • 对所有(和我一样)不太熟悉 Windows 批处理文件的人提个醒:如果您想在批处理文件中使用上述命令,则需要替换 %A %%A 在 for 循环中
              • 什么?哈哈。只需使用 git archive - git diff 组合(尼古拉斯的回答)。更容易和直接。 (老实说,我不知道这里发生了什么。)
              • 警告:这将从您的工作副本中复制文件,这实际上可能与 HEAD 中的内容不匹配(或您替换它的任何提交哈希)
              • 好东西,但只能使用 CMD 而不是 Powershell,-v°v-
              【解决方案12】:
              git diff-tree -r --no-commit-id --name-only --diff-filter=ACMRT <i>$commit_id</i> | xargs tar -rf mytarfile.tar

              为了解决这个问题,这里是通过管道传输到 tar 的命令。这会将文件导出到 tar 存档中。

              【讨论】:

              • 这可以对多个提交到一个 tar 文件中完成吗?
              • 当尝试向同一个 tar 文件添加多个提交时,有时 tar 会使用其他名称 (filename1.ext) 再次创建 filename.ext。我意识到,如果我使用 zip,我可以添加并覆盖该文件(如果它存在于 zip 中)...您只需将 xargs 替换为 xargs zip -r0 myzipfile.zip $1
              • 这里需要 xargs 吗?我知道它适用于 rm 之类的东西,但我认为 tar 可以处理任意数量的文件。
              • 小心这个答案,这需要差异文件,但作为当前分支的当前版本
              • 在 windows slave 机器上执行此命令时,我收到以下错误“'xargs' 未被识别为内部或外部命令、可运行程序或批处理文件。”
              【解决方案13】:

              我制作了一个 php 脚本来在 Windows 上导出更改的文件。如果您有一个设置了 php 的 localhost 开发服务器,那么您可以轻松运行它。它会记住您的最后一个存储库并始终导出到同一个文件夹。导出文件夹总是在导出前清空。您还将看到已删除的文件以红色显示,因此您知道要在服务器上删除什么。

              这只是两个文件,所以我将它们发布在这里。假设您的存储库位于它们自己的文件夹中的 c:/www 下,并且 http://localhost 也指向 c:/www 并且启用了 php。让我们把这两个文件放在 c:/www/git-export -

              index.php:

              <?php
              /* create directory if doesn't exist */
              function createDir($dirName, $perm = 0777) {
                  $dirs = explode('/', $dirName);
                  $dir='';
                  foreach ($dirs as $part) {
                      $dir.=$part.'/';
                      if (!is_dir($dir) && strlen($dir)>0) {
                          mkdir($dir, $perm);
                      }
                  }
              }
              
              /* deletes dir recursevely, be careful! */
              function deleteDirRecursive($f) {
              
                  if (strpos($f, "c:/www/export" . "/") !== 0) {
                      exit("deleteDirRecursive() protection disabled deleting of tree: $f  - please edit the path check in source php file!");
                  }
              
                  if (is_dir($f)) {
                      foreach(scandir($f) as $item) {
                          if ($item == '.' || $item == '..') {
                              continue;
                          }
              
                          deleteDirRecursive($f . "/" . $item);
                      }    
                      rmdir($f);
              
                  } elseif (is_file($f)) {
                      unlink($f);
                  }
              }
              
              $lastRepoDirFile = "last_repo_dir.txt";
              $repo = isset($_POST['repo']) ? $_POST['repo'] : null;
              
              
              if (!$repo && is_file($lastRepoDirFile)) {
                  $repo = file_get_contents($lastRepoDirFile);
              }
              
              $range = isset($_POST['range']) ? $_POST['range'] : "HEAD~1 HEAD";
              
              
              $ini = parse_ini_file("git-export.ini");
              
              $exportDir = $ini['export_dir'];
              ?>
              
              <html>
              <head>
              <title>Git export changed files</title>
              </head>
              
              <body>
              <form action="." method="post">
                  repository: <?=$ini['base_repo_dir'] ?>/<input type="text" name="repo" value="<?=htmlspecialchars($repo) ?>" size="25"><br/><br/>
              
                  range: <input type="text" name="range" value="<?=htmlspecialchars($range) ?>" size="100"><br/><br/>
              
                  target: <strong><?=$exportDir ?></strong><br/><br/>
              
                  <input type="submit" value="EXPORT!">
              </form>
              
              <br/>
              
              
              <?php
              if (!empty($_POST)) {
              
                  /* ************************************************************** */
                  file_put_contents($lastRepoDirFile, $repo); 
              
                  $repoDir = $ini['base_repo_dir'] ."/$repo";
                  $repoDir = rtrim($repoDir, '/\\');
              
                  echo "<hr/>source repository: <strong>$repoDir</strong><br/>";
                  echo "exporting to: <strong>$exportDir</strong><br/><br/>\n";
              
              
                  createDir($exportDir);
              
                  // empty export dir
                  foreach (scandir($exportDir) as $file) {
                      if ($file != '..' && $file != '.') {
                          deleteDirRecursive("$exportDir/$file");
                      }
                  }
              
                  // execute git diff
                  $cmd = "git --git-dir=$repoDir/.git diff $range --name-only";
              
                  exec("$cmd 2>&1", $output, $err);
              
                  if ($err) {
                      echo "Command error: <br/>";
                      echo implode("<br/>", array_map('htmlspecialchars', $output));
                      exit;
                  }
              
              
                  // $output contains a list of filenames with paths of changed files
                  foreach ($output as $file) {
              
                      $source = "$repoDir/$file";
              
                      if (is_file($source)) {
                          if (strpos($file, '/')) {
                              createDir("$exportDir/" .dirname($file));
                          }
              
                          copy($source, "$exportDir/$file");
                          echo "$file<br/>\n";
              
                      } else {
                          // deleted file
                          echo "<span style='color: red'>$file</span><br/>\n";
                      }
                  }
              }
              ?>
              
              </body>
              </html>
              

              git-export.ini:

              ; path to all your git repositories for convenience - less typing
              base_repo_dir = c:/www
              
              ; if you change it you have to also change it in the php script
              ; in deleteDirRecursive() function - this is for security
              export_dir = c:/www/export
              

              现在在浏览器中加载 localhost/git-export/。该脚本设置为始终导出到 c:/www/export - 更改所有路径以适应您的环境或修改脚本以满足您的需要。

              如果你已经安装了 Git 并且 git 命令在你的 PATH 中,这将起作用 - 这可以在你运行 windows Git 安装程序时进行配置。

              【讨论】:

              • 这段代码很棒,但我想导出带有子模块的文件和目录,您的代码仅适用于主存储库,如果子模块更改了您的代码,则说子模块文件夹已删除!
              猜你喜欢
              • 2019-09-07
              • 1970-01-01
              • 2013-05-03
              • 2018-12-17
              • 1970-01-01
              • 2018-08-15
              • 1970-01-01
              • 2019-02-02
              相关资源
              最近更新 更多