GIT重新分配需要重新进行更改 [英] GIT rebase requires to recommit changes

查看:103
本文介绍了GIT重新分配需要重新进行更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在别的之前,我只是对git分支的新手。我不知道每个功能分支应该从 master 分支,并且只使用具有与下一个功能分支关系的先决条件功能分支。



我有三个分支。 master 功能-1 功能-2 全部推送一个Bitbucket存储库(启用问题跟踪)。提交事务 M4 M5 是关键提交,所有分支都应该在提交合并之前重新绑定它(任务 git rebase

  M1  -  M2  -  M3  -  M4  -  -  M5 [master] 
\ /
A1 --- A2 [feature-1]
\
B1 - B2 - B3 [feature-2]

feature-2 的开发已完成,现在现在需要合并为主。以下是我为 feature-2 重新命名 M4 M5所完成的任务的优先顺序提交至特征-2


  1. git push - 开发特性-2

  2. git checkout feature-2

  3. git rebase master
  4. 冲突
  5. git pull

执行完这些步骤后,我注意到执行 git status 后。我必须再次提交所有提交( feature-2 M4 M5 和冲突提交)。那么我所要做的只是做 git push 并发起pull request,我完成了,对不对?但是,这将增加另一个git提交对问题跟踪器的评论。



有没有办法重定位 feature-2 master ,而无需再次推送 feature-2 M4 M5 git log 应该包含冲突提交。



更新




  • 更改问题详情以获得更好的验证


解决方案

既然您已经推送了两个功能分支,那么您根本不应该改变分支。重新启用已发布的分支机构,因为它破坏了其他开发人员的存储库,因此极不鼓励。原因在于rebase只是提交的完整重写。您正在重新设定的提交被重新创建,内容已更改,最重要的是,使用不同的哈希。这会导致新的提交与旧的提交不兼容,因此,任何拥有旧提交的提交最终都会与替换它们的新提交冲突。

适当的解决方案因为这是简单地合并更改。虽然这可能不会看起来非常漂亮,但它是一种无破坏性的行为,其中不存在任何更改。所发生的一切就是提交添加,当推或拉时不会导致任何问题。



这就是说,你可以 rebase并仍然发布已更改的分支。但要这样做,您需要强制推送分支机构,其他开发人员需要将分支机构重新设置为新版本。






将下面的一些评论引入到答案中:理解在Git中,分支仅仅是提交的指针很重要。整个历史 - 没有分支 - 是一个大的非循环图,其中提交只是指向他们的父提交。所以,以这个问题为例,这是历史,没有任何分支指针:

  A  -  B  -  C -  D  -  E 
\ /
F --- G
\
H - I - J

每个字母代表一个提交,并且连接到到左边的所有提交都是它的父代。因此,例如 F 的父母是 A ,并且 C 是与父母 B G



的合并提交

现在,如果我们向该可视化添加分支,那么我们只需添加指向某些提交的指针即可。它真的不是别的(分支实际上只是一个包含提交哈希的文件):

  master 

A - B - C - D - E
\\ /
F - G←feature-1
\
H - - I - J

feature-2

现在想象一下,对 feature-2 分支进行提交。我们将该提交添加到树中...

  \ 
H - I - J - K

feature-2

...然后我们向前移动分支指针:

  \ 
H - I - J - K

功能-2

现在,为了理解推送过程中会发生什么,我们需要记住远程分支也是只是分支机构,就像另一组指针。所以它看起来像这样:

  \ 
H - I - J ------ ----- K
↑↑
来源/功能-2功能-2

我想你可以想象在推送过程中会发生什么:我们告诉远程存储库更新其分支指针,使其指向 K 。但是服务器只有 J ,所以我们需要给服务器一切构建可由 K (so之间的任何其他提交以及这些提交的所有实际内容)。但我们当然不需要物理地推送 J H ,甚至 feature-2 分支上(尽管这些都是技术上的,因为您可以 >他们); Git非常聪明,能够找出哪些对象实际上缺失了(你可以看到Git会在你开始推送时计算它)。

因此,一旦我们将缺失的对象转移到远程仓库,然后我们告诉远程仓库更新它的 feature-1 指针,所以它也指向 K 。如果成功了,我们也会更新远程分支( origin / feature-2 )以指向它(仅仅为了同步)。



现在,合并的情况也是如此。想象一下,我们将 master 合并为 feature-2 (使用 git merge master $ code> $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b↓
A - B - C - D - E -----
\ \\\
F --- G←feature-1 \
\\\
H - I - J - K - L

功能-2
feature-2 ,我们需要再给远程仓库所有的对象它没有。因为我们现在正在进行合并提交,所以我们需要检查父母:所以如果服务器没有 K ,我们需要推送 K ;但是,如果它没有 E ,我们将不得不推出 E 。当然,我们需要再次跟踪这些父母,确保遥控器上存在所有物体。一旦完成,我们再次告诉远程机器来更新分支指针。



所以总结一下:分支包含所有提交可以通过在非循环树中导航其提交的父项而以某种方式访问​​。但即使这意味着分支通常非常大(历史的长度),Git也只会将这些对象转移到它没有的远程存储库。因此,虽然合并可以向分支添加更多的提交,但如果远程已经从另一个分支知道它们,则这些提交并不一定必须被传输。






最后,在rebasing上最后一段话:上面我们做了 git merge master 来合并 master 分支到 feature-2 。如果我们做了 git rebase master ,那么现在完整的树就像这样:

  master feature-2 
↓↓
A - B - C - D - E - H' - I' - J' - K'
\ /
F --- G←feature-1
\
H - I - J - K

origin / feature- 2

正如您所见,有新的提交 H' I' J' K'。这些是重写的提交,所以它们以 E (其中 master 在rebase时指向))开始,而不是 G 。由于我们重新设计,没有合并提交 L 。如上所述,原始提交仍然存在。只是没有指针指向他们,所以他们会迷路,并最终被垃圾收集。



那么现在推动什么问题呢?远程分支仍然指向原来的 K ,但我们希望它现在指向 K'。因此,我们开始为远程存储库提供所需的所有对象,就像以前一样。但是当我们告诉它设置更新分支指针时,它将拒绝这样做。原因是通过将指针设置为 K',它将不得不回到历史记录并忽略提交 H的存在 K 。它不知道我们已经重新设计了这些,重写的和原来的也没有联系。因此,为了防止意外的数据丢失,远程会拒绝更新分支指针。



现在,您可以推送分支。这将告诉远程存储库更新分支指针,即使这样做会抛出原始提交。所以你这样做,情况将如下所示:

  origin / feature-2 
主特征-2
↓↓
A - B - C - D - E - H' - I' - J' - K'
\ /
F --- G←feature-1

到目前为止,一切都很顺利:您决定重新绑定分支,并且你告诉远程存储库接受它,而不用质疑它。但现在想象一下,我想拉那个;而我的分支仍然指向 I 。因此,运行pull的操作与反向推送的操作相同:远程程序为我提供了完成历史记录所需的所有对象,然后告诉我在哪里设置分支指针。在那个时候,我的本地Git拒绝这样做,原因与远程仓库之前做过的相同。



通过之前的推动,我们获得了我们想要的知识替换原始提交;但由于我们没有这个功能,所以我们现在需要调查或询问我们是否应该更换我们的本地分支,或者实际上是否存在远程故障。如果我们在我们自己的地方做了一些我们现在想要合并的工作,情况会变得更糟。

这些问题发生在所有提取了原始提交的人。一般来说,你想完全避免这个混乱,所以规则是永远不要重新发布你已经发布的东西。只要没有其他人能够获得这些原始提交,你就可以重新分配资金,但只要这种情况不再发生,对每个参与者来说都是一团糟。所以合并是绝对的首选。


Before anything else, I'm just new to git branching. I wasn't aware that every feature branch should be branched out from master and only use the pre-requisite feature branch with such relation to the next feature branch.

I have three branches. master, feature-1 and feature-2 all pushed the a Bitbucket repository (with issue tracking enabled). The thing is commits M4 and M5 are critical commits that all branches should rebase it before committing a merge (task of git rebase)

M1 -- M2 -- M3 -- M4 -- M5   [master]
  \        /
   A1 --- A2                 [feature-1]
           \
            B1 -- B2 -- B3   [feature-2]

The development for feature-2 is done and is now needed to be merged to master. Here's the precedence of tasks I've done for feature-2 to rebase M4 and M5 commits to feature-2.

  1. git push - development of feature-2
  2. git checkout feature-2
  3. git rebase master
  4. Resolve conflicts
  5. git pull

After doing those steps I've noticed after executing git status. I have to push again all the commits (feature-2, M4, M5 and conflict commits). Well all I have to do is just do git push and initiate the pull request and I'm done, right? But that would add another git commit comments to the issue tracker.

Is there a way to rebase feature-2 to master without the need to push again the commits of feature-2, M4 and M5 and the git log should contain conflict commits.

Update

  • Change the question details for better clarrification

解决方案

Since you have pushed both feature branches already, you shouldn’t rebase at all. Rebasing already published branches is strongly discouraged since it breaks other developer’s repositories. The reason for that is that a rebase is just a complete rewrite of the commits. The commits you are rebasing are recreated, with changed contents, and—most importantly—with different hashes. This causes the new commits to be incompatible to the old ones, so whoever had the old ones ends up being in conflict with the new ones which replace them.

The proper solution for this is to simply merge the changes. While this might not end up looking so pretty, it is a non-destructive action in which no existing commits are changed. All that happens is that commit are added which will result in no problems when pushing or pulling.

That being said, you can rebase and still publish the changed branches. But to do so, you will need to force-push the branches and other developers pulling those changes will need to reset their branches to the new versions.


To incorperate some of my comments below into the answer: It’s important to understand that in Git, branches are just pointers to commits. The whole history—without branches—is a large acyclic graph where commits just point to their parent commits. So to take your example from the question, this is the history, without any branch pointers:

A -- B -- C -- D -- E
 \       /
  F --- G
         \
          H -- I -- J

Each letter represents a commit and all commits one is connected to to the left are its parent. So for example F’s parent is A, and C is a merge commit with the parents B and G.

Now, if we add branches to that visualization, then we just add pointers that point to some commits. It’s really nothing else (a branch is literally just a file that contains the hash of a commit):

                  master
                    ↓
A -- B -- C -- D -- E
 \       /
  F --- G  ← feature-1
         \
          H -- I -- J
                    ↑
                feature-2

Now, imagine we make a commit to the feature-2 branch. We add that commit to the tree …

         \
          H -- I -- J -- K
                    ↑
                feature-2

… and then we move the branch pointer one forward:

         \
          H -- I -- J -- K
                         ↑
                     feature-2

Now, to understand what happens during a push, we need to remember that remote branches are also just branches, and as such just another set of pointers. So it actually looks like this:

         \
          H -- I -- J ----------- K
                    ↑             ↑
           origin/feature-2    feature-2

I think you can imagine what happens during a push now: We tell the remote repository to update its branch pointer so it points to K. But the server only has J, so we need to give the server everything to construct the tree accessible by K (so any other commits in between, and all the actual contents of those commits). But of course we don’t need to physically push J, or H, or even A (although those are all technically on the feature-2 branch since you can reach them); Git is smart enough to figure out which objects are actually missing (you can see Git calculating that when you start a push).

So once we transferred the missing objects to the remote repository, we then tell the remote repository to update its feature-1 pointer, so it will also point at K. And if that succeeded, we also update our remote branch (origin/feature-2) to point to it too (just to get in sync).

Now, the situation is really the same with merges. Imagine that we merged master into feature-2 (using git merge master while on feature-2):

                  master
                    ↓
A -- B -- C -- D -- E -----
 \       /                 \
  F --- G  ← feature-1      \
         \                   \
          H -- I -- J -- K -- L
                              ↑
                          feature-2

Now, if we want to push feature-2, we again need to give the remote repository all the objects it doesn’t have. And since we are on a merge commit now, we need to check all parents: So if the server didn’t have K we would need to push K; But also, if it doesn’t have E, we would have to push E. And of course we need to follow those parents again to make sure that all objects exist on the remote. And once that’s done, we just tell the remote again to update the branch pointer.

So to sum this up: A branch contains all commits that are somehow accessible by navigating the parents of its commits in the acyclic tree. But even if that means that branches are usually very "big" (in length of history), Git will only transfer those objects to the remote repository that it doesn’t have. So although a merge can add many more commits to a branch, those doesn’t necessarily have to be transferred if the remote already knows about them from another branch.


And finally, some last words on rebasing: Above we did git merge master to merge the master branch into feature-2. If we did git rebase master instead, the full tree would look like this now:

                  master               feature-2
                    ↓                      ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
 \       /
  F --- G  ← feature-1
         \
          H -- I -- J -- K
                         ↑
                  origin/feature-2

As you can see, there are new commits H', I', J' and K'. These are the rewritten commits so that they start at E (where master pointed to at the time of the rebase) instead of G. Since we rebased, there is no merge commit L. And as made clear above, the original commits still exist. It’s just that there is no pointer left that points to them; so they are "lost" and will eventually be garbage collected.

So what is the problem when pushing now? The remote branch still points to the original K, but we want it to point to K' now. So we start giving the remote repository all the objects it needs, just like before. But when we tell it to set update the branch pointer, it will refuse to do that. The reason for that is that by setting the pointer to K' it would have to "go back in the history" and ignore the existence of commits H to K. It doesn’t know that we have rebased those and there is also no link between the rewritten ones and the original ones. So just to prevent accidental data loss, the remote will refuse to update the branch pointer.

Now, you can force push the branch. This will tell the remote repository to update the branch pointer even though doing so would throw those original commits away. So you do that, and the situation will look like this:

                                   origin/feature-2
                  master               feature-2
                    ↓                      ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
 \       /
  F --- G  ← feature-1

So far, everything is well: You decided to rebase the branch, and you told the remote repository to accept that without questioning it. But now imagine I wanted to pull that; and my branch is still pointing to I. So running pull does the same as a push in reverse: The remote gives me all the objects necessary to complete the history and then it tells me where to set the branch pointer. And at that point, my local Git refuses to do so for the same reason the remote repository did that before.

With the push before, we had the knowledge that we wanted to replace the original commits; but with a pull we don’t have that, so we are now required to investigate, or ask around, whether we should just replace our local branch, or if there’s actually some fault on the remote. And the situation gets even worse if we did some work on our own locally which we would want to merge now.

And these problems happen to everyone that fetched those original commits once. In general, you want to avoid this mess completely, so the rule is to never rebase something that you have already published. You can rebase as long as nobody else ever got those original commits, but as soon as that’s no longer the case, it will be a mess for everyone involved. So a merge is definitely preferred.

这篇关于GIT重新分配需要重新进行更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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