高级Git

本章介绍了GIT的一些高级用法,这些用法超出了使用分支所需的范围。这些功能可以在Sage开发中使用,但并不是真正为Sage做出贡献所必需的。如果您刚刚开始使用Sage开发,您应该阅读 开发演练Git基础知识 取而代之的是。

独立的负责人和审查PR

每次提交都是某个时间点的Sage源树的快照。到目前为止,我们总是使用在分支中组织的提交。但实际上,分支只是特定提交的捷径,即分支的头提交。但是您可以只执行特定的提交,而不使用分支,这称为“分离头”。如果您的本地历史记录中已有提交,则无需访问互联网即可直接查看::

[alice@localhost sage]$ git checkout f9a0d54099d758ccec731a38929902b2b9d0b988
Note: switching to 'f9a0d54099d758ccec731a38929902b2b9d0b988'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at f9a0d54099 Fix a slow doctest in matrix_integer_dense_hnf.py

如果它没有存储在您本地的Git存储库中,则需要从 upstream 回购优先::

[alice@localhost sage]$ git fetch upstream f9a0d54099d758ccec731a38929902b2b9d0b988
From https://github.com/sagemath/sage
 * branch                  f9a0d54099d758ccec731a38929902b2b9d0b988 -> FETCH_HEAD
[alice@localhost sage]$ git checkout FETCH_HEAD
HEAD is now at f9a0d54099 Fix a slow doctest in matrix_integer_dense_hnf.py

无论采用哪种方式,最终都会得到与任何本地分支机构都没有关联的当前头目录和工作目录::

[alice@localhost sage]$ git status
HEAD detached at f9a0d54099
nothing to commit, working tree clean

这太好了。您可以切换到现有分支机构(使用通常的 git checkout my_branch ),回到你那超然的头脑。

在审查公关时,独立的头脑可以成为你的优势。只需将您正在审查的提交(查看PR的“Commits”选项卡)作为独立的头部进行检查即可。然后,您可以查看更改并在分离的头部中运行测试。当你完成复习时,你就放弃了超然的头脑。这样,您就不会创建新的本地分支机构,因此您不必键入 git branch -D my_branch 在结尾处删除您创建的本地分支机构,该分支机构仅用于审查票证。

将分支更新到最新的Sage版本

  • 您有一个经过编译并可正常工作的新SageMath版本 n ,以及

  • 您想要在分支机构工作 some_code 它是基于某个旧的SageMath版本 o

  • 通过从版本更新此分支 on

  • 仅重新编译更改的文件(而不是所有来自 on ),

  • 然后继续阅读这一节。

引言

在开发时,经常会得到一个不是基于最新(测试版)SageMath版本的分支。

备注

继续使用基于旧分支的功能是完全可以的,通常不需要合并到最新的SageMath版本中。

然而,有时需要合并,例如

  • 如果与最新版本冲突或

  • 用户需要最新的功能或

  • 很简单,因为旧的SageMath版本在您的计算机上不再可用。

然后,必须合并到最新版本的SageMath中。

合并到最新的Sage版本

(这是不会最小化重新编译时间的简单方法。)

假设我们在当前的工作分支上 some_code (分支已检出)。然后::

git merge develop

进行合并,即我们将最新的开发版本合并到我们的工作分支中。

然而,在这次合并之后,我们需要(部分)重新编译SageMath。有时,这可能需要很长时间(因为许多文件都会被触及并更新它们的时间戳),而且有一种方法可以避免这种情况。

最大限度地减少重新编译时间

假设我们在某个新的SageMath上(例如,在分支上 develop ),它已经编译并成功运行,并且我们有一个“旧”分支 some_code ,我们希望将其引入到此SageMath版本中(而不会触发不必要的重新编译)。

我们首先在目录中创建一个新的工作树 new_worktree 并切换到此目录::

[alice@localhost sage]$ git worktree add new_worktree
[alice@localhost sage]$ cd new_worktree

这里我们有一个源文件的新副本。因此,不会更改原始存储库的时间戳等。现在我们执行合并::

[alice@localhost sage/new_worktree]$ git checkout some_code
[alice@localhost sage/new_worktree]$ git merge develop

并返回我们的原始存储库::

[alice@localhost sage/new_worktree]$ git checkout develop
[alice@localhost sage/new_worktree]$ cd ..

我们现在可以安全地结账了 some_code **

[alice@localhost sage]$ git checkout some_code

我们仍然需要调用::

[alice@localhost sage]$ make

但只有更改过的文件才会被重新编译。

要删除新的工作树,只需使用::

[alice@localhost sage]$ rm -r new_worktree

为什么不反过来合并呢?

在某个新的SageMath上(例如,在分支上 develop )成功运行,则可以在我们的分支机构中合并 some_code 变成了发展。这将生成相同的源文件,并避免不必要的重新编译。然而,这使得阅读Git的历史非常不愉快:例如,很难跟踪变化等,因为一个人不能简单地追求每个Git提交的第一个父项 (git log --first-parent )。

重置和恢复

Git让人很难真正把事情搞砸。无论如何,这里有一条让你重新站稳脚跟的捷径。首先,如果您只想返回到工作的Sage安装,您可以通过切换到您的本地副本 develop 分支机构::

[alice@localhost sage]$ git checkout develop

只要您没有对 develop 直接分支,这将带给您一个工作的Sage。

如果您希望保留分支但返回到以前的提交,则可以使用 reset 指挥部。为此,在日志中查找COMMIT,该日志是某个40位十六进制数(SHA1散列)。然后使用 git reset --hard 要将文件恢复到以前的状态,请执行以下操作:

[alice@localhost sage]$ git log
...
commit eafaedad5b0ae2013f8ae1091d2f1df58b72bae3
Author: First Last <alice@email.com>
Date:   Sat Jul 20 21:57:33 2013 -0400

    Commit message
...
[alice@localhost sage]$ git reset --hard eafae

警告

任何 uncommitted 更改将丢失!

你只需要输入前几个十六进制数字,如果这没有唯一地指定一个提交,Git就会抱怨。此外,还有一个有用的缩写 HEAD~ 对于上一次提交和 HEAD~n ,并带有某个整数 n ,用于第n次前一次提交。

最后,也许最终的人为错误恢复工具是reflog。这是按时间顺序排列的Git操作历史,如果需要,您可以撤消这些操作。例如,让我们假设我们搞砸了 git reset 命令,并且后退太远(比方说,后退5次)。最重要的是,删除了一个文件并提交:

[alice@localhost sage]$ git reset --hard HEAD~5
[alice@localhost sage]$ git rm sage
[alice@localhost sage]$ git commit -m "I shot myself into my foot"

现在,我们不能只签出重置前的存储库,因为它不再存在于历史中。然而,以下是参考日志::

[alice@localhost sage]$ git reflog
2eca2a2 HEAD@{0}: commit: I shot myself into my foot
b4d86b9 HEAD@{1}: reset: moving to HEAD~5
af353bb HEAD@{2}: checkout: moving from some_branch to master
1142feb HEAD@{3}: checkout: moving from other_branch to some_branch
...

这个 HEAD@{n} 修订是Git运营历史的捷径。既然我们想倒带到之前的错误 git reset 命令,我们只需重置回到未来::

[alice@localhost sage]$ git reset --hard HEAD@{2}

重写历史

Git允许您重写历史,但要小心:提交的SHA1散列包括父对象的散列。这意味着散列实际上依赖于工作目录的整个内容;每个源文件都处于与计算散列时完全相同的状态。这也意味着在不修改散列的情况下无法更改历史。如果其他人从您的代码分支,然后您重写历史,那么其他人就彻底完蛋了。因此,理想情况下,您只需在尚未推动公开回购的分支机构上重写历史。

作为一个高级示例,考虑三个相互重叠的提交A、B、C。为简单起见,我们假定他们刚刚添加了一个名为 file_A.pyfile_B.py ,以及 file_C.py **

[alice@localhost sage]$ git log --oneline
9621dae added file C
7873447 added file B
bf817a5 added file A
5b5588e base commit

现在,让我们假设委托B是真正独立的,应该在单独的票证上。所以我们想把它移到一个新的分支,我们称之为 second_branch 。首先,在添加A::之前,在基本提交处分支

[alice@localhost sage]$ git checkout 5b5588e
Note: checking out '5b5588e'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 5b5588e... base commit
[alice@localhost sage]$ git checkout -b second_branch
Switched to a new branch 'second_branch'
[alice@localhost sage]$ git branch
  first_branch
* second_branch
[alice@localhost sage]$ git log --oneline
5b5588e base commit

现在,我们在当前分支中创建提交B的副本::

[alice@localhost sage]$ git cherry-pick 7873447
[second_branch 758522b] added file B
 1 file changed, 1 insertion(+)
 create mode 100644 file_B.py
[alice@localhost sage]$ git log --oneline
758522b added file B
5b5588e base commit

请注意,这会更改Commit B的SHA1,因为它的父级更改了!还有,挑剔 copies 提交时,它不会将它们从源分支中删除。因此,我们现在必须修改第一个分支以排除提交B,否则将添加两个提交 file_B.py 后来,当我们的两个分支合并到Sage时,它们会发生冲突。因此,我们首先将第一个分支重置回添加B之前的状态::

[alice@localhost sage]$ git checkout first_branch
Switched to branch 'first_branch'
[alice@localhost sage]$ git reset --hard bf817a5
HEAD is now at bf817a5 added file A

现在我们仍然希望提交C,所以我们再次精挑细选。请注意,尽管此时提交C不包括在任何分支中,但此操作仍然有效:

[alice@localhost sage]$ git cherry-pick 9621dae
[first_branch 5844535] added file C
 1 file changed, 1 insertion(+)
 create mode 100644 file_C.py
[alice@localhost sage]$ git log --oneline
5844535 added file C
bf817a5 added file A
5b5588e base commit

同样,我们注意到Commit C的SHA1更改了,因为它的父级更改了。瞧,现在您有两个分支,第一个包含提交A、C,第二个包含提交B。

交互式重定基数

另一种方法来解决 重写历史 就是使用交互式的重定基址功能。这将打开一个编辑器,您可以在其中修改最近的提交。同样,这将自然而然地修改所有更改的提交及其所有子提交的散列。

现在,我们首先创建与第一个分支相同的分支::

[alice@localhost sage]$ git log --oneline
9621dae added file C
7873447 added file B
bf817a5 added file A
5b5588e base commit
[alice@localhost sage]$ git checkout -b second_branch
Switched to a new branch 'second_branch'
[alice@localhost sage]$ git rebase -i HEAD~3

这将打开一个带有最后3个(对应于 HEAD~3 )承诺和如何修改它们的说明:

pick bf817a5 added file A
pick 7873447 added file B
pick 9621dae added file C

# Rebase 5b5588e..9621dae onto 5b5588e
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

为了只使用Commit B,我们删除了第一行和第三行。然后保存并退出您的编辑器,您的分支现在只包含B Commit。

您仍然需要从第一个分支中删除B COMMIT,因此您将返回 (git checkout first_branch ),然后运行相同的 git rebase -i 命令并删除B Commit。