我分叉了一个存储库,我希望在更高级别添加内容。所以我用谷歌搜索了如何将一个回购的内容移动到一个级别。 标准建议似乎是使用 filter-branch 例如,其中一个答案是:
How can I rewrite history so that all files, except the ones I already moved, are in a subdirectory?
这里可能有更好的建议:
Move file and directory into a sub-directory along with commit history
在我看来,将 git mv 应用到存储库中的每个文件更有意义。没有内置的方法可以递归执行此操作,因此您必须执行以下操作:
find . -type f > files
find . -type d | xargs -idir mkdir -p subdir/dir
cat files | xargs -ifile git mv file subdir/file
我对改写历史而不是修复历史的改变持谨慎态度。如果您尝试从原始上游项目中获取(同步),我希望使用filter-branch重写历史记录会导致问题。希望git能够更好地理解(和合并)基于移动的提交。
如果是这样的话,为什么 filter-branch 建议更频繁(我的看法 - 这可能是错误的)以及为什么没有更强大的 git变体mv (例如递归)冒泡到表面了吗? (Q1)
我理解使用 filter-branch 来删除所有版本存储库的密码等敏感数据。但是,建议将主要(有意而非偶然)更改隐藏到存储库似乎是一种非常糟糕的做法。
是否建议使用过滤器分支(或其他最佳实践)的等效项,以便跟踪历史记录以进行深思熟虑的更改? (Q2)
澄清:历史记录不必附加到同一实体(文件),但必须是可跟踪的,例如使用git log --follow。
答案 0 :(得分:1)
你在Git中要求的技术上是不可能的。原因很简单,虽然相当自我纠缠:
存储库中有四种对象:提交,树,blob(文件)和带注释的标记。每个对象都有一个唯一的标识符,表示为40个字符的SHA-1哈希,例如7c56b20857837de401f79db236651a1bd886fbbb
。 1 存储库基本上是一个键/值存储,其哈希ID为key和对象的内容是值。
唯一ID完全取决于对象的内容,实际上,它是通过对对象进行散列形成的(前缀是一个微小的标题,给出了对象的类型和大小)。这意味着,例如,包含仅包含单词hello
的一行的文件的哈希值为ce013625030ba8dba906f756967f9e9ca394464a
。 Universe中的每个文件由该一行组成,具有相同的哈希值。 2
换句话说,哈希的唯一性取决于对象的唯一性。再次使用同一个对象,您将获得相同的哈希值。使用不同的对象,您将获得不同的哈希值。在底层,Git就是这个相同的键/值存储:给它一个键(你必须以某种方式,神奇地,知道),并且它会返回一个哈希 该键的值。
commit 对象记录五个项目作为其值:
tree
ID:该提交的源树。 (树本身也存储为一个对象,但多个提交可以重用一个树。例如,如果你进行提交,然后立即为该提交进行恢复提交,则恢复的提交将重用原始树。是的,我们从tree T1
开始;我们使用tree T2
进行新的提交;然后我们进行还原提交,并再次tree T1
。它是一个不同的提交< / em>但它存储相同的源树。)git log
有工具来提取这些部分。最后,关键词:历史是提交。
存储库中的历史记录是存储库中的提交集:仅此而已。 查看历史记录的方式是从引用开始,例如分支名称或标记,这只是Git为您提供的一种人性化字符串转换方式将master
或v2.2.1
添加到哈希ID中。这会让你最后一次(或提示)提交。提示提交有一个或多个存储的父ID,它可以获取历史记录的下一位,并且这些提交包含更多父项,这使您可以向后移动历史记录。
由于parent
和tree
行是提交对象的一部分,如果您想对任何提交进行任何更改,任何地方在存储库中存储的历史记录中,您必须进行新的不同的提交。即使您完全保留作者和提交者名称+电子邮件+时间戳,即使您保留确切的消息,如果您以任何方式更改了tree
,您将获得一个新的,不同的提交,新的,不同的哈希ID。
然后,由于您已经提交了属于某个提交链的某个新提交,因此您必须重新复制每个后续提交。您必须重新复制提交的子 3 才能输入新的parent
行。这会为孩子产生一个新的,不同的哈希,所以现在你必须重新复制它的孩子,这是你的提交者的孙子。这迫使你重新复制曾祖母,等等,一直到尖端。
1 这实际上是Git本身的Git存储库中的标记v2.2.1
。从理论上讲,这个相同的ID将被分配给另一个不同的Git对象,在宇宙的某个地方,只要不同的Git对象从不使用Git存储库的克隆为Git的。但一般情况下,除非内容逐位相同,否则ID不会在任何地方重复;并且非常关键的是,在单个存储库中没有ID会像那样重复 - 实际上,Git字面上可以使用相同的ID在一个存储库中存储两个不同的对象。 / p>
2 如果Git曾经改变哈希算法,这将导致一些痛苦。 Mercurial也使用SHA-1,但Mercurial故意留下空间切换到SHA-256,并且更好地隐藏内部哈希值。 Git太容易暴露哈希值,而tree
个对象没有更大的哈希空间,因此转换会更具破坏性。
3 或者孩子,如果提交有多个孩子。请注意,查找 children 很难,因为提交只记录他们的父母。 Git必须遍历整个提交图以查找给定提交的所有子项。通常情况下,它并不麻烦:大多数情况并非如此,并且一些似乎需要的案例可以通过找到一部分孩子而逃脱。 Git有一种不幸的倾向,让用户知道你找到所有孩子的真正重要性,并让你强迫Git这样做。
git filter-branch
会做什么呢?答案很简单:git filter-branch
副本提交。
filter-branch脚本尽力将原始提交保留为逐位相同的副本。 如果它可以完全复制提交,则新副本与原始具有相同的ID,因此 原始。但是如果树中的任何内容发生了变化,或者父ID也发生了变化,那么新副本就会有一个新的不同ID。
Filter-branch通过首先列出要复制到文件中的每个ID来执行此复制。然后它通过这个文件,在父母面前的父母和#34;订购。它提取要复制的提交,应用所有过滤器,并从结果中进行新的提交。如果新提交是逐位相同的,那么&#34; new&#34;提交只是分享旧的;否则它有一个新的,不同的ID。
filter-branch命令也构成一个映射文件:&#34;旧ID是 X ,新ID是 Y &#34;。每个新提交只添加一个新映射: X 和 Y 相等,如果提交实际上是逐位相同的,否则它们是不同的。当然,您可以跳过一些提交(使用--commit-filter
参数),这会使跳过的提交映射到最近未跳过的提交:这是&#34;重新映射到祖先&#34;在the documentation中显示的概念。
当filter-branch完成时,它会使用累积的映射重写部分或全部引用(分支名称和可选的标记名称 - 它可能默认包含标记)。
请注意,在过滤后,您的存储库中有两组历史记录:原始提交,例如保存在refs/original/refs/heads/master
中,以及重写{指向的新副本} {1}}分支{1}}。
虽然Git本身并不是加密安全的,但请注意您可以对带注释的标签进行GPG签名。这些GPG签名仅验证一个特定的签名对象,即仅验证标签本身。但是,标记实际上包含目标提交的ID,因此您实际上已经证明相应的提交是好的和有效的,不包含特洛伊木马,后门,病毒或其他Bad Things™。并且,由于该提交包含其父提交ID,因此您还在父级及其父级上签字,依此类推历史记录。
当你使用filter-branch并让它复制标签时,它会切断签名,因为它们不再有效:它们指向已更改的复制提交。如果要签署副本,则必须手动执行此操作。 (这可能是为什么 filter-branch默认情况下不会复制标记。问题是它在完成时会丢弃提交ID映射文件,所以现在它也是如此迟到:最好复制标签,删除过程中的签名,然后让你用签名副本替换副本。)
(你也可以对个人提交进行GPG签名。这对于filter-branch来说效果不佳,无论如何通常都会造成很大的麻烦。)
虽然这与更改提交历史几乎没有关系,但提及Git&#34;注意事项&#34;是一个好主意。这里。 Notes是两部分问题的替代解决方案,(a)提交是不可变的,但(b)我们希望能够进行提交,然后以某种方式标记该提交,例如,说它已通过一些自动化测试,或已被第42号检查,或其他任何。
A&#34; note&#34;只是一个附加到commit-ID的文件 4 。此文件与提交历史记录分开存储在&#34;注释历史记录&#34;:提交链中,其提示存储在refs/heads/master
(嗯,master
无论如何,{{1 part是可配置的默认值,你可以有多组笔记)。 Git有一小组命令可以让你将一个注释附加到一个提交,默认情况下,refs/notes/commits
将检查每个提交,通过它的哈希ID来查看是否有注释它
由于注释是仅返回提交哈希值的单独文件,因此您可以更新这些文件,从而更新附加到任何给定提交的注释。
当然,过滤会更改提交ID,这会丢失注释和提交之间的链接。 refs/notes/
更新笔记是可能的(尽管不是很重要),但它现在不会这样做。
4 &#34;文件名&#34;提交注释实际上是提交本身的ID,稍微修改以便更快地查找。修改类似于对象在commits
中的存储方式:哈希ID为git log
的对象存储在filter-branch
中。提交注释采用树形结构,树深度是变量,而不是简单的前两个/所有其余的扇出,所以它是#s有点棘手。但是,前端.git/objects
界面非常好地隐藏了这一切。