首页 > 解决方案 > 如何统一分支之间的文件权限差异?

问题描述

我搞砸了一些东西,我不太确定如何或如何修复它。我有分支 A 和分支 B,其中 A 有我一直在做的事情。出于某种原因,我不打算更改的许多文件的文件权限在我的分支 A 中是不同的。分支 B 具有所有这些文件的预期文件权限。有没有办法让 git 设置它,以便将 A 和 B 之间唯一区别是权限的文件设置为 B 中的权限?

简而言之,我有点想让 B 替换 A,只保留我想要的文件添加、删除和修改。

标签: git

解决方案


对此没有方便的内置解决方案,但编写脚本很容易:Git 有一个可供您使用的大型工具集。

不过,首先,让我们建立一些背景信息。你不确定这一切是如何发生的,所以很有可能它会再次发生,而这一次你可以将它扼杀在萌芽状态(并弄清楚是什么造成的)。如果您想直接跳到潜在的解决方案,请拉到底部。

  1. Git 存储提交。你可能已经知道了,但让我们把它写下来。deadcab1feeddad2...:-) 每个提交都由一些大而丑陋的哈希 ID或其他唯一标识。这些提交一旦做出,就永远无法更改。每个提交都有一些元数据 - 例如您的姓名和电子邮件地址和时间戳,以及您的日志消息,并且还存储其父提交的原始哈希 ID(或对于合并提交,两个/所有父级)。

  2. 提交存储文件,作为运行时Git索引git commit中的每个文件的快照。索引是你必须继续运行的原因git add。存储在提交中的文件是一种特殊的、仅限 Git 的压缩形式,只要提交存在,它们就会被冻结。

    当您签出某个提交时,Git 会将整个快照提取到索引中,以便索引现在与提交匹配。1 “解冻”文件:一旦它们在索引中,您就可以更改它们。但它们仍然是特殊的 Git-only 格式。所以现在 Git 将索引提取到工作树中,将文件转换为有用的形式。

    工作树中的文件具有权限。索引中的文件只有一个权限指示位,即:该文件是否可以执行? 由于历史(和 Linux)的原因,Git 将其表示为mode 100644“不可执行”或mode 100755“可执行”,但它实际上只允许一个标志位。其余的工作树权限来自您在提取文件时为系统/您自己设置的任何内容

    当你git add在一个文件上运行时,你实际上是在告诉 Git:将工作树文件复制到索引副本中。 这个 Git 对数据进行了化,使其准备好被冻结到提交中。它还根据文件当前是否可执行设置模式。

    通常,git diff它会告诉您是否存在从 100644 到 100755 的模式更改,反之亦然,因此您可以git diff用来比较。由于每个文件都有三个副本——已提交、已索引和工作树——您需要两个 git diffs 来查找所有更改:

    • 提交和索引有什么不同?
    • 索引和工作树有什么不同?


    您可以运行git diff --cachedgit diff --staged获得第一组,而git diff根本没有选择获得第二组。(请注意,如果可执行模式已更改,Git 将认为文件已更改。)该git status命令运行两个差异,但以不明确显示模式的方式汇总它们。


1这并不完全准确(有关详细信息,请参阅Checkout another branch when there are uncommitted changes on the current branch),但有助于作为起始心智模型。


分支 B 具有所有这些文件的预期文件权限。

关于分支在 Git 中的意义不大这一事实,我们还需要绕道而行。(另请参阅“分支”到底是什么意思?。)

在 Git 中,一个分支名称——我将在这里使用branch-Abranch-B作为这两个名称——只是标识一个特定的提交

我在上面提到过,每个提交都有一个唯一的哈希 ID。不过,多个名称可以具有相同的哈希 ID。事实上,这就是新分支的开始方式。同时,每个提交都“指向”其父级,这就是新分支的开始方式:

A  <-B  <-C   <--master

在这里,只有一个分支,master. 它的哈希 ID 为 commit C。这是分支中包含的最后一次提交(在这种情况下,也是存储库中的最后一次提交)。同时 commitC记住了 的哈希 ID B,它记住了 的 ID AA是第一次提交,所以它没有父级,操作在这里停止。

要将提交添加到master,Git 会将索引冻结到新快照中,为元数据添加日志消息和您的姓名等,并将新提交的父级设置为C. 这会生成新提交的哈希 ID D,Git 现在将其写入 name master,给出:

A  <-B  <-C  <-D   <--master

由于内部箭头不能改变(它们是提交的一部分!),我们只需要记住它们总是指向后面(在这些图中是向左)。所以在某些时候我们有一些提交哈希,我们H简称它,我们创建一个名称branch-A

...--H   <-- branch-A

如果我们现在也创建名称branch-B,我们的两个名称都指向H

...--H   <-- branch-A, branch-B

这意味着两个分支都包含相同的提交(从开始H和向后工作)。中的快照H将与中的快照完全匹配H,当然,具体到文件模式和内容。所以这两个提交之间没有区别。

因此,如果您在签出branch-A和签出时看到文件的不同权限branch-B,它们必须指向不同的提交,否则文件上的可执行/不可执行标志将匹配。

但是,修复工作树(和索引)中的权限不会更改任何现有的提交。这实际上是不可能的:Git 本身无法做到这一点。您只能进行新的提交。如果您需要修复较旧的提交,则必须将具有错误权限的提交复制到新提交,其主要区别在于它们具有正确的权限:

...--H--I--J   <-- branch-A
      \
       K--L   <-- branch-B

权限现在在提交中是错误的J,并且在提交中是正确的L:但是它们在提交中是对还是错I,这也是唯一的branch-A?您是否要进行类似于 的新提交I,但具有正确的权限?如果你这样做了,你也必须做一个替换J,这很像,J但使用I' 替换作为它的父级。

如果只需进行的提交就足够了branch-A,将其文件设置为从 commit 获取的可执行模式L,那是相对微不足道的。你最终会得到:

...--H--I--J--M   <-- branch-A
      \
       K--L   <-- branch-B

其中 commitM匹配J除了每个此类文件的存储可执行位之外的所有内容。

查看任何提交中的模式

要查看任何给定提交的文件名及其模式,请使用git ls-tree( -rrecursive) 选项。只需确定您想要的提交:

git ls-tree -r <hash>

输出是每个提交的文件一行。由于 Git存储文件,默认递归列表不包括 Git 内部表示所需的任何中间子树,您将获得所有blob对象的列表——这些是 Git 对文件的内部表示——除了两种特殊情况:符号链接和子模块(技术上,这个级别的gitlinks )。如果你没有这些,你可以忽略特殊情况。

输出将类似于此,这是实际 Git 存储库(针对 Git 本身)上输出的第一部分:

100644 blob 12a89f95f993546888410613458c9385b16f0108    .clang-format
100644 blob 49b30516419c8dfe8c039ef368a3af984439ebcc    .gitattributes
100644 blob 64e605a02b71c51e9f59c429b28961c3152039b9    .github/CONTRIBUTING.md
100644 blob adba13e5baf4603de72341068532e2c7d7d05f75    .github/PULL_REQUEST_TEMPLATE.md

第一个字段是mode字符串。第二个总是blob(除了那些您可能没有的特殊情况,但如果有,请过滤掉任何非“blob”行)。第三个是文件快照的内部哈希 ID,最后一个字段,在文字 ASCII 制表符之后,给出了文件在该快照中的路径名。

为了获得上述信息,我跑了:

git ls-tree -r HEAD

我也可以使用git ls-tree -r masteror git ls-tree -r f84b9b09d40408cf91bbc500d9f190a7866c3e0f: all those identify commit f84b9b09d40408cf91bbc500d9f190a7866c3e0f,这是master分支的尖端。在您的情况下,您可以使用git ls-tree -r branch-Bbranch-B.

更改工作树模式并提交

那么,此时,您只需要branch-A签出当前的提示即可。然后,从模式和名称中,您可以使用计算机的“更改文件模式”命令来设置所需的工作树模式。 git add所有更新的工作树文件将它们的新模式复制到索引中,然后运行git commit​​. 例如,如果您在 Linux 系统上,则可以chmod直接使用(注意:如果您有名称包含空格的文件,这有一个小缺陷,read除非您摆弄 否则不会正确地尊重它IFS):

git checkout branch-A
git ls-tree -r branch-B | while read mode ftype hash path; do
    [ $ftype = blob ] && chmod "$path" $mode
done
git add -u
git commit

这将抱怨提交中的任何文件,这些文件的branch-B名称在由git checkout branch-A. 如果您有以空格命名的文件,您也可能会在此处遇到错误,因此请务必检查。

您可以根据需要对其进行幻想,但这就是本质。请注意,如果您想重建现有提交,您可以使用交互git rebase方式一次提交一个。


推荐阅读