Git 分支与冲突解决探究

有一天,你忘记在修改代码前更新代码,直接在你的 dev_zxlm 更新着你所在业务模块的代码,修改完后按流程在自己的分支上 git add + git commit。

殊不知在你更新代码之前,有另外一个人也在 dev_sb 分支修改了同一份文件同一处的代码,并完成了同样的操作,然后将他的代码push上去而且合并到主分支master

现在,你可能会做出两种操作:

  1. 将自己的分支推上去,然后在仓库管理平台进行 Pull Request 提出合并请求;

  2. 先将自己的代码更新到 master 分支,再尝试上传;

但是哪种操作都会让你直冒冷汗:我是谁我在哪,我在干什么?下一秒项目负责人和甲方是不是就得给我发消息了……

本文主要探究相关冲突问题的复现和解决方案。

Git 基础命令回顾

  1. git init 初始化仓库

  2. git clone 克隆仓库

  3. git add 文件更改添加到暂存区

  4. git commit 提交暂存区更改的仓库

  5. git status 查看工作目录与暂存区文件状态

  6. git log 查看提交历史

  7. git branch <分支名> 创建新分支

  8. git checkout <分支名> 切换到指定分支

  9. git merge <要合并的分支名> 将指定分支的更改合并到当前分支

  10. git pull 从远程仓库获取更改

  11. git push 推送更改到远程仓库

Git 分支相关命令

查看操作单一分支 git branch

  1. 查看当前分支:git branch

  2. 查看远程分支:git branch -r

  3. 查看所有分支:git branch -a

  4. 查看分支最后提交:git branch -v

  5. 创建分支:git branch <分支名>

  6. 删除分支:git branch -d <分支名>

  7. 重命名分支:git branch -m <旧分支名> <新分支名>

切换分支 git checkout

  1. 切换分支:git checkout <分支名>

  2. 创建并切换到新分支:git checkout -b <分支名>

合并分支

  1. git merge <分支名> :将<分支名>分支合并到当前分支。将两个分支的历史合并在一起,并创建一个新的合并提交。这个合并提交有两个父提交,代表了被合并的分支的历史。

  2. git rebase <分支名> :将当前分支的提交移到<分支名>分支的顶端,然后将<分支名>分支的提交应用到当前分支。它会重写提交历史,使得提交历史更加线性。

两者的区别:

假设有两个分支:feature 分支和 main 分支。

初始状态如下:

main:  A---B---C
            \
feature:      D---E
  • 使用 git merge :非线性提交历史

运行 git checkout main; git merge feature

main:  A---B---C---F
            \     /
feature:      D---E

在这里,feature 分支的提交 DE 被合并到 main 分支上,形成了新的合并提交 F。这样的提交历史在合并点会有一个分叉,看起来不太线性。

  • 使用 git rebase :线性提交历史

运行 git checkout feature; git rebase main

main:  A---B---C
                 \
feature:           D'---E'

在这里,feature 分支的提交 DE 被移到了 main 分支的最新提交 C 的顶端,并创建了新的提交 D'E'。这样的提交历史更加线性。

  1. Pull Request :如果你使用的是基于 GitHub、GitLab 或 Bitbucket 等平台的协作开发,通常会使用 Pull Request(PR)来进行分支合并。你可以在平台上创建一个 PR,然后选择要合并的分支,进行必要的审核和讨论后,再进行合并操作。

推送拉取分支

git在本地会保存两个版本的仓库,分为本地仓库远程仓库
1、本地仓库就是我们平时 add、commit 的那个仓库
2、远程仓库可以用 git remote -v 查看(这里的远程仓库是保存在本地的远程仓库,等同于另一个版本,不是线上的远程仓库)。

  1. 推送分支 git push <远程仓库> <本地分支> :如 git push origin master 会将本地 master 分支推送到线上远程 origin 仓库的 master 分支。

  2. 推送分支 git push <远程仓库> <本地分支>:<远程分支> :如 git push origin master1:master2 会将本地 master1 分支推送到远程 origin 仓库的 master2 分支

  3. 拉取分支 git fetch <远程仓库> <远程分支> :如 git fetch origin master 会将远程 origin 仓库的 master 分支更新到本地远程仓库的 origin/master 分支。之后需要手动进行合并操作 git merge origin/master 合并到当前分支

  4. 拉取分支 git fetch <远程仓库> <远程分支>:<本地分支> :如 git fetch origin master1:master2 会将远程 origin 仓库的 master 分支更新到本地远程仓库的 origin/master1 分支,再将其合并到本地 master2 分支。若不存在master2分支,则新建master2分支。

相当于 git fetch origin master1git merge origin/master1 master2 两个命令

  1. 拉取分支 git pull <远程仓库> <远程分支>

相当于 git fetch origin mastergit merge origin/master

注意是合并到当前分支

命令行处理冲突

单一用户合并分支冲突

PS D:\Users\Donnie\Desktop\gitTest> git branch
  local_1
  local_2
* master

PS D:\Users\Donnie\Desktop\gitTest> git checkout local_1
Switched to branch 'local_1'
# 在这里编辑 README.en.md

PS D:\Users\Donnie\Desktop\gitTest> git add .\README.en.md

PS D:\Users\Donnie\Desktop\gitTest> git commit -m "local1提交"
[local_1 c669b56] local1提交
 1 file changed, 1 insertion(+), 1 deletion(-)

PS D:\Users\Donnie\Desktop\gitTest> git checkout local_2
Switched to branch 'local_2'
# 在这里编辑 README.en.md

PS D:\Users\Donnie\Desktop\gitTest> git add .\README.en.md

PS D:\Users\Donnie\Desktop\gitTest> git commit -m "local2提交"
[local_2 870f478] local2提交
 1 file changed, 1 insertion(+), 1 deletion(-)

PS D:\Users\Donnie\Desktop\gitTest> git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

PS D:\Users\Donnie\Desktop\gitTest> git merge local_1
Updating b414149..c669b56
Fast-forward
 README.en.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
PS D:\Users\Donnie\Desktop\gitTest> git merge local_2
Auto-merging README.en.md
CONFLICT (content): Merge conflict in README.en.md
Automatic merge failed; fix conflicts and then commit the result.

此时,Readme.en.md的冲突处被git修改如下:

<<<<<<< HEAD
Git 分支与冲突解决探究local1
=======
Git 分支与冲突解决探究local2
>>>>>>> local_2

解决办法:在master分支上修改冲突后,再进行git add和git commit;

如果希望将这些更改同步到其他开发分支(例如 dev 分支)上,以保持一致性,可以切换到 dev 分支后再进行 git merge mastergit rebase master

多用户拉取同一分支冲突

  1. 首先我在码云Gitee上新建一个代码仓库;

  2. 然后在两台机器上分别使用 git config 配置不同的用户名和邮箱;

  3. 在两台机器上分别git clone 这个仓库;

  4. 两台机器同一分支(master分支)修改同一文件同一处代码,并且git add git commit;

  5. 其中一台机器git push上去,另一台机器git fetch下来,这时再继续git merge origin/master时,会弹出冲突;

  6. 在这台机器上解决冲突,之后git add git commit git push;

  7. 最后在另外一台机器上git pull,将起冲突的代码更新。

多用户管理多分支冲突

回顾一下开头的情景,我们进行复现:

在local机器上,我在local_1分支对文件进行了修改,先合并到了master分支然后push了上去。

在server机器上,我在dev_server分支上对文件同一处进行了修改,并且已经进行了提交。

情况1. 将自己的分支推上去,然后在仓库管理平台进行 Pull Request 提出合并请求:

git push origin dev_server 

Gitee 提示无法自动合并,只能手动合并。

git checkout master

git pull https://gitee.com/donniezfr/gitTest.git dev_server	# 出现冲突 解决冲突

git push origin master

情况2.先将自己的代码更新到 master 分支,再尝试上传;

[root@iZ2zeazxy4xlxrrwv9g7qwZ gitTest]# git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 3 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

[root@iZ2zeazxy4xlxrrwv9g7qwZ gitTest]# git merge dev_server
Updating b414149..41e5200
Fast-forward
 README.en.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

上传时,由于在 git push 之前没有进行更新,会出现如下错误提示,需要git pull:

 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://gitee.com/donniezfr/gitTest.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

那么接下来就尝试 git fetch git merge ,然后发现冲突,再解决冲突之后再进行上传。

IDEA Git插件

IDEA 的 Git 模块大概是这个样子:

点击左侧蓝色小按钮会进行 git fetch 即拉取远程所有分支。

除此之外,还可以在这里管理Git各个分支:

代码更新

先 Update Project 实现 git pull ;然后 git checkout 到自己的分支,将 master 分支 merge 到自己的分支;或者在自己的分支上 rebase onto master分支:

或者在自己的分支上 rebase onto master 分支。

代码冲突解决

在 merge 或 rebase 时,出现代码冲突会弹出一下窗口:

如果继续点击 Merge,会弹出来代码修改窗口:

窗口分左、中、右三部分,左、右是要合并的代码,中间是合并结果。

左右两部分为只读内容,中间部分是可以修改的,修改好后点击Apply即可。

可以通过点击 “×”(Ignore) 和 箭头(Accept)来实现快捷更改(提示:可以尝试按住Ctrl键);