前言
进入正题之前先唠叨几句,不久前听到一个名词叫——费曼学习法,核心思想就是用通俗的话语把复杂道理或技术讲清楚,如果你已经明白了这个方法的含义,那么我好像离成功又进了一步。其实这个方法一直在尝试使用,但是没想到它居然有个“洋气”的名字。
使用 git merge
命令合并代码的时候可能会产生文件冲突,产生这种冲突的根本原因是文件的同一处同时被多次修改,这种同时修改常体现的不同分支上,当多个分支修改了同一处代码,再合并代码的时候就会产生冲突,因为 git
程序也不知道我们想要保留哪一份修改,这时就需要我们手动修改产生冲突的文件。
当冲突内容很少的时候我们可以打开文本编辑器,找到 >>>>>>>>>>>>
、===========
和 <<<<<<<<<<<<
这三行字符包裹的内容就是需要解决冲突的部分,但是当冲突内容特别多时我们还是习惯于通过可视化的工具来处理,Beyond Compare
就是这样一款工具,可以用来比较不同的文本文件、表格文件,还可以比较文件夹内容,之前用着比较习惯,所以在处理 git
冲突的时候也想使用这个工具来做,通过查找技术文档发现了下面的方法。
鉴于大家都比较急,查找问题时想要直接找到答案,所以我这里直接说明配置步骤,送给不求甚解的小伙伴,也方便今后我可以直接找到,不过配置之前还是要先看一下前提。
git
客户端,可以执行 git
命令(废话!没装 git
怎么产生冲突的)Beyond Compare 4
这个软件,下载链接很多,自己找一个吧,实在找不到,那就放弃吧(找我要)首先找到 Beyond Compare
的安装路径,比如我的软件安装路径是 D:\mybc4\BComp.exe
,然后在 git
命令行客户端中执行下面命令:
1 | git config --global merge.tool bc4 |
至此,git mergetool
就配置完了,当下次冲突的时候,直接使用 git mergetool
命令就可以调用 Beyond Compare
解决冲突文件了,但是你不好奇,这些设置命令都是什么意思吗?为什么执行完这些命令就能调用 Beyond Compare 4
这个软件了,如果你感兴趣可以接下往下看一看。
这是一款强大的比较工具,前面提到它可以比较文本、比较表格、比较文件夹,但是它的能力不仅限于此,它甚至可以比较MP3、比较图片、比较注册表,我们的目的是调用它的比较功能,但是前提是这款软件允许你调用,如果它不给你提供接口,你就是想调用也得绕上八百个圈才可以。
这一点我们可以查询文档确定,文档是安装软件时自带的,名字为 BCompare.chm
,如果找不到,安利你一个叫做 Everything
的软件,装上它以后,电脑中的一切东西都能搜索找到。
这个文档应该很容易找到的,与软件的可执行文件在同一目录,其实我们使用的比较工具应该是 BCompare.exe
,但是为什么在配置 git mergetool
的是后用的是 BComp.exe
呢?这一点文档中有写:
BCompare.exe: This is the main application. Only one copy will run at a time, regardless of how many windows you have open. If you launch a second copy it will tell the existing copy to start a comparison and exit immediately.
BComp.exe: This is a Win32 GUI program. If launched from a version control system, it should work just fine. If launched from a console window, the console (or batch file) will not wait for it.
文档是英文的,但是比较容易理解,总的来说 BCompare.exe
是主程序,BComp.exe
用在版本控制工具中更加优秀,至于文档中提到的主程序只能启动一个副本的说明,我试了一下并不是这样的,但是这不是重点,根据文档建议,我们应该调用 BComp.exe
程序。
关于调用参数,文档中对于每种形式的比较也给出了说明,我们这里只列举两个文件和四个文件这两种参数,两个文件作为参数时常用来对比,我直接使用主程序对比文件就是这种形式,参数格式为 BCompare.exe "C:\Left File.ext" "C:\Right File.ext"
,但是使用时我常把文件直接拖拽到软件上进行比较。四个文件作为参数时常用来处理文件冲突,参数类型为 BCompare.exe C:\Left.ext C:\Right.ext C:\Center.ext C:\Output.ext
,参数中文件的名字表明处理时的位置和作用,看下面这个图就明白了。
从红框圈定的位置就可以发现和文件的对应关系了,最下面是最终的输出文件,也是我们可以手动修改的文件。
先看一下 git
仓库的原始情况
1 | albert@home-pc MINGW64 /d/gitstart (dev) |
在此基础上新建两个分支 dev1
和 dev2
1 | albert@home-pc MINGW64 /d/gitstart (dev) |
在 dev2
分支上修改 README.md
文件后提交
1 | albert@home-pc MINGW64 /d/gitstart (dev2) |
切换回 dev1
分支修改 README.md
文件后提交
1 | albert@home-pc MINGW64 /d/gitstart (dev2) |
这时在 dev1
分支上合并 dev2
分支上的修改就会产生冲突
1 | albert@home-pc MINGW64 /d/gitstart (dev1) |
冲突产生了,文档中同一位置被两个分支修改后合并导致的,内容里出现了 <<<
、===
、>>>
,包裹的内容被分成了两部分,上面一部分是当前分支修改的,下面一部分是从 dev2
分支合并过来的,还要注意虽然产生了产生了冲突,但是目录中并没有产生其他多余的文件。
这样的冲突比较简单,我们只要使用文本工具删除不想要的内容,保存后 git add README.md
,然后再 git commit
就完成了冲突的解决,但是因为配置了 git mergetool
,我们可以用它来解决冲突,直接在命令行敲命令 git mergetool
就可以:
1 | albert@home-pc MINGW64 /d/gitstart (dev1|MERGING) |
这时光标不会退出,一闪一闪并且打开 BComp.exe
工具,截图如下:
这时如果你打开 git
库所在目录会发现除了 README.md
还多了下面4个文件:
1 | README.md |
按照自己的实际情况修改最下面的文件,然后点击箭头所指的保存按钮,关闭 Beyond Compare
,查询一下仓库状态
1 | albert@home-pc MINGW64 /d/gitstart (dev1|MERGING) |
不但冲突文件没有了,还给我们自动执行 git add README.md
命令,我们只需要执行 git commit
就解决完了冲突。
1 | albert@home-pc MINGW64 /d/gitstart (dev1|MERGING) |
回过头来再看看 git mergetool
的4句配置到底有什么用
1 | git config --global merge.tool bc4 |
首先你需要知道 git config
的作用,就是用来配置 git
的,加上了 --global
表示调整全局 git
配置,不加的话就是调整当前库的 git
配置。windows上的全局配置一般在 C:\Users\用户名\.gitconfig
,如果你之前用过 git
,一般会执行过 git config --global user.name xxx
对吧,这些命令都是来调整 git
配置的,打开这个 .gitconfig
你会看到
1 | [user] |
看看最后几行就是我们添加的4项配置,只不过到文件中变成了键值对的形式,经过测试后发现,这些属性最少两级,比如 user.name
、core.autocrlf
,最多三级比如 mergetool.bc4.cmd
、 mergetool.bc4.trustExitCode
,如果级数再多会怎么办,你可以试试 git config --global a.b.c.d.e test
,它最终也会被拆成三级如下
1 | [a "b.c.d"] |
这个需要查一下官方文档了,git mergetool --help
就能打开git官方文档,文档写得真不错,排版格式看着就很舒服。
文档提到添加 --tool-help
选项可以列举可以的合并工具,展示如下
1 | albert@home-pc MINGW64 /d/gitstart (dev1) |
这一查才发现,原来 git mergetool
支持的工具有这么多,不过下面这些我都没安装,用一下上面列举的3个,试试 git mergetool --tool=vimdiff
,果然打开了一个界面
幸亏不如 Beyond Compare
好用,不然我不是白配置了,不过这些工具确实方便,都不需要配置,只要安装了参数中指定一下就可以用了,比如这个 bc3
,我猜它是 Beyond Compare 3
,只不过我安装的是 Beyond Compare 4
这个版本。
这些内置工具使用的前提是已经安装了,并且安装软件的目录放在了环境变量 Path
中,如果没有放在这个变量中需要通过 mergetool.<tool>.path
参数来配置,比如我把 Beyond Compare 3
安装在了 D
盘根目录,就可以设置 git config --global mergetool.bc3.path "D:\\"
。
我们在可用工具中没有找到 Beyond Compare 4
为什么我们可以用呢?因为 git mergetool
命令还支持自定义合并解决冲突的工具,只要指定 mergetool.<tool>.cmd
就可以调用了,就像 git mergetool --tool-help
查询结果中提到的 user-defined: bc4.cmd "D:\Program Files\Beyond Compare 4\BComp.exe" "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
,git mergetool
把 bc4
作为了一个等同于内置合并工具的软件。
再来看看这4句配置的含义:
1 | git config --global merge.tool bc4 |
第一句 git config --global merge.tool bc4
是说把 git mergetool
的默认工具配置成 bc4
,如果不指定默认工具在使用时就需要写成 git mergetool --tool=bc4
或者 git mergetool -t bc4
了,可是 bc4
是我们自己起的名字,根本就没有这个名字啊,接着往下看。
第二句 git config --global mergetool.bc4.cmd "\"D:\\mybc4\\BComp.exe\" \"\$LOCAL\" \"\$REMOTE\" \"\$BASE\" \"\$MERGED\""
指定了工具 bc4
的调用路径和参数,后面的这4个参数都是 git mergetool
命令提供的,依次代表本地修改,被合并分支修改,两端未修改前版本文件,最终合并导出的文本文件。
第三句 git config --global mergetool.bc4.trustExitCode true
, 设置为 true
表示信任软件的返回码,并依据返回码确定合并是否成功,如果设置成 false
就会在合并完成后问你是否解决完冲突,设置成 true
会方便很多。
第四句 git config --global mergetool.keepBackup false
, 是指定在合并完成后删除备份文件 *.orig
,这个文件会在调用 git mergetool
是产生 *.orig
备份文件,成功合并后自动删除就可以了。
至此终于弄明白这个 git mergetool
是怎么工作的了,但是想这样一个问题,这个 <tool>.cmd
一定得调用冲突解决工具吗?如果你从头看到这里应该会明白,这里只是给用户提供了一个调用自定义工具的方式,至于你调用什么它是不关心的,你完全可以在 git mergetool
的时候让电脑关机,这些都是可以的,在你明白了原理以后,一切都变得简单了。
Beyond Compare
是一款强大的比较工具,合理的使用可以有效的提升工作效率git mergetool
内置了很多可以使用的合并工具,并且支持调用自定义的合并工具git
的官方文档写得真的挺详细,有时间可以多看一看,你会发现很多有意思的功能尽管科技很发达,但有些人一旦分开可能真的就是一生不见了
这个命令功能单一,但是非常强大,可以用来还原C++编译后的函数名,为什么C++的函数名需要单独的命令来还原,因为他们看起来都是这样 _ZNK4Json5ValueixEPKc
、这样 _Z41__static_initialization_and_destruction_0ii
或者这样的 _ZN6apsara5pangu15ScopedChunkInfoINS0_12RafChunkInfoEED1Ev
,仅通过这一串字母很难知道原函数的名字是什么,参数类型就更难分析了,实际上C++在编译函数时有一套命名函数的规则,每种参数使用什么字母表示都是有约定的,但是通过学习这些约定来还原函数太麻烦了,还好有人编写了 c++filt
命令可以让我们直接得到编译前的函数名,真好……
C++
编译后的函数名字非常古怪,相比而言 C
语言编译后的函数看起来就正常许多了,extern "C"
、函数重载、name mangling
这些知识点都与 C++
这个奇怪的函数名有些关系,extern "C"
的作用简而言之就是告诉编译器和链接器被“我”修饰的变量和函数需要按照 C
语言方式进行编译和链接,这样做是由于 C++
支持函数重载,而 C
语言不支持,结果导致函数被 C++
编译后在符号库中的名字和被 C
语言编译后的名字是不一样的,程序编译和连接就会出现问题,此类问题一般出现在 C++
代码调用 C
语言写的库函数的时候。
而 name mangling
就是实现 C++
函数重载的一种技术或者叫做方式,要求同名的 C++
函数参数个数不同或参数类型不同,如果只有返回值类型不同,那么两个函数被认为是相同的函数,无法成功通过编译。接下来我们就来看几个例子,看看 C++
编译后的函数名有什么变化。
我们来写一段相同的代码,分别使用 gcc
和 g++
进行编译,从代码到可执行文件需要经历“预处理、编译、汇编、链接”4个步骤,接下来为了看到编译后函数名的不同,我们只进行前两步,生成汇编代码,再来比较不同。
1 | // simple.c |
gcc simple.c -S
生成汇编代码文件simple.s内容
1 | .file "simple.c" |
1 | // simple.cpp |
g++ simple.cpp -S
生成汇编代码文件simple.s内容
1 | .file "simple.cpp" |
虽然只有几行代码,可是生成汇编文件之后变成了50多行,我们只需要关注 myadd()
这个函数编译之后变成了什么就可以了,汇编代码虽然不好读,但是查找一个函数名应该没问题的,对照着上面的代码我们发现,myadd()
这个函数通过 gcc
编译之后的函数名还是 myadd
,而通过 g++
编译之后的函数名变成了 _Z5myaddii
,可以明显感觉到最后的两个字母 i
代表的是参数 int
,使用 c++filt
命令还原如下:
1 | $ c++filt _Z5myaddii |
我们还是在刚才的代码的基础上增加一个参数类型不同的 myadd
函数,修改后的代码如下:
1 | int myadd(int a, int b) |
g++ simple.cpp -S
生成汇编代码文件simple.s内容为:
1 | .file "simple.cpp" |
这次一共3个函数,生成的汇编代码更长,但是我们一眼就能看见汇编代码中包含 _Z5myaddii
和 _Z5myaddff
两个函数,这就是函数重载的产物,两个参数类型不同的同名函数编译之后生成了不同的名字,_Z5myaddff
函数末尾的两个 f
应该指的就是参数类型 float
。
c++filt的作用就是还原函数名字,它可以帮我们查找动态链接库中缺少的函数,还原崩溃堆栈中一大串的函数名字母等等,下面来看一个崩溃堆栈的例子,代码内容尽量简写,只为了说明问题,现实情况可能要复杂的多。
首先定义一个打印函数堆栈的函数,参考之前的总结《linux环境下C++代码打印函数堆栈调用情况》,代码如下:
1 | #include <execinfo.h> |
再写一段隐藏着崩溃问题的代码:
1 | #include <string> |
编译运行,果然崩溃了:
1 | $ g++ simple.cpp --std=c++11 |
这时崩溃的堆栈中发现了一个特别长的函数 _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc
,使用 c++filt
命令来还原函数:
1 | $ c++filt _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc |
从函数名来看是一个与字符串相关的 compare
函数,查看代码发现是 s == "20200517"
这一句的问题,所以说能确切的知道函数名对我们查找问题来说还是挺有帮助的。
c++filt
命令可以还原 C++
为实现函数重载采用 name mangling
搞出来的奇奇怪怪的函数名signal(SIGSEGV, show_stack);
,SIGSEGV
代表无效的内存引用C
语言和 C++
在编译后函数命名方式的不同,C
语言不支持严格意义的重载,C++支持阳光、空气、水,这些真的是好东西,当你真的快要失去它们才意识的到的话就有些晚了…
今天写这篇记录要解决的问题来源于最近一名读者的提问,之前写过一篇名为《.bat批处理(六):替换字符串中匹配的子串》的总结文章,结果有读者在评论区提问说,如果想要替换的子串中包含等号 =
,那么就无法替换了,问有没有什么办法可以解决。遇到这个问题的第一感觉应该挺好处理的吧,如果批处理程序在替换操作中认为等号 =
比较特殊,那就加个转义字符应该就可以了,但事实却证明这种想法有些天真了。