来自特定提交的检出文件失败,路径中有 space

Checkout files from a specific commit failed with space in the path

我想要实现的是获取特定提交 76363636 从 branch1 到 branch2 的所有更新。

我使用了以下代码而不是仅仅检查这个提交,因为它不符合我的目的:

git checkout branch1 $(git diff-tree --no-commit-id --name-only -r 76363636)

当提交中的文件路径之间没有 space 时,这个工作正常。我多次使用此代码。

但不是当文件路径中有 space 时,例如。下面:

force-app/main/default/layouts/PersonAccount-Layout Professionnel.layout-meta.xml

我收到以下错误:

error: pathspec 'force-app/main/default/layouts/PersonAccount-Layout' did not match any file(s) known to git

如何在我动态获取文件时使用双引号实际保护文件

下面的代码将不起作用:

git checkout branch1 "$(git diff-tree --no-commit-id --name-only -r 76363636)"

TL;DR

您想在 运行 执行命令时临时设置 IFS

save="$IFS"
IFS=$'\n'    # depends on shell
git checkout branch1 $(git diff-tree --no-commit-id --name-only -r 76363636)
IFS="$save"

或类似的。您可能必须在此处使用 $'\n' 以外的其他内容。您也可以考虑使用 git restore 而不是 git checkout。查看下面的所有详细信息。

这不是 Git 的问题——嗯,不完全是——而是 shell 你使用。解决方法是让 shell 表现得更好,或者完全绕过它(请参阅下面的 git restore 部分)。

当您在命令提示符下输入命令时:

$ command with some arguments

shell——command-line 解释器——将这里的部分分解:command , with, some, arguments 各成为一个“词”, shell 然后在系统某处找到一些名为 command 的可执行文件,并 运行 将所有四个“单词”作为四个单独的 argv 参数提供。 argv[0] 参数通常要么被完全忽略,要么用于增加错误消息,以防命令以与预期不同的名称安装(例如,git-2.17 到 运行 旧版本 Git).剩余的参数,在本例中从 argv[1]argv[3],然后由程序解释——但请注意它们是 pre-divided

您是否希望运行,例如

$ git restore --source=HEAD -SW "file with spaces"

必须 使用引号(双引号或单引号),以便 shell 调用带有参数的 git restore, --source=HEAD, -SW, 和 file with spaces。请注意封闭的引号是如何消失的,但 spaces 被保留:有一个包含两个空格的参数。

命令:

git diff-tree --no-commit-id --name-only -r 76363636

本身分为六个字,以git开头,以76363636结尾。该命令的 shell 运行 并且——因为整个命令包含在 $(...) 中—— 读取它的输出 。 shell 然后 将其输出解释为一系列单词 ,由白色 space 分隔:space、制表符和换行符。 shell 将这些单词打散,然后 运行:

git checkout branch1 <word1> <word2> ... <wordN>

所有 N broken-apart 个单词。

因为是shell在做breaking-up,所以是shell 你必须在这里克服。有一种方法可以做到这一点。

shell 使用 $IFS

断词

Bourne-derived shells 使用 内部字段分隔符 变量 $IFS 来确定什么使某物成为“词”。默认 IFS 设置为 space-tab-newline。这有点难以显示,因为 space、制表符和换行符都 在屏幕上显示 为空白或空无。

为了表示space、制表符和换行符,我们可以在引号中使用文字space,序列\t代表TAB,以及 NL 的序列 \n(换行符)。 有些 shell让你直接这样做:

var=$' \t\n'

这是一个单引号,前面有一个 dollar-sign $ 字符:然后用反斜杠序列解释其中的文本,处理方式类似于它们在 C 编程语言中的用法。

某些版本的 shell 不允许这样做;在这里,我们可以使用 POSIX printf 命令:

printf " \t\n"

(此处双引号或单引号均可)

我们想要,当然是shell只在换行处换行。如果你的文件名包含嵌入换行符,这将不起作用——但这样的文件名特别邪恶,似乎不常用,不像 Windows 和 macOS 文件名通常嵌入 space在其中。如果你真的想要 bullet-proofing,你会想要 -z 选项和 NUL-terminated 路径:ASCII NUL 是唯一在 Linux 上的路径名中字面禁止的字符(因此在 Git 中也是如此)。

让 shell 在换行处分解我们的路径名后,我们应该——为了“shell 卫生”,如果没有别的——恢复 $IFS 之后设置。为此,我们可以直接将 IFS 设置回 space-tab-newline:

IFS=$'\n'
... do things using $(...) ...
IFS=$' \t\n'

或者,我们可以获取当前设置,将其设置为我们需要的设置,然后恢复旧设置。这对于可能出于 他们的 目的设置 IFS 并且不希望我们破坏它的其他用户来说很好:

f() {
    local save
    save="$IFS"
    IFS=$(printf '\n')
    ... our code ...
    IFS="$save"
}

函数 f 现在可以从 暂时更改 IFS 的其他 shell 函数安全地调用,而无需继续设置IFS 在另一个 shell 函数中。

(在这里,我使用 printf 而不是 $'...',以防我们有一个 shell 不允许使用 $'...'。)

使用git restore

git checkout 命令在这里有几个缺陷:

  • 如果 dif 列表git diff-tree 的 erences 是 empty,我们检查整个分支。这可能不是我们想要的。这是一个很大的缺陷!
  • 如果某些文件名以 - 开头,这个 git checkout 将表现不佳。我们可以通过添加 -- 来解决这个问题,这可能是个好主意。
  • 如果 branch1 不是有效的分支名称,git checkout 将表现不佳。

我们可以避免所有这些问题并且通过使用git restore及其--pathspec-from-file和[来解决整个IFS相关的问题=58=]旗帜。 (请确保您的 git restore 的 Git 版本支持这些标志;它们首次出现在 Git 2.25 中,而 git restore 本身首次出现在 Git 2.23 中。) -z 标志在 git diff-tree 中存在的时间更长,因此如果您的 Git 至少为 2.25,那么您同时拥有两者。

那么我们要做的是:

git diff-tree <options> -z <rev> |
git --literal-pathspecs restore --source=branch1 --pathspec-from-file=- --pathspec-file-nul

git diff-tree 运行 和往常一样,但这次输出 un-encoded 路径名——这解决了一些你没有解决的问题yet encountered——并以 ASCII NUL 终止每个路径名。然后 git restore 命令 从标准输入中读取 这些路径名(作为路径规范)。 git--literal-pathspecs 选项本身告诉 git restore 不要尝试解释任何输入路径中的路径规范魔法。