如何重置 master 并将我的分支保留在 git 中?

How do I reset master and keep my branch in git?

假设我这样做

$ git checkout master
$ touch foo.py
$ git commit -m "oops" foo.py
$ git checkout -b new_branch
$ touch bar.py
$ git commit -m "changes" bar.py

现在,当我尝试推迟 new_branch 上的更改时,我得到

Local branch 'master' is ahead of remote branch 'origin/master'

如何在不丢失 new_branch 上的更改(foo.py、bar.py)的情况下重置 master?

我读了 git reset page,看起来它可能涉及 --keep,但我不知道。

如何在不丢失 new_branch 上的更改(foo.py、bar.py)的情况下重置 master?

Yes, foo.py and bar.py is in your new branch.

  • 您已经从未推送到原点的主分支创建了一个分支。所以当你尝试推送 master 时你得到了错误,因为它领先于远程。

  • 您收到该消息是因为您在本地 master 中进行了更改,但没有将它们推送到远程。

您有多种方法 "solve" 它通常取决于您的工作流程:

  • 在一个好的工作流程中,master 的远程副本应该是好的,而 master 的本地副本只是远程副本的副本。使用此工作流程,您将再也不会收到此消息。

  • 如果您以另一种方式工作并且应该推送您的本地更改,那么只需 git 推送原点,假设原点是您的远程

我会怎么做:

git checkout master
git create -b la_lalalla
touch foo.py bar.py
git add drama.py bar.py
git commit -m "Drama commit"

将分支合并到你的master,这取决于你做merge或者做rebase,然后,

git push origin master. 

您已将两个文件添加到 origin/master。

一开始这可能会让人很困惑,您需要的是正确介绍 Git 如何实现分支;但此时我们将使用改造方法。 :-) 理解这一切的诀窍是 Git 的 提交 是永久不变的,但它的 分支 — 或更多确切地说,分支 名称— 是临时的,实际上大部分是无关紧要的。

在构建 new 提交时,有三件事很重要(它们是 HEADindex、和 work-tree),但是一旦你构建并提交了提交,它就非常永久并且很难 Git 完全失去它。不过,很容易不小心放错地方,所以让我们尽量避免这种情况。 :-)

如果我们完全忽略分支名称,我们可以绘制存储库中存在的提交的 。考虑到你所做的——做出两个新的提交——让我们把它们画成这样,其中圆形 os 代表提交,AB 是你的两个 new 提交:

...--o--o--o
            \
             A
              \
               B

我们可以把它们都画在一条线上,但我想在右边留出空间来写标签。提交 A 是你的 "oops",B 是你的 "changes"。

这张图的主要值得注意之处在于每个提交 指向 (存储哈希 ID)其前一个提交。这意味着提交 B 指向提交 A。提交 A 指向下一个最近的提交,它指向更远的地方,依此类推。

现在我们添加标签——分支名称。最后一个无聊的提交 o 可能仍然有一个标签 origin/master。提交 A 有标签 master,提交 B 有标签 new_branch,所以让我们把它们画在:

...--o--o--o   <-- origin/master
            \
             A   <-- master
              \
               B   <-- new_branch (HEAD)

这就是分支名称的意义和作用:它们是指向提交的指针;他们会为您记住每个提交的大而丑陋的哈希 ID。

当您在某个分支上并进行 new 提交时,分支 name 会随之而来。特殊名称 HEAD 会记住您所在的分支,因此 Git 知道要将哪个名称 移动 到新提交。 (我们至少暂时需要这个,当 masternew_branch 都短暂地指向提交 A 时。)

你现在要做的是移动 master back 指向最后一个无聊的 o 提交。为此,您可以使用 git reset,它可以让您以任意方式移动名称:

git checkout master
git reset --hard origin/master

这个假设(见下面的最后一节)origin/master确实确实指向无聊的最后一个o 提交。 git reset --hard 说:清除我当前的索引和工作树,并根据 HEAD 移动我当前的分支,以便它指向我在这里命名的提交。我们必须先git checkout master,这样HEADmaster。然后 git reset 这样做:

...--o--o--o   <-- master (HEAD), origin/master
            \
             A
              \
               B   <-- new_branch

所以现在你的两个新提交 AB 只能通过 new_branch 而不是通过 master.

找到

(分支名称还可以为您做更多的事情。特别是,它们 保护 提交不被 Git 的 "garbage collector" 删除. 如果一个提交有任何我们可以找到它的名字,它是受保护的。如果它有 no 名字,它就不再受保护。有一些半隐藏的名字可以保护一切虽然——默认至少 30 天——以确保提交不会被意外丢弃,但是通过这些 reflog 名称找到它们很烦人,所以我们尽量不要太依赖它.

分支名称也用于 git push,因此它们很重要。)

(我提到 "trashing" 上面的索引和工作树,这并不完全正确。git reset 命令,使用我们在这里使用它的方式,有三个工作可以做:

  • 移动当前分支
  • 重置索引
  • 重置工作树

它总是做第一个,选择性地 添加 第二个作业,然后选择性地 添加 第三个。 --hard 模式执行全部三个,--mixed 模式执行前两个,而 --soft 模式只执行一个。为了正确理解它们,我们必须仔细查看 indexwork-tree 的定义,我会把它留给其他人questions/answers。不过,这里的关键是您 不想 git reset --hard 直到您将所有内容都保存在提交中。)

如果没有 origin/master(或者指向太远)怎么办?

我们在上面假设有一个方便的标签——所谓的远程跟踪分支——标识我们想要git reset我们的master 指向。如果我们没有这个标签,或者它指向更早的提交,我们必须以其他方式找到该提交。

有很多方法可以做到这一点。最直接的方法是通过其哈希 ID。如果你 运行 git log 在某个分支上,你会看到 "on" 或 "contained in" 该分支的每个提交,通常在 Git 通常的倒退中命令。例如,我们可能会 git log 看到提交 B,它有丑陋的大哈希 ID,然后是提交 A 和它的哈希 ID,然后是无聊的提交 o 和它的又大又丑的哈希 ID。

我们可以使用原始哈希 ID:

git checkout master && git reset --hard <hash-id>

如果我们没有标签。如果我们确实有标签,我们应该直接使用它。

您可能记得通过 git log 从 "A DOG" 获得帮助:

git log --all --decorate --oneline --graph

all 使 Git 显示所有分支和所有远程跟踪分支(以及其他所有内容:标签、"stash"、注释等). decorate 选项将标签名称附加到提交。 oneline 选项使每次提交只显示一行,而 graph 选项使 Git 尝试绘制提交图。