获取每个分支的工作树并将其保存在单独的文件夹中 - Bash

3
我需要编写一个bash脚本,每小时将我们的代码库分支复制到本地的Linux Web服务器上。 我们有一个git远程代码库(gitolite),其中包含以下分支:"master" "testing" "feature-featurename" "hotfix-number" 。这些分支中的每一个都应该复制其工作树到/var/www/html/branchname。 首先:如何将不同的工作树复制到不同的文件夹中? 其次:如果“feature-”和“hotfix-”分支名称经常更改,如何自动化此过程? 这与git挂钩无关,这应该是在不同服务器上运行的脚本,并由cron job触发。
3个回答

3

可怕的几行代码:

mkdir -p /var/www/html
git clone --bare user@git-server:/your-repo.git && cd your-repo.git

git for-each-ref --format='%(refname)' refs/heads/ | grep 'branch-pattern' | while read branchRef; do
  branchName=${branchRef#refs/heads/}
  git archive --format=tar --prefix="$branchName/" "$branchRef" | tar -C/var/www/html -x
done

让我们逐步分解:
1. 确保目标目录存在。这对您可能不是必需的。
mkdir -p /var/www/html 2. 克隆git仓库并进入目录
git clone --bare user@git-server:/your-repo.git 3. 列出分支。这将从已克隆的目录中运行。请注意,不使用git branch,因为在脚本中使用会产生意外的输出。
git for-each-ref --format='%(refname)' refs/heads/ 4. 选择要筛选的分支。在您的情况下,模式可能类似于grep -E "(master)|(testing)|(feature-.*)"等。
grep 'branch-pattern' 5. while语句读取每个分支名称并将其分配给branch变量。
6. 创建一个branchName变量,它是不包括ref前缀的分支名称。请注意,这是特定于bash的。
7. git archive创建所选分支的tar归档文件,并使用分支名称作为所有条目的前缀。将存档发送到标准输出。
git archive --format=tar --prefix="$branch/" "$branch" 8. 立即将档案提取到其目标位置。
tar -C/var/www/html -x

太好了!你能解释一下这个答案吗?具体是发生了什么? - Theo
2
添加了解释,希望有所帮助 :) - Henrik Gustafsson
1
我看到这里有几个性能问题。首先,它告诉archive将每个分支打成tar包,然后再让tar程序撤销此步骤。其次,它必须为每个分支复制整个工作树,因为它绕过了git仅更新已更改内容的能力。还有一件事-与性能无关,但是... branch不是用于脚本列出分支的最佳命令。 - Mark Adelsberger
1
是的,我刚想起来了。改成了 for-each-ref :) - Henrik Gustafsson
1
tar并不是那么糟糕,只要你不压缩输出,实际上,但是对于大量分支或大型存储库可能会有问题。不过这并不是用于交互式使用的,所以我认为没问题。 - Henrik Gustafsson

3
我如何将不同的工作树复制到不同的文件夹中?
使用Git的工作树功能即可实现,具体操作请参考Mark Adelsberger答案中的示例。
该答案结尾处写道:
您还可以使用git worktree list --porcelain代替直接搜索工作树目录 - 在特殊情况下(例如命名空间分支)这可能更可取。
从Git 2.36开始(2022年第二季度),您还可以:
git worktree list --porcelain -z

实际上,你应该这样做,特别是在 Git 2.31(2021年第一季度)之后:git worktree list(man) 现在将工作树标注为可清除的,在 --porcelain 模式下显示锁定和可清除属性,并增加了一个 --verbose 选项。

请查看提交 076b444, 提交 9b19a58, 提交 862c723, 提交 47409e7 (2021年1月27日),以及提交 eb36135, 提交 fc0c7d5, 提交 a29a8b7(2021年1月19日)由Rafael Silva (raffs)撰写。
(已在提交02fb216(2021年2月10日)中由Junio C Hamano -- gitster --合并)

worktree: 教授 list 的详细模式

协助者:Eric Sunshine
签署者:Rafael Silva
审查者:Eric Sunshine

"git worktree list"(man) annotates each worktree according to its state such as prunable or locked, however it is not immediately obvious why these worktrees are being annotated.
For prunable worktrees a reason is available that is returned by should_prune_worktree() and for locked worktrees a reason might be available provided by the user via lock command.

Let's teach "git worktree list" a --verbose mode that outputs the reason why the worktrees are being annotated.
The reason is a text that can take virtually any size and appending the text on the default columned format will make it difficult to extend the command with other annotations and not fit nicely on the screen.
In order to address this shortcoming the annotation is then moved to the next line indented followed by the reason If the reason is not available the annotation stays on the same line as the worktree itself.

The output of "git worktree list" with verbose becomes like so:

$ git worktree list --verbose
...
/path/to/locked-no-reason    acb124 [branch-a] locked
/path/to/locked-with-reason  acc125 [branch-b]
    locked: worktree with a locked reason
/path/to/prunable-reason     ace127 [branch-d]
    prunable: gitdir file points to non-existent location
...

git worktree现在在其手册页面中包含:

For these annotations, a reason might also be available and this can be seen using the verbose mode. The annotation is then moved to the next line indented followed by the additional information.

$ git worktree list --verbose
/path/to/linked-worktree              abcd1234 [master]
/path/to/locked-worktree-no-reason    abcd5678 (detached HEAD) locked
/path/to/locked-worktree-with-reason  1234abcd (brancha)
locked: working tree path is mounted on a portable device
/path/to/prunable-worktree            5678abc1 (detached HEAD)
prunable: gitdir file points to non-existent location

Note that the annotation is moved to the next line if the additional information is available, otherwise it stays on the same line as the working tree itself.

并且:

worktree: 教授 list 如何注释可修剪的工作树

协助者:Eric Sunshine
签名者:Rafael Silva
审核者:Eric Sunshine

The "git worktree list"(man) command shows the absolute path to the worktree, the commit that is checked out, the name of the branch, and a "locked" annotation if the worktree is locked, however, it does not indicate whether the worktree is prunable.

The "prune" command will remove a worktree if it is prunable unless --dry-run option is specified.
This could lead to a worktree being removed without the user realizing before it is too late, in case the user forgets to pass --dry-run for instance.
If the "list" command shows which worktree is prunable, the user could verify before running "git worktree prune"(man) and hopefully prevents the working tree to be removed accidentally on the worse case scenario.

Let's teach "git worktree list" to show when a worktree is a prunable candidate for both default and porcelain format.

In the default format a "prunable" text is appended:

$ git worktree list
/path/to/main      aba123 [main]
/path/to/linked    123abc [branch-a]
/path/to/prunable  ace127 (detached HEAD) prunable

In the --porcelain format a prunable label is added followed by its reason:

$ git worktree list --porcelain
...
worktree /path/to/prunable
HEAD abc1234abc1234abc1234abc1234abc1234abc12
detached
prunable gitdir file points to non-existent location
...

git worktree现在在其手册页面中包括:

当前检出的分支(如果没有,则为“分离的HEAD”),如果工作树被锁定,则为“已锁定”,如果可以通过prune命令修剪工作树,则为“可修剪”。

git worktree现在在其手册页面中包括:

The command also shows annotations for each working tree, according to its state. These annotations are:

  • locked, if the working tree is locked.
  • prunable, if the working tree can be pruned via git worktree prune.
$ git worktree list
/path/to/linked-worktree    abcd1234 [master]
/path/to/locked-worktreee   acbd5678 (brancha) locked
/path/to/prunable-worktree  5678abc  (detached HEAD) prunable

在 Git 2.36(2022年第二季度)之前, "git worktree list --porcelain"(man) 没有正确引用路径名和锁定原因的不安全字节,通过引入以“-z”结尾的输出格式进行解决。

请参见 提交 d97eb30 (2022年3月31日) 由Phillip Wood (phillipwood)
(由Junio C Hamano -- gitster --合并于提交 7c6d8ee,2022年4月4日)

工作树:为列表子命令添加-z选项

签署人:Phillip Wood

为了处理包含换行符的工作树路径,现在在git worktree中添加了一个-z选项,用于与--porcelain一起使用并提供NUL终止的输出。由于'worktree list --porcelain'不会引用工作树路径,因此这使得它能够处理包含换行符的工作树路径。现在git worktreeman page中推荐将其与-z结合使用。当使用--porcelainlist时,将每行以NUL而不是换行符终止,这样可以解析包含换行符的工作树路径的输出。

git worktree现在在其man page中包括:

瓷器格式每个属性占一行。

如果给出-z,则行以NUL而不是换行符终止。


2

首先你需要一个分支列表。为了脚本化的目的,最好使用for-each-ref命令来完成。假设你只需要本地分支名称,可以使用类似下面的命令:

git for-each-ref refs/heads/* |cut -d\/ -f3

顺便提一下,上述命令中有几个假设条件是您在"命名空间"中不使用分支。如果您使用像qa/feature-1这样包含/的分支名称,则会改变一些内容。上述命令简单地变成:

git for-each-ref refs/heads |cut -d\/ -f3-

但更大的问题是您可能需要更多地考虑分支名称应如何映射到目录名称。因此,现在我会假设分支名称不包含/

您需要处理每个分支,所以

git for-each-ref refs/heads/* |cut -d\/ -f3 |while read branch; do
  # ... will process each branch here
done

现在您可以使用git worktree来简化各个检出。请注意,这比使用archive复制每个分支的整个提交内容,然后调用tar撤消您不想要archive首先执行的工作要高效得多。
为确保定义了所有必需的工作树。
git for-each-ref refs/heads/* |cut -d\/ -f3 |while read branch; do
  if [ ! -d .git/worktrees/$branch ]; then
    git worktree add /var/www/html/$branch $branch
  fi
done

现在讲一件事情,当分支被移动时(即收到推送时),它会使工作树“不同步”,以便您似乎已经暂存了每个更改的“撤销”。 (默认工作树的保护似乎不适用。)

但这似乎符合您的要求;另一种选择是在推送到来时更新目录,这与您描述的问题相矛盾。因此,在这种情况下,您的脚本应通过“取消撤消”来同步新更改的工作树。

git for-each-ref refs/heads/* |cut -d\/ -f3 |while read branch; do
  if [ ! -d .git/worktrees/$branch ]; then
    git worktree add /var/www/html/$branch $branch
  fi
  git reset --hard HEAD
done

当然,有时分支会消失;如果你不想要过时的工作树元数据,可以添加一个。
git worktree prune

您还可以使用git worktree list --porcelain而不是直接搜索工作树目录-在一些奇怪的情况下,这可能更可取,比如(再次)命名空间分支。


1
不错!小心“refs/heads/*”的意外扩展。虽然这不太可能成为问题,但是嘿...我考虑使用工作树,但我不想在目标目录中留下任何意外的.git目录。如果你被困在旧版本的git中,工作树可能不可用。 - Henrik Gustafsson
1
关于.git文件的观点很正确。(在“add”工作树中,.git实际上是一个文本文件,指向.git/worktree/foo目录,而不是它自己的元数据目录;但仍然有一定用途。) - Mark Adelsberger

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