如何使git rebase和保持提交时间戳? [英] How to make a git rebase and keep the commit timestamp?

查看:190
本文介绍了如何使git rebase和保持提交时间戳?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想进行重新组织以从我的历史记录中删除某个提交。我知道该怎么做。但是,如果我这样做,提交时间戳就设置为我完成rebase的那一刻。我想提交保留时间戳。

我在这里看到了最后一个答案: https://stackoverflow.com/ a / 19522951/3995351 ,但它没有奏效。

最后一个重要命令显示了新行:

 > ; 

所以我打开一个新问题。

解决方案

设置



假设这是围绕提交你想删除

  ... o  -  o  -  o  -  o ... ... o 
^ ^ ^ ^
| | + - 下一个|
| + - 坏+ - 主(头)
开始

其中:




  • bad 是您要删除的提交;
  • start 是要删除的提交的父项;
  • 下一个 bad 之后的下一个提交。它是好的,你想保持它,并在它之后的所有时间表;它将在rebase之后替换 bad

    $ b

    先决条件



    为了能够安全地移除 bad ,重要的是当<时不存在其他分支在 bad 之后,创建的被合并到主时间轴中。即通过从历史图中删除 bad 及其与父级和子级提交的连接,可以获得两个断开连接的时间线块。



    即使在 bad 之后合并了另一个现有分支,也可能删除 bad 。我没有检查这种情况,但我期望因合并提交而产生一些障碍。



    想法



    每个 git 提交都由使用提交的属性(内容,消息,作者和提交者日期和电子邮件)计算的哈希标识。



    rebase总是改变提交者的日期。它还可以更改提交者电子邮件,提交邮件和内容。



    为了恢复rebase之后的原始提交者日期,我们需要将它们与一些信息一起保存确定rebase之后的每个提交。



    因为您想要修改提交,所以提交内容会在rebase期间更改。添加或删除文件或提交会更改所有将来提交的内容。



    这使我们没有一个属性来唯一标识提交,并且在期望的rebase期间不会更改。我们可以尝试使用两个或更多在rebase期间不会更改的属性。



    电子邮件(作者和提交者)几乎没有用处。如果有一个人从事该项目,他们对所有提交都是相同的,并且不能使用。保留的属性(在大多数提交时不同,不受rebase影响)是 author date commit message (第一行)。



    如果pair(author date,commit message)为所有被rebase影响的提交提供唯一值,那么我们可以在没有错误的情况下恢复提交日期。



    验证它是否可以安全地完成



    有一种简单的方法可以验证(作者日期,提交消息)对对于受影响的人是否是唯一的提交。



    运行以下两个命令:

      $ git log --format =%aI%sstart ... master | uniq | wc -l 
    $ git log --oneline start ... master | wc -l

    如果它们显示相同的数字,那么您很幸运:对(作者日期,提交消息)可以用来唯一标识提交。阅读。



    如果数字不同(第一个命令总是会产生一个小于或等于第二个命令产生的数字),那么您已经不在



    提取修复rebase之后的提交日期所需的信息



    这个命令

      $ git log --format =%H%cI%aI%sstart ... master> / tmp / hashlist 

    提取提交哈希,提交者日期(有效负载),作者日期和提交消息(key),以 start 开头的所有提交并将它们存储在一个文件中。



    备份当前主设备



    尽管 git 重写历史记录是一种常见的误解,实际上它只是生成一个替代历史记录行并决定这是正确的历史。它不会更改或删除重写的提交;它们在数据库中仍然存在一段时间,并且在操作失败的情况下可以恢复。

    我们可以主动备份当前的历史记录行,以便在需要时轻松恢复。我们所要做的就是创建一个指向 master 的新分支。这样,当 git rebase 移动 master 到新时间轴时,旧的仍然可以使用新分支访问。

      $ git branch old_master 

    上面的命令创建了一个名为 old_master 的分支,它将当前时间线保持在焦点上,直到完成所有更改并对新的世界秩序感到满意。



    执行rebase



    删除提交 bad 从历史就是这样简单:

      $ git rebase --preserve-merges --onto start bad 



    修复提交日期



    以下命令重写历史记录,并使用我们之前保存的值更改提交者日期:
    $ b $ pre $ git filter-branch --env-filter'export GIT_COMMITTER_DATE = $(fgrep -m 1$(git log -1 --format =%aI%s$ GIT_COMMIT)/ tmp / hashlist | cut -d-f2)'-f start ... master

    运作方式

    git 在标记为 start master 的提交以及for在重写提交之前,每次提交都会运行提供的参数作为 - env-filter 的参数。它设置了环境变量 GIT_COMMIT ,其中重写的提交的哈希值为



    由于我们已经做了一个 rebase 修改了所有提交的哈希,我们不能直接使用 $ GIT_COMMIT 来标识提交的原始提交日期(因为 $ GIT_COMMIT 是由 git rebase 生成的提交,我们对其提交者日期不感兴趣)。



    我们提供给 - env-filter



    <$ p $的命令p> export GIT_COMMITTER_DATE = $(fgrep -m 1$(git log -1 --format =%aI%s$ GIT_COMMIT)/ tmp / hashlist | cut -d-f2 )

    运行 git log -1 --format =%aI%s $ GIT_COMMIT 来生成上面讨论的密钥对(作者日期,提交消息)。它的输出作为参数传递给命令 fgrep -m 1.../ tmp / hashlist |剪切-d-f2 ,它在先前保存的散列列表中找到该对( fgrep ),并从保存的内容中提取原始提交日期行( cut )。最后,提交日期的值存储在由 git 用来重写提交的环境变量 GIT_COMMITTER_DATE 中。



    验证



    使用 git log 命令再次

      $ git log --format =%cI%aI%sstart ... master 

    您可以验证重写的历史记录是否与原始历史记录匹配。如果您使用图形 git 客户端,则可以通过目视检查更容易地检查结果。分支 old_master 可以让旧的历史记录行在客户端可见,您可以轻松地比较 old_master 分支与相应的 master 分支。



    如果出现问题,或者需要修改您可以通过以下方式轻松重新开始:

      $ git reset --hard old_master 



    清理



    当您对结果满意时,您可以删除备份分支和文件用于存储原始提交日期:

      $ git分支-D old_master 
    $ rm / tmp / hashlist

    这就是全部!


    I want to make a rebase to remove a certain commit from my history. I know how to do that. However if I do it, the commit timestamp is set to the moment I completed the rebase. I want the commits to keep the timestamp.

    I saw the last answer here: https://stackoverflow.com/a/19522951/3995351 , however it didn't work.

    The last important command just showed a new line with

    >
    

    So I am opening a new question.

    解决方案

    The setup

    Let's say this is the history around the commit you want to remove

    ... o - o - o - o ...       ... o
            ^   ^   ^               ^
            |   |   +- next         |
            |   +- bad              +-- master (HEAD)
          start
    

    where:

    • bad is the commit you want to remove;
    • start is the parent of the commit you want to remove;
    • next is the next commit after bad; it is good, you want to keep it and all the timeline after it; it will replace bad after rebase.

    Prerequisites

    In order to be able to safely remove bad, it's important that no other branch existing at the time when bad was created was merged into the main timeline after bad. I.e. by removing bad and its connections with its parent and child commits from the history graph, you get two disconnected timeline pieces.

    It is probably possible to remove bad even if another existing branch was merged after bad. I didn't check this situation but I expect some impediments because of the merge commit.

    The idea

    Each git commit is identified by a hash that is computed using the commit's properties: content, message, author and committer date and email.

    A rebase always changes the committer date. It can also change committer email, commit message and content too.

    In order to restore the original committer dates after a rebase we need to save them together with some information that can identify each commit after the rebase.

    Because you want to modify a commit, the commit contents change during the rebase. Adding or removing files or commits change the contents all future commits.

    This leave us without a property that uniquely identifies the commits and does not change during the desired rebase. We can try to use two or more properties that do not change during the rebase.

    The emails (author and committer) are of almost no use. If there is a single person that worked on the project, they are the same for all commits and cannot be used. The properties that remains (are different on most commits, are not affected by the rebase) are author date and commit message (the first line).

    If the pair (author date, commit message) provides unique values for all the commits affected by the rebase then we can restore the commit dates afterwards without errors.

    Verify if it can be done safely

    There is a simple way to verify if the (author date, commit message) pairs are unique for the affected commits.

    Run the following two commands:

    $ git log --format="%aI %s" start...master | uniq | wc -l
    $ git log --oneline start...master | wc -l
    

    If they display the same number then you are lucky: the pair (author date, commit message) can be used to uniquely identify the commits. Read on.

    If the numbers are different (the first command will always produce a number smaller than or equal to the one produced by the second command) then you are out of luck.

    Extract the information needed to fix the commit dates after the rebase

    This command

    $ git log --format="%H %cI %aI %s" start...master > /tmp/hashlist
    

    extracts the commit hash, committer date (the payload), author date and commit message (the key) for all the commits starting with start and stores them in a file.

    Backup the current master

    While it is a common misconception that git "rewrites history", in fact it just generates an alternative history line and decides it is the correct history. It does not change or remove the "rewritten" commits; they are still present for some time in its database and can be restored in case the operation fails.

    We can proactively backup the current history line to easily restore it if needed. All we have to do is to create a new branch that points to master. This way, when git rebase moves master to the new timeline, the old one is still accessible using the new branch.

    $ git branch old_master
    

    The command above creates a branch named old_master that keeps the current timeline in focus until we complete all the changes and are satisfied with the new world order.

    Do the rebase

    Removing the commit bad from the history is as simple as:

    $ git rebase --preserve-merges --onto start bad
    

    Fix the commit dates

    The following command "rewrites" the history and changes the committer date using the values we saved before:

    $ git filter-branch --env-filter 'export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)' -f start...master
    

    How it works:

    git walks the history between the commits labelled start and master and for each commit it runs the command provided as argument to --env-filter before rewriting the commit. It sets the environment variable GIT_COMMIT with the hash of the commit being rewritten.

    Since we already did a rebase that modified the hashes of all the commits we cannot use $GIT_COMMIT directly to identify the original commit date of the commit (because $GIT_COMMIT is a commit generated by git rebase and we are not interested in their committer dates).

    The command we provide to --env-filter

    export GIT_COMMITTER_DATE=$(fgrep -m 1 "$(git log -1 --format="%aI %s" $GIT_COMMIT)" /tmp/hashlist | cut -d" " -f2)
    

    runs git log -1 --format="%aI %s" $GIT_COMMIT to generate the key pair (author date, commit message) discussed above. Its output is passed as argument to the command fgrep -m 1 "..." /tmp/hashlist | cut -d" " -f2 that finds the pair in the list of previously saved hashes (fgrep) and extracts the original commit date from the saved line (cut). Finally, the value of the commit date is stored in the environment variable GIT_COMMITTER_DATE that is used by git to rewrite the commit.

    Verification

    Using the git log command again

    $ git log --format="%cI %aI %s" start...master
    

    you can verify that the rewritten history matches the original history. If you use a graphical git client you can check the results easier by visual inspection. The branch old_master keeps the old history line visible in the client and you can easily compare the dates of each commit of old_master branch with the corresponding one of master branch.

    If something didn't go well or you need to modify the procedure you can easily start over by:

    $ git reset --hard old_master
    

    Cleanup

    When you are satisfied by the result you can remove the backup branch and the file used to store the original commit dates:

    $ git branch -D old_master
    $ rm /tmp/hashlist
    

    That's all!

    这篇关于如何使git rebase和保持提交时间戳?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆