总结一下在工作中遇到的冲突及避免的办法,
一、修改同一处代码引起的冲突
git主要有master,release,dev,feature等分支,
以dev/feature分支为例,主要用于开发测试主分支。
demo在feature合并dev分支的时候会出现的冲突。
dev环境的版本
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
feature_A的版本
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
feature_B的版本
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
下面展示是如何造成冲突的:
这个时候feature_B 加了2个import
feature_B的版本变为:
import java.text.SimpleDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
显然这个时候和staging的版本造成冲突:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
git合并的时候会进行three-way merge,
这个时候有歧义性:
1、第一种合并方案
认为你的版本删除了feature_A的
import java.util.Date;
又增加了两行
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
最终结果为:
import java.text.SimpleDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
2、第二种合并方案,
即不删除feature_A的
import java.util.Date;
最终合并方案为:
import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
避免这种情况的最好方法是import总是放在自己的代码中间,import不要交错放。禁止使用代码格式化工具,尽量手动格式化。
二、改动他人代码引起冲突:
另外一种冲突的原因是因为改动了他人的代码,这个是很危险的,非常容易引起冲突和bug。
三、文件引起的冲突
因为共同创建了一个同名文件,对方删除了我们正在使用的文件。也会引起冲突,叫做树冲突。
比如,a用户把文件改名为a.c,b用户把同一个文件改名为b.c,那么b将这两个commit合并时,会产生冲突。
如果最终确定用b.c,那么解决办法如下:
git rm a.c
git rm origin-name.c
git add b.c
git commit
例子:引发此问题描述和背景
我们在主干dev和branch1分支上进行并行开发。当要把branch1功能的代码合并到dev上时,发现dev上开发的部分功能代码找不到了。
那么,是在branch1上,作了删除提交导致的吗?然而,查提交日志,并没有发现删代码的提交记录。
难道一个分支有一个功能,另一个分支没这个功能,git合并时就有可能把这块功能代码丢掉?跟功能添加时间顺序有关系?
为了解决这个问题和相关的疑问,我们需要先了解下git合并的过程。
git-merge过程
git合并是用的git merge命令。它只有两种,一种是快速合并(fast-forward),还有一种是三方合并(thirdparty merge)。
快速合并(fast-forward):当两个分支有直系关系时,使用快速合并,git不产生新的commit结点,只是把head进行更新。
三方合并(thirdparty merge):三方合并稍显复杂点,它会产生一个新的commit结点,并把head指向它。它会先去找这两个要合并分支的最近公有结点,git对 C1
、C3
和C5
三个结点进行三方合并产生新结点。这里的三方合并,具体来说,就是把后新节点相较于之前共有节点的 diff差异应用,最后产生新commit结点。
现在回答上面的疑问,三方合并其实只看三个点的内容,和中间结点无任何关系,更别提跟时间有关系了。在一个分支上删除代码,如果合并时没有冲突的话,合并后是会直接删除的。
所以,我们找到了问题的初步方向了。dev上的代码合并后没了,一定是branch1分支有问题!!!
问题起因及检测
总结问题的原因是,在正式合并前,进行了逆向的合并,并在合并中悄悄
把主干代码删除掉了。一般如果查看提交记录中,没有看到删除记录,那么很有可能是之前的Merge中把代码删除了。可以使用 merge-base
和git diff
工具来进行定位,也可以用来检测是否有问题。
解决思路
既然我们发现了问题的原因,并知道怎么去规避、检测。那么,如果已经发生了问题,怎么去解决呢?这个可能是大家更关心的。
其实我们最终的目标是,把branch1和dev进行合并,产生一个合并节点,并且这个合并结点的代码是正确的。
确保合并后代码正确
1. dev重置到合并前
既然最后合并branch1到dev会导致dev丢代码,我们首先把dev重置到合并前。
# git checkout dev
# git reset --hard HEAD~1
2. 创建tmp分支,绕过错误的合并74a8d10
我们知道branch1是有问题的,因为进行了合并dev的操作。所以,基于branch1创建一个临时分支tmp。
# git checkout branch1
# git checkout -b tmp
把tmp的提交记录重塑,使tmp分支回到branch1上的,合并dev到branch1那个错误的合并之前的结点,示例中 74a8d10
之前的那个c26c5e3
结点,并提交一个新记录,这样tmp内容与branch1一样,而完全跟那个74a8d10
结点没关系了。
# git checkout tmp
# git reset c26c5e3
# git add .
# git commit -m "内容与branch1一致"
# git checkout dev
# git merge tmp
这里dev和tmp合并时是我们设想的dev和branch1最初分开的结点。
解决冲突,并add进暂存区后,我们代码就是正确的了
产生合并commit对象
上面代码正确了,如果我们直接commit的话,这个合并结点,就变成dev和tmp的合并了,而我们要的是dev和branch1的合并。所以,我们要产生一个dev和branch1合并的结点,并且内容是当前dev和tmp合并后的代码。显然,git merge不能满足我们的需求,我们需要更底层的git命令,就是git merge过程中,调用的底层命令。
需要按序要用到 write-tree -> commit-tree -> update-ref,这三条底层命令。这部分命令,可以查看参考资料2。
1. write-tree产生tree对象
# git add .
# git write-tree
853c36012082314f9463f3819d0a24da49dc5bb1
我们产生了SHA-1值为 853c360
的tree对象。
2. commit-tree产生commit对象
# git commit-tree 853c360 -p 98d19a4 -p 0acedcb -m "Merge branch 'branch1' into dev"
675baf3973508ee03306cc5a36fe489d694e107f
我们把tree对象 853c360
进行了提交,并设置它的两个父结点为dev和branch1,产生了commit对象675baf3
。我们可以看下这个结点的情况:
# git cat-file 675baf3 -p
tree 853c36012082314f9463f3819d0a24da49dc5bb1
parent 98d19a4a5913f18a2c0e9821e114df9995b23d82
parent 0acedcb89e4d25a0256fcbe7fba0bbc13de9d92e
author Vincent <xxx> 1498497182 +0800
committer Vincent <xxx> 1498497182 +0800
Merge branch 'branch1' into dev
3. 更新head
使用如下命令,更新dev指向这个新的commit对象, 675baf3
:
# git update-ref refs/heads/dev 675baf3
更粗暴的方法
如果你觉得底层命令不好理解。你可以:
-
先整个目录拷备下工程(包含.git目录),比如拷贝到bak目录
-
在工程中直接合并branch1到dev上,不解决冲突,不提交
-
在bak目录,按照上面确保代码正确的方法,在bak目录合并出正确的代码。
-
把bak目录中,除了.git目录外的东东,全部拷贝覆盖到原来工程目录中
-
在原来工程目录中,提交
这样比较好理解,缺点是工程如果大的话,拷来拷去花费时间比较长,而且不够优雅。
其他解决思路
上面描述的思路,我认为是最行之有效的。也试了其他思路,比如:
-
查看git merge的参数,发现并没有可以自由设置base节点的方法,只有设置发现base节点的策略,而且这些策略发现的base节点都是那个错误的合并。
-
undo merge。参考资料3。然而,感觉revert merge的能力有限,加-m1参数、和-m2参数,均无法满足要求。
-
rebase branch1。错误发生在branch1,那么重建branch1呢?把所有branch1上合并后的提交都重新提交呢?结果发现branch1上有太多合并冲突,rebase时,要把这个合并的冲突重新解决,很麻烦。
这些思路,大家也可以继续研究下,感觉不能解决问题,也可能是我了解得有问题。当然,你有其他思路,也希望你交流下。
迷思
本次我的操作中,是因为解决冲突使错误地把dev合并到branch1上,导致了后面合并的问题。但是,我们真实遇到的场景,虽然看起来是一样的,也可以用文中的方法解决,但是也有细微不同,而且不知道如何出现这个问题。
真实的场景下,也会出现一个dev合并到branch1的Merge提交,但是显示的信息是 "Revert xxx",据提交人员讲,这个确实是做的Revert操作,不知如何变成Merge结点了。用的sourcetree,提交人员也没法说清怎么必现这个问题。
如果,你知道怎么操作能出现这个问题,希望你告诉我。。。
总结
文中描述了一种可能导致git合并代码丢失的错误操作,并讲解了如何规避、检测、解决这种错误。并粗略介绍了,git merge流程,git merge底层过程。
说简单点,问题是因为悄悄
在合并中把代码删除了。解决思路是,悄悄
在后面的合并中把代码加回来。
相关推荐
提交解决冲突后的文件到本地仓库,使用命令`git commit -m "fix conflict"`,在提交消息中简要描述冲突解决的过程。 e. 最后,用`git push origin dev`将解决冲突后的代码推送到远端仓库。 理解这些基本的冲突...
- 学习冲突标记:Git会在冲突区域插入特定的标记,如`, `=======`, `>>>>>>>`,理解这些标记有助于你理解冲突的来源和范围。 总的来说,KDiff3是Git开发者处理代码冲突的强大助手,通过它的可视化界面,我们可以更...
Git在IDEA中的冲突解决方法是版本控制中一个核心的操作技能,尤其对于多人协作的项目管理至关重要。理解并掌握这些技能,可以帮助开发者有效地解决代码合并时出现的冲突,提高工作效率。 首先,我们需要了解什么是...
- 解决冲突需要手动编辑这些文件,删除或保留合适的改动,然后使用`git add <conflict_file>`将解决冲突后的文件添加到暂存区,最后通过`git commit`提交解决冲突后的代码。 5. **推送和拉取** - 将本地分支的...
git-resolve-conflict <strategy> <filename> 使用给定的策略(-我们,-他们的,-联盟)仅解决一个文件中的合并冲突 git resolve-conflict --ours package.jsongit resolve-conflict --theirs package.jsongit ...
指令Git冲突解决程序附带五个命令: Find Next Conflict , Keep Ours , Keep Theirs , Keep Common Ancestor和Show Conflict Files 。 尽管其中大多数都是很好的自我解释,但“ Keep Common Ancestor可能需要一些...
git commit -m "conflict fixed" ``` 三、 Git 冲突的避免技巧 要避免 Git 冲突,可以 采取以下几种方法: 1. 使用 `git pull` 命令来获取最新的远端分支代码。 2. 使用 `git stash` 命令来暂存当前的修改。 3. ...
Idea作为流行的Java开发IDE,集成了强大的Git工具,使得在Idea中处理分支和解决冲突变得方便高效。以下是对`idea+git合并分支解决冲突及详解步骤`的知识点详细解析: 1. **主干分支(master)**: - 主干分支是...
4. 解决冲突后,B执行`git add .`和`git commit -m "Resolve conflict"`完成合并。 通过这种方式,Git有效地管理了版本之间的冲突,并确保了代码的一致性。 ### 结论 Git作为一款强大的版本控制工具,不仅能够...
11. 解决冲突:当合并时发生冲突,需要手动编辑冲突文件,解决后使用`git add <conflict_file>`,再用`git commit`提交解决后的结果。 12. 查看提交历史:`git log` - 显示提交历史,可以配合`--pretty=format:`参数...
解析具有git合并冲突的JSON字符串,并尽可能解决。 如果JSON有效,则照常执行JSON.parse 。 如果冲突的任何一方都是无效的JSON,则将引发一个错误。 用法 // after a git merge that left some conflicts there ...
手动编辑冲突文件,解决后使用`git add <conflict_file>`和`git commit`完成合并。 4. **合并分支**:使用`git merge`合并分支时,若存在冲突,需手动编辑冲突文件并`git add`,然后`git commit`。 5. **Stash**:...
手动编辑这些文件,解决冲突后,使用`git add <conflict-file>`将解决后的文件添加到暂存区,再用`git commit -m "Resolved conflicts"`提交。 **错误提交的回退** 如果提交错误,可以使用`git revert HEAD`回退...
git add <conflict-file> git commit -m "Merge dev branch into master" ``` 4. **推送至远程master分支**:完成合并后,将更新的master分支推送到远程仓库。 ``` git push origin master ``` 5. **清理**...
- **示例**: `git add resolved-conflict.txt`会将`resolved-conflict.txt`标记为已解决。 ##### 手动解决冲突并删除文件 **命令**: `git rm <resolved-file>` - **用途**: 如果文件冲突解决后需要删除该文件,可以...
你可以手动编辑冲突文件,解决后使用`git add <conflict_file>`,再执行`git commit`来完成合并。 此外,Git还有许多其他强大的功能,如标签(Tag,用于标记特定版本)、重置(Reset,可以撤销提交)、 cherry-pick...
通过`merge_conflict_resolve_demo-main`这样的实践项目,你可以亲手操作,熟悉这些步骤,加深对Git合并冲突解决的理解。记得在解决冲突时保持耐心,因为清晰地理解冲突和有效地解决它们是团队合作的关键。
git commit -m "fix conflict" ``` 提交解决冲突后的更改。 ##### 6. 更新远程仓库 ``` git pull origin dev ``` 再次拉取远程仓库的最新更改,以确保没有遗漏的更改。 ##### 7. 推送更改 ``` git push origin...
4. 冲突解决(Conflict Resolution):在多人协作的开发环境中,合并操作有时候会遇到代码变更冲突的情况。冲突通常发生在多个分支对同一代码文件的同一部分进行了不同的修改。解决冲突需要人工介入,选择最终要保留...
git commit -m "Resolved merge conflict" ``` 以上就是Git的基本操作,通过不断地实践,你将逐渐熟悉并掌握这个强大的版本控制工具。Git不仅提供了代码版本管理,还支持代码审查、合并请求等高级功能,是现代软件...