Use gitk to understand git – merge and rebase

本文通过实例演示了如何使用Git进行分支的创建、合并及重新基化操作,并详细解释了快进合并与真实合并的区别。

This is the second part of my Use gitk to understand git post.

In my initial overview, I demonstrated creating a branch, making a couple commits to that branch, and then merging them back into master. In that scenario, there were no changes in my local master (and since it was contrived, I knew there were no changes in the remote origin/master), so the merge was really just a fast-forward. In the real world, my workflow would be slightly different, as I would have to account for other people making changes to our shared repository (my origin remote).

To demonstrate, I’ll rewind time and pretend we’re back at the moment where we switched to master as we prepared to merge in the changes from the issue123 branch. The gitk visualization of the repository looked like:

Just before merging issue123 into master

Before I merge my changes into master, I want to make sure my master branch is in synch with the central repository on github (which I refer to using the remote “origin”). We can see in the screenshot that my master branch refers to the same commit as origin/master, but that’s because I haven’t communicated with origin in a long time. All of my previous operations were done locally. In order to get the latest state from the remote repository, I need to perform a fetch.

d:codegitk-demo>git fetch origin  // This command copies all branches from the remote repo origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From github.com:joshuaflanagan/gitk-demo
   bf37c64..ec8d10f  master     -> origin/master

new changes from remote

I’ve downloaded new commits to my local repository and moved the remote branch pointer, but I haven’t changed anything in my local branches. If I were to look in my working folder, I would see that none of my files have changed. To get the latest changes to the master branch from Tony, I need to merge them into my master branch.

d:codegitk-demo>git merge origin/master
Updating bf37c64..ec8d10f
Fast-forward
 dairy.txt |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 dairy.txt

After merging in remotex

 

Once again, since there was a straight line from my local master to origin/master, git was able to perform a fast-forward merge. The master branch has moved to point to Tony’s latest commit. My working directory has been updated accordingly to have the changes he made.

Note that none of the changes I made for issue123 have been included in master yet. We need to merge the issue123 branch back into master, and ultimately push them to the shared repository on github. However, there is no straight line between issue123 and master – neither is a direct descendent of the other – which means we cannot do a fast-forward merge. We have to do either a “real” merge, or rebase.

Merge

To perform a “real” merge, we just use the merge command as we have all along. Doing a fast-forward vs. a real merge is handled by git – not something you specify.

d:codegitk-demo>git merge issue123
Merge made by recursive.
 fruits.txt     |    1 +
 vegetables.txt |    3 ++-
 2 files changed, 3 insertions(+), 1 deletions(-)

After merge

Previously with our fast-forward merges, no new commits were created – git just moved branch pointers. In this case, since there is a new snapshot of the repository that never existed before (includes Tony’s new changes, as well as my changes from issue123), a new commit is required. The commit is automatically created with an auto-generated commit message indicating it was a merge. The merge commit has multiple ancestors (indicated by the red line going to the “Forgot the yogurt” commit” and the blue line going to the “Added another fruit” commit). We can safely delete the issue123 branch now, but unlike in the fast-forward example, when we push our changes to the central server, there will be evidence that the issue123 message existed (in the merge commit message, and the repository history shows the branched paths).

d:codegitk-demo>git branch -d issue123
Deleted branch issue123 (was cac3c72).

d:codegitk-demo>git push origin master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 914 bytes, done.
Total 8 (delta 0), reused 0 (delta 0)
To git@github.com:joshuaflanagan/gitk-demo.git
   ec8d10f..5835415  master –> master

After pushing merge to github

Commit History after merge

Rebase

There are a few reasons not to like the merge approach:

  • Branching paths in the history can be unnecessarily complicated
  • The extra merge commit.
  • Your branch is now no longer a private, local concern. Everyone now knows that you worked in an issue123 branch. Why should they care?

Note: There are some scenarios where you want to preserve the fact that work was done in a separate branch. In those cases, the above “downsides” are not really downsides, but the desired behavior. However, in many cases, the merge is only necessary because of the timing of parallel work, and preserving that timeline is not important.

You can use git rebase to avoid these issues. If you have commits that have never been shared with anyone else, you can have git re-write them with a different starting point. If we go back in time to the point right after we merged in Tony’s changes, but before merging in issue123:

Before rebase

Currently, the issue123 commits branch off from the “third commit”. The rest of the world doesn’t need to know that is where we started our work. We can re-write history so that it appears like we started our work from Tony’s latest changes. We want the issue123 commits to branch off from master, the “Forgot the yogurt” commit.

d:codegitk-demo>git checkout issue123
Switched to branch 'issue123'

d:codegitk-demo>git rebase master
First, rewinding head to replay your work on top of it...
Applying: My first commit
Applying: Added another fruit

After rebase

After a rebase, the “My first commit” now directly follows the “Forgot the yogurt”” commit, making the issue123 branch a direct descendent of the master branch. This means we can now do a fast-forward merge to bring issue123’s changes into master.

d:codegitk-demo>git checkout master
Switched to branch 'master'

d:codegitk-demo>git merge issue123
Updating ec8d10f..b5a86d6
Fast-forward
 fruits.txt     |    1 +
 vegetables.txt |    3 ++-
 2 files changed, 3 insertions(+), 1 deletions(-)

No merge commit required after rebase

When we delete the issue123 branch and push these changes to the remote repository on github, there is no longer any evidence that the issue123 branch ever existed. Anyone that pulls down the repository will see a completely linear history, making it easier to understand.

d:codegitk-demo>git branch -d issue123
Deleted branch issue123 (was b5a86d6).

d:codegitk-demo>git push origin master
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 626 bytes, done.
Total 6 (delta 1), reused 0 (delta 0)
To git@github.com:joshuaflanagan/gitk-demo.git
   ec8d10f..b5a86d6  master –> master

Pushed to remotex

 

Commit History after rebase

### Git MergeGit Rebase 的区别 #### 合并方式 `git merge` 是一种将两个分支的历史记录合并在一起的操作。它会创建一个新的提交,这个提交有两个父提交,分别指向原来的两个分支的最新提交。这种方法保留了所有的历史记录,并且适合用于公共分支(如 `master` 或 `main`)的合并。 ```bash # 示例:使用 git merge 合并 develop 分支到 master 分支部署 git checkout master git merge develop ``` 这种方式会在 Git 历史中生成一个明确的合并提交,表示两个分支的历史在此交汇[^1]。 而 `git rebase` 则是通过重新应用提交的方式来合并分支。它会将一个分支上的提交“移植”到另一个分支的顶端。具体来说,它会找到两个分支的共同祖先,然后将当前分支的提交逐个应用到目标分支上。 ```bash # 示例:使用 git rebase 将 develop 分支变基到 master 分支上 git checkout develop git rebase master ``` 这种方式会产生线性化的提交历史,使得提交记录更加清晰,但可能会覆盖原始的提交信息和时间顺序[^1]。 #### 提交历史的影响 `git merge` 保留了完整的提交历史,包括合并提交本身。这对于团队协作和调试非常有用,因为它能够准确地反映项目的发展过程。然而,这也可能导致提交历史变得复杂,尤其是在频繁合并的情况下。 `git rebase` 则会重写提交历史,将一个分支的提交依次应用到另一个分支上。这会导致提交历史看起来更简单、更直观,但它也会改变提交的哈希值,从而可能引发冲突或混淆,特别是在多人协作的场景中。 #### 使用场景 `git merge` 更适合用于公共分支的合并,例如 `master` 或 `main`。由于它不会修改现有的提交历史,因此可以避免因重写历史而导致的问题。此外,`git merge` 可以很好地处理多个分支的并发开发情况。 `git rebase` 更适合用于本地开发分支或者私有分支的更新。例如,在开发过程中,如果需要将最新的上游更改同步到自己的分支上,可以使用 `git rebase` 来保持提交历史的整洁性。不过需要注意的是,对已经推送到远程仓库的分支进行 `rebase` 操作时,必须强制推送(`git push -f`),这可能会对其他开发者造成困扰。 #### 冲突解决 在 `git merge` 过程中,如果存在冲突,Git 会在一次操作中提示所有冲突,并等待用户解决后生成最终的合并提交。这种方式适合一次性解决所有冲突。 而在 `git rebase` 过程中,冲突会在每个提交被应用时逐一出现。这意味着用户需要多次解决冲突,并在每次解决后继续执行 `git rebase --continue`。这种方式更适合逐步排查和解决冲突。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值