gitmerge二进制文件|如何 git push 或者 merge 指定的几个文件

① git merge是怎样判定冲突的

在解决git merge的冲突时,有时我总忍不住吐槽git实在太不智能了,明明仅仅是往代码里面插入几行,没想到合并就失败了,只能手工去一个个确认。真不知道git的合并冲突是怎么判定的。在一次解决了涉及几十个文件的合并冲突后(整整花了我一个晚上和一个早上的时间!),我终于下定决心,去看一下git merge代码里面冲突判定的具体实现。正所谓冤有头债有主,至少下次遇到同样的问题时就可以知道自己栽在谁的手里了。于是就有了这样一篇文章,讲讲git merge内部的冲突判定机制。recursive three-way merge和ancestorgit的源码先用merge作关键字搜索,看看涉及的相关代码。找了一段时间,找到了git merge的时候,比较待合并文件的函数入口:ll_merge。另外还有一份文档,它也指出ll_merge正是合并实现的入口。从函数签名可以看到,mmfile_t应该就代表了待合并的文件。有趣的是,这里待合并的文件并不是两份,而是三份。int ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, const struct ll_merge_options *opts)看过git help merge的读者应该知道,ours表示当前分支,theirs表示待合并分支。看得出来,这个函数就是把某个文件在不同分支上的版本合并在一起。那么ancestor又是位于哪个分支呢?倒过来从调用方开始阅读代码,可以看出大体的流程是这样的,git merge会找出三个commit,然后对每个待合并的文件调用ll_merge,生成最终的合并结果。按注释的说法,ancestor是后面两个commit(ours和theirs)的公共祖先(ancestor)。另外前面提到的文档也说明,git合并的时候使用的是recursive three-way merge。关于recursive three-way merge, wikipedia上有个相关的介绍#Recursive_three-way_merge)。就是在合并的时候,将ours,theirs和ancestor三个版本的文件进行比较,获取ours和ancestor的diff,以及theirs和ancestor的diff,这样做能够发现两个不同的分支到底做了哪些改动。毕竟后面git需要判定冲突的内容,如果没有原初版本的信息,只是简单地比较两个文件,是做不到的。鉴于我的目标是发掘git判定冲突的机制,所以没有去看git里面查找ancestor的实现。不过只需肉眼在图形化界面里瞅上一眼,就可以找到ancestor commit。(比如在gitlab的network界面中,回溯两个分支的commit线,一直到岔路口)有一点需要注意的是,revert一个commit不会改变它的ancestor。所谓的revert,只是在当前commit的上面添加了新的undo commit,并没有改变“岔路口”的位置。不要想当然地认为,revert之后ancestor就变成上一个commit的ancestor了。尤其是在revert merge commit的时候,总是容易忘掉这个事实。假如你revert了一个merge commit,在重新merge的时候,git所参照的ancestor将不是merge之前的ancestor,而是revert之后的ancestor。于是就掉到坑里去了。建议所有读者都看一下git官方对于revert merge commit潜在后果的说法:https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt 结论是,如果一个merge commit引入的bug容易修复,请不要轻易revert一个merge commit。剖析xdiff从ll_merge往下追,可以看到后面出了一条旁路:ll_binary_merge。这个函数专门处理bin类型文件的合并。它的实现简单粗暴,如果你没有指定合并策略(theris或ours),直接报Cannot merge binary files错误。看来在git看来,二进制文件并没有diff的价值。主路径从ll_xdl_merge到xdl_merge,进到一个叫xdiff的库中。终于找到git merge的具体实现了。平心而论,xdiff的代码风格十分糟糕,不仅注释太少,而且结构体成员变量居然使用类似i1、i2这样的命名,看得我头昏脑胀、心烦意燥。吐槽结束,先讲下xdl_merge的流程。xdl_merge做了下面四件事:由xdl_do_diff完成two-way diff(ours和ancestor,theirs和ancestor),生成修改记录,存储到xdfenv_t中。xdl_change_compact压缩相邻的修改记录,再用xdl_build_script建立xdchange_t链表,记录双方修改。xdchange_t主要包括了修改的起始行号和修改范围。这时候分三种情况,其中两种是只有一方有修改(只有ours或theirs一条链表),直接退出。最后一种是双方都有修改,需要合并修改记录。由于修改记录是按行号有序排列的,所以直接合并两个链表。修改记录如果没有重叠部分,按先后顺序标记为我方修改/他方修改。如果发生了重叠,就表示发生了冲突。之后会重新过一遍两个待合并链表,对于那些标记为冲突的部分,比较它们是否相等的,如果是,标记为双方修改。由xdl_fill_merge_buffer输出合并结果。如果有冲突,调用fill_conflict_hunk输出冲突情况。如果没有冲突(标记为我方修改/他方修改/双方修改),则合并ancestor的原内容和修改记录,按标记的类型取修改后的内容,并输出。输出冲突情况的代码位于fill_conflict_hunk中。它的实现很简单,毕竟此时我们已经有了双方修改的内容,现在只需要同时输出冲突内容,供用户取舍。(这便是那次花了一个晚上和一个早上改掉的冲突的源头,凶手就是你,哼)。输出格式恐怕大家都很熟悉。该函数会先打印若干个<,个数由DEFAULT_CONFLICT_MARKER_SIZE决定,也即是7个。然后是ours分支名。接着输出我方的修改,然后输出若干个=。最后是他方的修改,以及若干个>。这个就是折磨人的合并冲突了:<<<<<<< HEAD3=======2>>>>>>> branch1总结git merge的冲突判定机制如下:先寻找两个commit的公共祖先,比较同一个文件分别在ours和theirs下对于公共祖先的差异,然后合并这两组差异。如果双方同时修改了一处地方且修改内容不同,就判定为合并冲突,依次输出双方修改的内容。

② 如何 git push 或者 merge 指定的几个文件

1、首先新建一个文本文件,名字为“.gitignore.txt”。

③ git中二进制文件冲突了怎么办

对于二进制文件的冲突,你肯定不想通过编辑二进制文件来解决冲突,那是不可能完成的事情。你要做的就是:要么选择对方的修改,要么选择自己的修改。你可以用git checkout的–theirs或–ours选项。git pullgit checkout –theirs YOUR_BINARY_FILE// git checkout –ours YOUR_BINARY_FILEgit add YOUR_BINARY_FILEgit commit -m 'merged with the remote repos.'git push

④ 代码文件是二进制文件,该怎么做版本管理

git 的优势是分支,二进制没法 merge,所以也不能分支。

⑤ git中二进制文件冲突了怎么办 / 网络技术编程

对于二进制文件的冲突,你肯定不想通过编辑二进制文件来解决冲突,那是不可能完成的事情。 你要做的就是:要么选择对方的修改,要么选择自己的修改。 你可以用git checkout的–theirs或–ours选项。 git pull git checkout –theirs YOUR_BINARY…

⑥ git中二进制文件冲突了怎么办

对于二进制文件的冲突,你肯定不想通过编辑二进制文件来解决冲突。那是不可能完成的事情。你要做的就是:要么选择对方的修改,要么选择自己的修改。你可以用git checkout的–theirs或–ours选项。git pullgit checkout –theirs YOUR_BINARY_FILE// git checkout –ours YOUR_BINARY_FILEgit add YOUR_BINARY_FILEgit commit -m 'merged with the remote repos.'git push

⑦ 如何使用git merge 一系列的commits

先来说说 git 所保存的对象。对于 git 来说, commits ,文件, branches 都是一些对象。 commits 保存的是一些文本文件之间 diff (只对文本文件来说)。所谓的 merge 就是把那些 diff 在某些 commit 点上面重播( replay )一次。可是 git 的 merge 没有直接 merge 一系列 commits 的功能。对于一个 commit ,我们可以使用 cherry-pick 来把那个 commit replay 到其它 commit 点上面。对于一系列的 commit ,这样做很麻烦,假如发生冲突麻烦更加大,你必须记住在 replay 到哪个 commit 的时候发生冲突,解决完之后还必须到跳过那个 commit 继续 cherry-pick 。git 的 rebase 能达到此目的。它和 merge 的不同之处在于 merge 是在历史记录分开的时候那个点开始重播的,而rebase 是保存好在 upstream 没有的 commits ,然后把那些 commits 重播在新的 base 点上。假设要把 branches A 中 commit c-d (从 a-f ) merge 到 branch B 有两种方法:方法一:首先用从 A 创建新的分支 A1 ,用 branch –f d 去除 commit e f ,然后用 rebase 去除 commit a , b(详情见 rebase –help )。然后把 branch B rebase 到 branch A1 上方法二:首先从 A 创建新的分支 A1 ,用 branch –f d 去除 commit e f 。然后用 rebase –onto B b A1 把 A1“嫁接”( transplant )到 B 上。方法一方法二的区别在于 c-d 应用的位置不同,方法一会出现在 B 的最前面;方法二相反。


赞 (0)