【问题标题】:Git history as regular filesGit历史作为常规文件
【发布时间】:2018-08-14 23:49:26
【问题描述】:

我想知道 Git 本身或文本编辑器中是否有办法将 Git 历史记录直接显示为文件树中的常规文件。

如果我有一个文件landing.html 和几个先前的提交,我想在当前文件旁边直接拥有完整的landind.html.old.v0landind.html.old.v1landind.html.old.v2 等历史文件。可能还有文件名中的提交消息。喜欢landind.html.old.v0.initial-commit

这可以通过在提交后添加一个钩子在 Git 本身中完成吗?或者,也许作为文本编辑器的扩展?我主要使用VSCodeSublime。我想避免使用像gitk 这样的额外工具。

【问题讨论】:

  • 你想要你的历史(因为......好吧,这是个问题:从什么时候开始?);或者你想要一个total历史?如果是后者,你打算如何处理图中的分支和合并?
  • 总历史会更好,但我只能解决我的一个分支。我希望能够在不与 Git 交互的情况下查找全部历史记录。

标签: git github visual-studio-code sublimetext3


【解决方案1】:

时间轴视图 - 内置,而不是扩展 - 似乎可以满足您的需求。非常甜蜜,专注于打开的编辑器,并列出了该文件的提交历史。单击一个条目并打开一个差异。它会打开当前版本的差异 - 如果您希望保留单独的文件,则必须复制所有差异并保存到新文件中。


vscode v1.44 更新

时间轴视图现在不在预览状态,并且默认启用(已添加重点)。这是可视化时间序列事件的统一视图(对于 例如,文件的 Git 提交、文件保存、测试运行等)。这 时间线视图会自动更新,显示时间线 当前活动的编辑器,默认情况下。您可以控制此默认值 通过切换视图工具栏中的眼睛图标来行为。还有,类似 对于其他视图,时间轴视图支持在您键入时查找或过滤。

在这个版本中,内置的 Git 扩展提供了一个时间线 提供指定文件的 Git 提交历史的源。 选择一个提交将打开一个由 那个承诺。同时提供上下文菜单,还提供Copy Commit ID复制提交消息 命令。还有一个新的打开 资源管理器文件上下文菜单上的时间线命令,用于快速显示 所选文件的时间线。


v1.42和v1.43相关功能预览:https://github.com/microsoft/vscode-docs/blob/vnext/release-notes/v1_42.md#timeline-viewhttps://github.com/microsoft/vscode-docs/blob/vnext/release-notes/v1_43.md#timeline-view

时间轴视图

在这个里程碑中,我们在新的时间轴视图上取得了进展,并且 有一个早期的预览分享。这是用于可视化的统一视图 时间序列事件(例如,Git 提交、文件保存、测试运行、 等)用于资源(文件、文件夹)。要启用时间轴视图,您 必须使用Insiders 版本,然后添加以下设置:

"timeline.showView": true [在 v1.44 中默认启用]

在这个早期预览中,时间轴视图显示了 Git 提交历史 的活动文档,目前限制为 32 项。 单击其中一个提交,将打开更改的差异 由该提交引入。扩展名也可以contribute their own timeline sources,这个统一展示 时间线视图。最终,您还可以选择(过滤) 您希望在视图中看到哪些来源。

敬请期待,我们为这项新功能准备了更多内容。你可以 订阅#84297 并关注问题 用时间线标签标记。如果你对其他类型有想法 您希望在此视图中看到的信息,请告诉我们!


【讨论】:

    【解决方案2】:

    Git 没有为此内置任何内容,因此您必须编写代码。

    在运行git clone 后立即尝试对任何特定文件执行此操作存在巨大问题,但您添加了以下注释:

    总历史会更好,但我只能解决我的一个分支。我希望能够在不与 Git 交互的情况下查找全部历史记录。

    在这种情况下,有一条明显的前进道路。我将为您概述一个想法,但您必须编写代码。如果你对 Git 有很多了解,请跳到底部关于使用 post-commit 钩子的部分。如果没有,请先阅读其余部分。通过编写 post-commit 钩子,您将学到很多关于 Git 的知识,但您可能还需要其他部分。

    首先,请记住未跟踪的文件是什么

    如果你打算使用 Git,Git 会强制你了解它的三个部分:

    • 工作树。这很简单:这是你工作的地方。工作树中的文件以通常的形式存储,您可以在其中查看和使用它们。

    • index,它还有两个名字,因为它在 Git 中非常重要:它也称为 暂存区,有时也称为 缓存时间>。索引中的文件采用特殊的仅 Git 格式。这里的关键是您可以替换索引中的文件,因此它们是可写的。

    • 提交。提交是永久的、只读的和不可破坏的。1 Git 中的提交历史:没有“文件历史”之类的东西。每个提交都是一个完整的快照,其内容独立于其他所有快照。 Git 通过保存(提交)索引的内容来创建新的快照。

    未跟踪的文件是不在索引中的文件。这是 Git 简单明了的罕见情况。 :-) 如果您在工作树中有一个不在索引中的文件,则它是未跟踪的。您的所有landing.html.<em>suffix</em> 文件都将不被跟踪。


    1提交的持久性取决于它们的可达性。正如下面关于提交的部分所述,Git finds 从分支名称(或任何其他标识提交的名称)开始提交。这些提交通过它们的哈希 ID 标识它们的父级,因此父级可以从分支提示中访问。父母会识别更多父母,因此这些父母也是可以访问的。 Git 很少(因为它需要很长时间)计算可到达提交集的传递闭包——实际上是可到达的对象——并将其与对象数据库的全部内容进行比较。此时,无法访问的对象可能会被垃圾收集(丢弃),具体取决于其他条件。

    清廉取决于它们是只读的和散列的。如果对象内部发生某种变化,它将不再匹配其(加密)哈希 ID,Git 会知道它已损坏。


    关于提交的一些注意事项

    (这些都不是直接相关的,但记住这一切很有用。)

    像所有 Git 的内部对象一样,提交由它们的哈希 ID 标识(命名)。对象的哈希 ID(包括每次提交)是其内容的加密校验和。每个提交的实际内容非常小,因为存储的快照是通过一个名为 tree 的单独 Git 对象完成的:Git 将索引变成一棵树,然后保存树的哈希 ID 以及您的提交元数据(您的姓名和电子邮件地址、一些时间戳、您的日志消息和提交的 哈希 ID)作为提交对象。

    存在分支,以及存储库中的历史记录,因为提交存储父 ID。像 master 这样的分支 name 只保存一 (1) 个提交哈希 ID。 Git 将此称为 tip commit,根据定义,它是分支上的 last 提交,即最新的。要查找历史记录,Git 会查看提示提交的父提交,即倒数第二个。然后Git查看parent的parent,也就是倒数第三个;等等。因此,生成的提交链就是分支,由分支 name 找到,它仅标识最尖端的提交:

            D--E   <-- master
           /
    A--B--C
           \
            F--G   <-- develop
    

    提交AE 都在分支master 上,提交AC 加上FG 都在分支develop 上。 请注意,有些提交在多个分支上。存储在存储库中的历史记录只是存储在存储库中的所有提交的总和.请注意,namesmasterdevelop 在此处仅标识 一个 提交。

    如果您愿意,您可以创建一个具有单个线性分支的存储库,其中每个提交都与之前的提交完全无关。更有用(但仍然故意歪曲),您可以创建一个存储库,其中每个 other 提交都有一个 不同 项目,这样如果您签出第一个提交,您获得项目 A 的初步尝试。如果您签出第二次提交,您将获得项目 B 的初始尝试。第三次commit是A的第二次commit;第四个commit是B的第二个commit;等等。也就是说,一个偶数提交N就是Project B,commit N/2;奇数提交是 ProjectA,提交 floor((N+1)/2)。

    这里的关键点是提交不是变更集。如果同一文件在连续多次提交中连续出现多次,则每次提交都有自己独立的该文件副本。确实,在 Git 的下层深处,它们都共享文件的一个“真实副本”(对于相同的对象来说,这真的很容易 Git 要做;对于细微的变化,Git 必须将对象放入它所谓的 pack 文件中以对它们进行增量压缩)。

    这真正的意思是,为了谈论一个文件或一组文件发生的事情,你必须选择一些提交来比较,一次一对提交. 显而易见的做法是比较每个父/子对。只要提交是线性的

    ... G--H--I--J   <-- develop
    

    在这里,G-H 对、H-I 对和I-J 对进行有用的比较。但假设这是以下内容的一部分:

            D--E
           /    \
    A--B--C      M   <-- master
           \    /
            F--G--H--I--J   <-- develop
    

    commit Mmaster 上的合并提交,此时有人将develop 合并到master。提交M两个 父母,而不仅仅是一个:你会将MEG 进行比较吗?与此同时,分支在C 处分叉,所以C 有——此刻;我们可以随时添加更多!——两个孩子。您会将CDCF 进行比较吗?这些是真正棘手的部分,你可以通过“只用我的和一个分支解决”来避免。

    提交

    您无疑已经知道,提交的过程包括执行以下步骤:

    1. 检查一些分支名称:这使其提示提交成为当前提交。关于这一点有一些重要的事实:特别是它如何影响索引和工作树。我们稍后再讨论。
    2. 在工作树中进行更改。工作树中的文件有它们普通的读/写形式,所以这很容易。
    3. 运行git add。这实际上是将更新后的文件从工作树复制到索引中,替换未编辑的索引文件。
    4. 运行git commit。这会收集您的提交日志消息,然后生成实际的提交对象。

    提交的棘手部分是将索引转换为树对象(有一个单独的命令 git write-tree,如果您想手动执行所有操作,可以运行该命令)。一旦 Git 有了树对象,它就可以写出提交的文本:

    tree <hash>
    parent <hash>
    author <name> <email> <timestamp>
    committer <name> <email> <timestamp>
    <log message>
    

    然后把它变成一个提交对象(如果你愿意,你也可以手动完成这部分,使用git hash-object -w -t commit)。创建对象通过计算文本的加密校验和来创建对象的哈希 ID。只要这个提交不同于其他所有提交——时间戳加上其余内容确保它是不同的,因为时间总是在增加2——它就会得到一个新的、不同的——每隔一个提交的哈希 ID。请注意,parent &lt;hash&gt; 行使用当前提交的哈希 ID — 您在步骤 1 中签出的那个。

    Git 然后简单地将新提交的哈希 ID 写入分支名称,以便当前分支(您在步骤 1 中签出的分支)现在将 new 提交标识为其提示。最后,这就是你可以做你想做的事的地方,git commit 运行一个 post-commit 钩子

    上面的内容可能会让人困惑,所以让我们举个例子,一个简单的三提交存储库变成四提交存储库:

    A--B--C   <-- master (HEAD)
    

    名称master 指向提交C。你git checkout master,进行一些更改,git addgit commit 并创建新的提交D。新提交指向 C 作为其父级:

    A--B--C   <-- master (HEAD)
           \
            D
    

    然后 Git 快速将名称 master 向右滑动,使其指向新的提交 D

    A--B--C
           \
            D   <-- master (HEAD)
    

    之后,我们通常会将绘图拉直,使其再次看起来像一条简单的线。

    请注意,您可以运行git commit --amend,这会使新提交以当前提交的父级 作为其父级。也就是说,我们可以让D 指向B,而不是让D 指向C

    A--B--C
        \
         D   <-- master (HEAD)
    

    这使历史记录转到D -&gt; B -&gt; A,跳过C(它已变得无法访问,最终将被垃圾收集)。换句话说,我们实际上并没有改变历史——C 仍然在那里,只是不再在我们的历史链接中——但它看起来我们已经改变了。如果您以后会使用git commit --amend,请在以后的 Git 挂钩中记住这一点。

    (Git 的 git rebase 具有类似的效果,但更加剧烈:它将 多个 提交复制到新的提交,放弃原来的提交。)


    2如果通过诡计和诡计(或仅通过运行使用诡计和诡计的git filter-branch),您设法做出与现有的逐位相同的新提交提交——它具有相同的作者和提交者、相同的时间戳、相同的父节点、相同的源快照和相同的日志消息——然后您将重用 old 提交的哈希 ID。但那又怎样?您刚刚做了一个与旧提交完全相同的新提交。它具有相同的作者,同时制作,具有相同的历史,并且具有相同的日志消息。它旧的提交。

    这里有一个奇怪的案例,当两个分支名称都指向同一个提示提交时,在两个不同的分支名称签出上非常快(在一秒钟内)进行两个相同的提交。这会导致分支名称最终指向一个共享的新提交,即使您希望它们指向两个不同的提交,并且如果进程跨越了一个时钟滴答,它们也会如此。从图论的意义上说,结果是正确的,并且有效;但令人惊讶。


    填写空白,或者更确切地说,填写索引和工作树

    我提到上面的第 1 步 - git checkout <em>branch-name</em> 步骤 - 对索引和工作树有重要影响。请注意,当 Git 在上面进行新提交时,它首先使用 git write-tree 写出索引以创建树对象。这意味着索引必须从当前提交开始匹配3

    git checkout 命令通过将当前(签出前)提交与目标(签出后)提交进行比较来实现此目的。当前提交有一些文件,而目标提交有另一组文件,大概至少有点不同。 Checkout 将从当前索引和工作树中删除那些必须删除的文件。它会将所有必须添加的文件添加到当前索引和工作树中。它将在索引和工作树中替换任何必须换出的文件,以从旧提交转到新提交。

    因此,在git checkout 之后,索引和工作树将(除了根本不在索引中的未跟踪文件)与刚刚成为当前提交的目标提交匹配。

    还要注意,当您运行git commit 时,这会使用当前索引进行新的提交。结果是一旦新的提交完成,当前的提交和索引再次匹配。所以我们得到了一个关于 Git 的基本(虽然有点灵活,见脚注 3)真相:索引通常匹配当前提交,直到你开始 git adding 从工作树复制文件。


    3实际上,结账时允许有一些差异。详情请见Checkout another branch when there are uncommitted changes on the current branch


    使用 post-commit 钩子来获得你想要的东西

    Git 在git commit 成功完成后立即运行您的提交后挂钩。这个git commit 做了一个新的提交,例如在我们将三提交存储库转换为四提交存储库的示例中提交D

    新提交有一个,例如C。现在您有机会比较父母与孩子:

    git diff --name-status HEAD^ HEAD
    

    例如。 (HEAD 是当前的,即子级,提交,HEAD^ 表示查看HEAD 的第一个父级。请记住这里有多个父级的合并提交:你例如,可以使用HEAD^2 查看合并的 second 父级。我不确定git merge 是否运行提交后挂钩,当git merge进行合并提交,尽管我怀疑它确实如此。)git diff --name-status 的输出告诉您它打印的每个文件发生了什么;详情请参阅the git diff documentation4

    此时,如果landing.html等文件发生了变化(状态M),或者创建了新文件(状态A),可以在下一个版本下复制该文件编号,并使用提交日志消息主题 (git log -1 --pretty=format:%s HEAD)。如果文件没有更改,您将不会得到任何输出——git diff 什么也没说,因为没有什么可说的——所以你不要复制。

    结果,随着时间的推移,您将在您的工作树中构建您想要作为您的历史记录的未跟踪文件,按您提交这些提交的顺序编号。为了使编号有意义,您甚至可以检查您在哪个分支上(如果有的话——在“分离 HEAD”模式下,例如当您查看历史提交时,HEAD 根本不附加到分支名称)。请注意,您可以使用git rev-parse --abbrev-ref HEADgit symbolic-ref --short HEAD 来获取分支名称。5


    4对于脚本,你真的应该使用git diff-tree,这样更可预测。例如,它不遵守每个用户的配置控制,因此它对每个人的行为都相同。 git diff 将查看您的 diff.renames 设置、diff.renameLimit 等等,以及差异输出着色选项,所有这些都可能与脚本编写混淆。

    5两者的区别在于git symbolic-ref失败(退出非零),并且不产生标准输出(但会默认写入stderr),如果 HEAD 分离。对于这种情况,git rev-parse 只会打印 HEAD

    【讨论】:

    • Waho,感谢您提供非常完整的答案。我总是Git 是如此复杂,它从来没有真正完全符合我的要求。我将在本周末实施一个完整的解决方案。
    猜你喜欢
    • 2011-04-27
    • 2021-05-29
    • 1970-01-01
    • 2012-07-06
    • 1970-01-01
    • 2019-05-05
    • 2013-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多