高级Git

本章将介绍git的一些高级用法,这些用法超出了使用分支所需的范围。这些特性可以在Sage开发中使用,但对于Sage来说并不是必需的。如果你刚刚开始与Sage发展,你应该阅读 Sage开发过程 相反。如果您是git新手,请参阅 艰难的路 .

分离头和检票

每次提交都是Sage源树在某一点的快照。到目前为止,我们一直使用在分支中组织的提交。但实际上分支只是一个特定提交的快捷方式,分支的头提交。但是你可以直接去一个没有分支的提交,这叫做“分离头”。如果您的本地历史记录中已经有提交,则可以直接签出它,而不需要访问internet::

[user@localhost sage]$ git checkout a63227d0636e29a8212c32eb9ca84e9588bbf80b
Note: checking out 'a63227d0636e29a8212c32eb9ca84e9588bbf80b'.

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 a63227d... Szekeres Snark Graph constructor

如果它没有存储在本地git存储库中,则需要先从trac服务器下载它:

[user@localhost sage]$ git fetch trac a63227d0636e29a8212c32eb9ca84e9588bbf80b
From ssh://trac/sage
 * branch            a63227d0636e29a8212c32eb9ca84e9588bbf80b -> FETCH_HEAD
[user@localhost sage]$ git checkout FETCH_HEAD
HEAD is now at a63227d... Szekeres Snark Graph constructor

不管是哪种方式,您最终得到的是与任何本地分支无关的当前头和工作目录:

[user@localhost sage]$ git status
# HEAD detached at a63227d
nothing to commit, working directory clean

这很好。您可以切换到现有的分支(使用 git checkout my_branch )回到你那超脱的头上。

在检票时,可以使用分离的头。只需检查一下您作为分离头正在审阅的提交(查看trac票证上的“提交:”字段)。然后可以查看分离的测试头,然后运行这些更改。当你完成了复习,你就放弃了那超脱的脑袋。这样就不会创建新的本地分支,因此不必键入 git branch -D my_branch 最后删除您创建的仅用于审核票证的本地分支。

将分支更新到最新的SageMath版本(并最小化重新编译时间)

  • 你有一个编译和工作的新版本 n

  • 你想在树枝上工作吗 some_code 这是基于一些古老的萨格数学版本 o

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

  • 只需重新编译更改的文件(而不是所有来自 on

  • 然后继续阅读本节。

介绍

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

注解

继续使用基于旧分支的特性是非常好的,通常不需要在这个最新的SageMath版本中合并。

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

  • 如果与最新版本或

  • 需要一个最新的功能或

  • 只是因为旧的SageMath版本在您的机器上不再可用。

然后在最新的SageMath版本中进行合并。

合并到最新的SageMath版本中

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

假设我们在目前的工作部门 some_code (分支已签出)。然后:

git merge develop

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

但是,在合并之后,我们需要(部分地)重新编译SageMath。有时,这可能需要很长时间(因为许多文件被触摸,它们的时间戳被更新),有一种方法可以避免它。

最小化重新编译时间

假设我们正在学习一些新的圣哲数学(例如,在branch上 develop )它已经被编译并成功运行,我们有一个“老”分支 some_code ,我们希望将其添加到这个SageMath版本中(而不会触发不必要的重新编译)。

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

git worktree add new_worktree
cd new_worktree

我们有新的文件来源。因此,不会更改原始存储库的时间戳等。现在我们合并:

git checkout some_code
git merge develop

回到我们原来的存储库:

git checkout develop
cd ..

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

git checkout some_code

我们还需要打电话:

make

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

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

rm -r new_worktree

为什么不换个方向合并呢?

正在学习一些新的圣哲数学(例如在branch上 develop )如果运行成功,就有可能合并到我们的分支机构 some_code 发展。这将生成相同的源文件并避免不必要的重新编译。然而,这使得阅读git的历史非常不愉快:例如,很难跟踪变化等等,因为不能简单地跟踪每个git提交的第一个父级。

重置和恢复

Git很难真正搞砸。不管发生什么,这里有一条捷径可以让你重新站起来。首先,如果您只想回到一个正常工作的Sage安装,您可以随时放弃您的工作分支,切换到您的本地副本 master 分支机构:

[user@localhost sage]$ git checkout master

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

如果您希望保留分支但又返回到以前的提交,则可以使用 重置 命令。为此,请在日志中查找commit,它是大约40位的十六进制数(SHA1散列)。然后使用 git reset --hard 要将文件还原到以前的状态:

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

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

警告

任何 未承诺的 更改将丢失!

你只需要输入前两个十六进制数字,如果这不是唯一指定提交,git会抱怨。还有一个有用的缩写 HEAD~ 对于上一次提交和 HEAD~n ,带有一些整数 n ,用于第n次提交。

最后,也许最终的人为错误恢复工具是reflog。这是git操作的时间顺序,如果需要,可以撤消这些操作。例如,假设我们把 git重置 命令并返回太远(例如,返回5个提交)。除此之外,还删除了一个文件并提交:

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

现在我们不能只从重置之前签出存储库,因为它已不在历史记录中。不过,这里有一个反思:

[user@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重置 命令,我们只要重新回到未来:

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

改写历史

Git允许您重写历史,但要小心:提交的SHA1散列包括父级的散列。这意味着哈希实际上依赖于工作目录的整个内容;每个源文件的状态与计算哈希时的状态完全相同。这也意味着您不能在不修改哈希的情况下更改历史记录。如果其他人把你的代码分支了,然后你重写了历史,那么其他人就彻底完蛋了。所以,理想情况下,您只需要在尚未提交给trac的分支上重写历史。

作为一个高级示例,考虑三个相互重叠的提交A、B、C。为了简单起见,我们假设他们只是添加了一个名为 file_A.pyfile_B.pyfile_C.py ::

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

现在,让我们假设commit B是真正独立的,应该在单独的罚单上。所以我们想把它转移到一个新的分支,我们会打电话给它 second_branch . 首先,在添加一个::

[user@localhost]$ 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
[user@localhost]$ git checkout -b second_branch
Switched to a new branch 'second_branch'
[user@localhost]$ git branch
  first_branch
* second_branch
[user@localhost]$ git log --oneline
5b5588e base commit

现在,我们在当前分支中复制commit B::

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

注意,这会更改提交B的SHA1,因为它的父级已更改!还有,樱桃采摘 副本 提交时,它不会将它们从源分支中移除。因此,我们现在必须修改第一个分支以排除提交B,否则将有两个提交添加 file_B.py 我们的两个分支后来合并为Sage时会发生冲突。因此,我们首先将第一个分支重置回添加B之前:

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

现在我们仍然需要提交C,所以我们再次选择它。请注意,即使此时commit C不包含在任何分支中,也可以这样做:

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

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

交互式重基

另一种方法 改写历史 是使用交互式重基功能。这将打开一个编辑器,您可以在其中修改最近的提交。同样,这将自然地修改所有已更改提交及其所有子提交的哈希值。

现在我们从第一个分支的相同分支开始:

[user@localhost]$ git log --oneline
9621dae added file C
7873447 added file B
bf817a5 added file A
5b5588e base commit
[user@localhost]$ git checkout -b second_branch
Switched to a new branch 'second_branch'
[user@localhost]$ 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。