如何统一分支之间的文件权限差异?

时间:2018-10-25 17:03:56

标签: git

我搞砸了,但我不确定如何解决。我有分支A和分支B,其中A一直在努力。由于某些原因,分支A中我无意更改的无数文件的文件权限有所不同。分支B具有所有这些文件的预期文件权限。有没有办法让git对其进行设置,以便将A和B之间唯一的区别是权限的文件设置为B中的权限?

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

1 个答案:

答案 0 :(得分:0)

没有内置的解决方案,但是很容易编写脚本:Git有一个很大的工具集,您可以借鉴。

首先,让我们建立一些背景信息。您不确定这一切是如何发生的,因此很有可能再次发生,这一次您可以将其捏在萌芽中(找出原因)。如果您想直接跳到潜在的解决方案,请拉到底部。

  1. Git存储提交。您可能已经知道这一点,但让我们写下来。 :-)每个提交都由某个较大的丑陋哈希ID deadcab1feeddad2...或其他任何东西唯一标识。这些提交一旦完成就无法更改。每个提交都有一些元数据,例如您的姓名和电子邮件地址,时间戳,日志消息,还存储其 parent 提交的原始哈希ID(或对于合并提交,两者/全部父母)。

  2. 提交存储文件,作为运行git commit时Git的 index 中每个文件的快照。索引是您必须继续运行git add的原因。提交中存储的文件采用特殊的,仅Git的压缩形式,在提交过程中,文件会一直冻结。

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

    工作树中的文件具有权限。 index 中的文件只有一个许可指示位,即:该文件应可执行吗?出于历史(和Linux)原因,Git将其表示为{{1 }},“不可执行”,或mode 100644,“可执行”,但实际上只允许一个标志位。工作树权限的 rest 来自解压缩文件时为系统/您自己设置的任何内容。

    在文件上运行mode 100755时,您实际上是在告诉Git:将工作树文件复制到索引副本中。此Git会修改数据,使其准备就绪被冻结为提交。它还根据文件当前是否可执行来设置模式。

    通常,git add会告诉您模式是否从100644更改为100755,反之亦然,因此您可以使用git diff进行比较。由于每个文件都有三个副本(已提交,建立索引和工作树),因此您需要两个 git diff来查找所有更改:

    • 提交和索引之间有什么区别?
    • 索引和工作树之间有什么区别?


    您可以运行git diffgit diff --cached来获取第一组,而git diff --staged则完全没有任何选项来获取第二组。 (请注意,如果可执行文件模式已更改,则Git会考虑更改文件。)git diff命令同时运行 diff,但以不显示模式的方式对其进行汇总


1 这并不完全准确(有关详细信息,请参见Checkout another branch when there are uncommitted changes on the current branch),但可以作为开始的心理模型。


  

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

在这里,我们还需要绕过一些事实,即分支在Git中的意义并不大。 (另请参见What exactly do we mean by "branch"?。)

在Git中,一个分支名称-我将在两个名称中分别使用git statusbranch-A-简单地标识一个特定的提交

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

branch-B

在这里,只有一个分支A <-B <-C <--master 。它具有提交master的哈希ID。这是分支中包含的 last 提交(在这种情况下,也是存储库中的最后一个提交)。同时,提交C会记住C的哈希ID,它会记住B的ID。 A是第一个提交,因此它没有父提交,操作在此处停止。

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

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时看到文件的不同权限,则它们必须指向不同的提交,否则,可执行文件/不是-文件上的可执行标志将匹配。

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

branch-B

权限现在在提交...--H--I--J <-- branch-A \ K--L <-- branch-B 中是错误的,并且在提交J中是正确的:但是在提交L中它们是对还是错,这也仅在I上?您是否要进行branch-A之外的新提交,但要获得正确的权限?如果这样做,您还必须替换I,这很像J,但是使用J的替换作为其父项。

如果仅在I上进行一次 new 提交就足够了,将其文件设置为从提交branch-A采取的可执行模式,那是相对琐碎的。您将最终得到:

L

其中提交...--H--I--J--M <-- branch-A \ K--L <-- branch-B 与所有文件中的提交M都匹配提交J

在任何提交中查看模式

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

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 mastergit ls-tree -r f84b9b09d40408cf91bbc500d9f190a7866c3e0f:所有这些都标识提交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一次进行一次提交。