1

所以我在理解 Git 时遇到了一些麻烦。我在 Windows 上使用它,带有远程(安装 GitLab)repo。我有一个 master 分支的本地副本,并且我非常勤奋地git pull经常使用以使我的本地副本保持最新。

所以现在是发布时间(当然……这不是发生这种情况的时候吗?)我想签入我已更改的 ONE 文件。我输入 agit pull但收到错误:

error: Your local changes to the following files would be overwritten by merge:
        <path>/filename.py
Please, commit your changes or stash them before you can merge.
Aborting

我使用git diff了,果然,另一个开发人员在我尝试提交的同一个地方修复了一个错字。快速git status揭示:

Your branch is behind 'origin/master' by 4 commits, and can be fast-forwarded

所以,不知所措,我将我的更改提交到我的本地仓库。

git add <filename>
git commit -m "reworking to ignore logs"

现在,我可以git pull成功了。

git pull origin master 
...
Merge made by the 'recursive' strategy
file1
file2...
....
<filename>
5 files changed, 1 insertion, 63 deletions...

现在,我可以将更改推送到远程仓库

git push

但是:这是我的问题。在远程服务器上,它显示了我的提交和我pull提交后由我的本地引起的文件更改。也就是说,它基本上表明远程文件发生了变化,但实际上已更改为远程存储库 中已经存在的内容。

我的老板惊慌失措地打电话问我为什么要更改所有这些(其他四个)文件,我无法回答。我们最终检查了两个提交(当前和我之前的更改)并对其进行了比较,以证明只有我的一个文件的更改才是不同的,然后我们都松了一口气。

所以我的问题是:我错过了什么?为什么 Git 应该认为我想看到我更改了文件以匹配远程 HEAD,在远程?我已经看到这个关于git squashing的问题,并意识到也有一个git rebase可以帮助我的问题。有人可以帮我理解为什么 Git 会这样做吗?我只是觉得好像我没有完全接受“分布式”范式转变。

4

2 回答 2

1

显然你你的老板都错过了同样的事情,你的老板往往会恐慌。:-)

这是您所做的,以提交的图形形式绘制。最初你在你的回购中有这个:

... - F - G    <-- HEAD=master, origin/master

G当时的最新版本在哪里。但是一个文件中有一个错字,所以你在你的工作树中编辑了那个文件,解决了这个问题。typo(为了方便起见,我将在下面命名该文件。)

$ vim typo
...

你还没有提交这个,但你确实改变了它。然后你做了:

$ git pull

这让你得到了那个错误信息。

现在,在幕后,可以说git pullgit fetch紧随其后git merge。那么让我们看看做了什么git fetch。似乎其他开发人员做了四次提交;让我们将它们绘制到提交树的图形表示中:

              H - I - J - K      <-- origin/master
            /
... - F - G                      <-- HEAD=master

git merge失败是因为其他人也修改了文件typo

所以,现在你做:

$ git add typo
$ git commit -m "reworking to ignore logs"

这会在您当前的分支上添加一个新提交(无论HEAD指向什么),这显然是master. 这给出了一个看起来像这样的提交树:

              H - I - J - K      <-- origin/master
            /
... - F - G ------------- L      <-- HEAD=master

现在你git pull再次运行,它再次fetch紧随其后merge。这次 fetch 没有什么要获取的(H通过K和 label提交origin/master,已经全部处理好了)。然后合并将 commit K( origin/master) 合并到您当前的分支中master,创建一个新的合并提交M

              H - I - J - K      <-- origin/master
            /               \
... - F - G ------------- L - M  <-- HEAD=master

如果您现在git push得到结果,则提交M进入,合并您的更改,L作为合并的第一个父级,并将它们的提交K作为第二个父级。

这是发生的事情的实际历史M(正如霍布斯在评论中指出的那样),并且是完全正常的。但是,您(和您惊慌失措的老板)可能更愿意构建一个不同的历史记录,假装您typo H通过K. 要做到这一点,您将“重新设置”您的提交(L上面的 , )以坐在origin/master.

你可以用git pull --rebase(就像我在输入这个长答案时出现的霍布斯的回答git rebase一样),或者在做git fetch. 在这种情况下,您要做的就是在提交中制作一份更改的副本L(人们经常拼写这个“提交L'”):

              H - I - J - K - L'
            /
... - F - G ------------- L

此时,您不再需要提交L——由于 git 的“reflogs”,它会在 repo 中停留 90 天左右,但它会从正常git log输出中消失,例如——所以你只需要重新 -稍微画一下这个图,省略去 的线L,然后像这样贴上标签:

              H - I - J - K      <-- origin/master
            /               \
... - F - G                   L' <-- HEAD=master

这正是git rebase命令序列中的内容:

$ git add typo
$ git commit -m "reworking to ignore logs"
$ git rebase

会做。(或者,同样,您可以提交然后使用git pull --rebase,这只是让 git 使用fetch-then-rebase序列,而不是fetch-then- merge。如果没有什么可获取的,在这种情况下,这与运行 相同git rebase。)

于 2013-10-25T19:02:58.770 回答
0

当您git pull在具有本地更改并且也有上游更改的分支上时,拉取会将上游更改合并到您的本地更改中,从而创建合并提交。当您推送时,此合并提交将被发送到服务器并成为您看到的历史记录的一部分。

两件可能有帮助的事情:

  1. 教你的老板正确阅读历史,不要对实际上不存在的变化感到害怕。
  2. 在功能分支上使用git pull --rebase以提取上游更改并将未推送的本地工作重新定位在它们之上。这导致了更清晰的历史,除了反历史重写纯粹主义者之外,每个人都普遍认为这是一种改进。
于 2013-10-25T18:52:55.867 回答