git - 如何统一分支之间的文件权限差异?
问题描述
我搞砸了一些东西,我不太确定如何或如何修复它。我有分支 A 和分支 B,其中 A 有我一直在做的事情。出于某种原因,我不打算更改的许多文件的文件权限在我的分支 A 中是不同的。分支 B 具有所有这些文件的预期文件权限。有没有办法让 git 设置它,以便将 A 和 B 之间唯一区别是权限的文件设置为 B 中的权限?
简而言之,我有点想让 B 替换 A,只保留我想要的文件添加、删除和修改。
解决方案
对此没有方便的内置解决方案,但编写脚本很容易:Git 有一个可供您使用的大型工具集。
不过,首先,让我们建立一些背景信息。你不确定这一切是如何发生的,所以很有可能它会再次发生,而这一次你可以将它扼杀在萌芽状态(并弄清楚是什么造成的)。如果您想直接跳到潜在的解决方案,请拉到底部。
Git 存储提交。你可能已经知道了,但让我们把它写下来。
deadcab1feeddad2...
:-) 每个提交都由一些大而丑陋的哈希 ID或其他唯一标识。这些提交一旦做出,就永远无法更改。每个提交都有一些元数据 - 例如您的姓名和电子邮件地址和时间戳,以及您的日志消息,并且还存储其父提交的原始哈希 ID(或对于合并提交,两个/所有父级)。提交存储文件,作为运行时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 diff
s 来查找所有更改:- 提交和索引有什么不同?
- 索引和工作树有什么不同?
您可以运行git diff --cached
或git diff --staged
获得第一组,而git diff
根本没有选择获得第二组。(请注意,如果可执行模式已更改,Git 将认为文件已更改。)该git status
命令运行两个差异,但以不明确显示模式的方式汇总它们。
1这并不完全准确(有关详细信息,请参阅Checkout another branch when there are uncommitted changes on the current branch),但有助于作为起始心智模型。
分支 B 具有所有这些文件的预期文件权限。
关于分支在 Git 中的意义不大这一事实,我们还需要绕道而行。(另请参阅“分支”到底是什么意思?。)
在 Git 中,一个分支名称——我将在这里使用branch-A
和branch-B
作为这两个名称——只是标识一个特定的提交。
我在上面提到过,每个提交都有一个唯一的哈希 ID。不过,多个名称可以具有相同的哈希 ID。事实上,这就是新分支的开始方式。同时,每个提交都“指向”其父级,这就是新分支的开始方式:
A <-B <-C <--master
在这里,只有一个分支,master
. 它的哈希 ID 为 commit C
。这是分支中包含的最后一次提交(在这种情况下,也是存储库中的最后一次提交)。同时 commitC
记住了 的哈希 ID B
,它记住了 的 ID A
。 A
是第一次提交,所以它没有父级,操作在这里停止。
要将新提交添加到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
( -r
recursive) 选项。只需确定您想要的提交:
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 master
or git ls-tree -r f84b9b09d40408cf91bbc500d9f190a7866c3e0f
: all those identify commit f84b9b09d40408cf91bbc500d9f190a7866c3e0f
,这是master
分支的尖端。在您的情况下,您可以使用git ls-tree -r branch-B
从branch-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
方式一次提交一个。
推荐阅读
- c++ - 如何在 MFC 工具栏中添加自定义控件
- flutter - 如何从它自己的 onTap 中禁用 ListTile?
- python - 如何提取可以在字符串中多次出现的片段
- javascript - 单个列搜索(文本输入)在我的 Rails 代码中不起作用
- rdf - 使用 Jena Fuseki 导入 ~500MB Turtle 会产生错误和崩溃
- javascript - import {class} from 'path' 返回 undefined
- snowflake-cloud-data-platform - 雪花 - 如何使用文件格式解码 csv 列?
- solr - Solr 请求未从数据库中获取记录
- r - 在R中将数据帧转换为json
- tsql - 在 where 子句中进行日期时间比较后的 MS SQL 算术溢出