-
2022-03-03 18:51:19
利用concat方法可以合并DataFrame,该方法有几个关键的参数,分别为axis,join,ignore_index
关于axis参数:axis=0表示纵向合并,axis=1表示横向合并
df1=pd.DataFrame([[1,2,3],[2,3,4]],columns=['a','b','c']) df2=pd.DataFrame([[2,3,4],[3,4,5]],columns=['a','b','c']) pd.concat([df1,df2],axis=1) # 横向合并 a b c a b c 0 1 2 3 2 3 4 1 2 3 4 3 4 5 pd.concat([df1,df2],axis=0) # 纵向合并 a b c 0 1 2 3 1 2 3 4 0 2 3 4 1 3 4 5
关于join参数:join默认为outer,直接合并,对于某个dataframe不含其他的dataframe中的行或列时,值默认填充NaN。当join为inner时,只合并具有相同行索引或列索引的数据。
df3=pd.DataFrame([[1,2,3],[2,3,4]],columns=['a','b','d']) pd.concat([df1,df3],axis=0, join='outer') #join默认为'outer', 直接合并,对于每行数据中不存在该列值的默认为NaN a b c d 0 1 2 3.0 NaN 1 2 3 4.0 NaN 0 1 2 NaN 3.0 1 2 3 NaN 4.0 pd.concat([df1,df3],axis=0, join='inner') # axis=0, join为'inner'时只合并具有相同列索引的列 a b 0 1 2 1 2 3 0 1 2 1 2 3 df4=pd.DataFrame([[1,2,3],[2,3,4],[3,4,5]],columns=['a','b','c']) pd.concat([df1,df4],axis=1, join='outer') # 对于不含某个行索引的dataframe,该行的值默认为NaN a b c a b c 0 1.0 2.0 3.0 1 2 3 1 2.0 3.0 4.0 2 3 4 2 NaN NaN NaN 3 4 5 pd.concat([df1,df4],axis=1, join='inner') # axis=1,join为'inner'时,只合并具有相同行索引的数据df4的行索引为2的行被忽略掉了 a b c a b c 0 1 2 3 1 2 3 1 2 3 4 2 3 4
关于ignore_index参数:ignore_index参数默认为False,表示合并后仍会保留原来Datafram的行索引,当设置为True时,表示合并后会重置行索引,按顺序填充。
pd.concat([df1,df2],axis=0) # ignore_index默认为False,合并后保留原来的dataframe的行索引 a b c 0 1 2 3 1 2 3 4 0 2 3 4 1 3 4 5 pd.concat([df1,df2],axis=0, ignore_index=True) # ignore_index设置为true,合并后行索引重置 a b c 0 1 2 3 1 2 3 4 2 2 3 4 3 3 4 5
更多相关内容 -
gridControl根据条件合并单元格
2015-06-25 22:37:00devexpress实现单元格合并非常的简单,这里介绍一种根据条件合并单元格的做法。 -
Git之深入解析高级合并
2021-09-20 18:28:07了解了管理或者维护 Git 仓库、实现代码控制所需的大多数日常命令和工作流程,尝试跟了踪和提交文件的基本操作,并且掌握了暂存区和轻量级地分支及合并的威力。如果想进一步对 Git 深入学习,可以学习一些 Git 更加...一、前言
- 了解了管理或者维护 Git 仓库、实现代码控制所需的大多数日常命令和工作流程,尝试跟了踪和提交文件的基本操作,并且掌握了暂存区和轻量级地分支及合并的威力。如果想进一步对 Git 深入学习,可以学习一些 Git 更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。
- 如果还不清楚 Git 的基础使用流程、分支的管理、托管服务器的技术以及分布式工作流程等相关的技术和能力,请参考博客:
- 在 Git 中合并是相当容易的,这是因为 Git 使多次合并另一个分支变得很容易,这意味着可以有一个始终保持最新的长期分支,经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。
- 然而,有时也会有棘手的冲突,不像其他的版本控制系统,Git 并不会尝试过于聪明的合并冲突解决方案。Git 的哲学是聪明地决定无歧义的合并方案,但是如果有冲突,它不会尝试智能地自动解决它。因此,如果很久之后才合并两个分叉的分支,可能会撞上一些问题。
- 现在我们将去学习如何仔细查看那些问题是什么,以及 Git 给了我们什么工具来帮助处理这些更难办的情形,也将了解可以做的不同的、非标准类型的合并,以及看到如何后退到合并之前。
二、合并冲突
- 在Git之深入解析Git的杀手级特性·分支管理与变基的开发工作流以及远程分支的跟踪中,介绍了解决合并冲突的一些基础知识,对于更复杂的冲突,Git 提供了几个工具来帮助我们指出将会发生什么以及如何更好地处理冲突。
- 首先,在做一次可能有冲突的合并前尽可能保证工作目录是干净的,如果有正在做的工作,要么提交到一个临时分支要么储藏它,这使我们可以撤消在这里尝试做的任何事情,如果在尝试一次合并时工作目录中有未保存的改动,下面的这些技巧可能会丢失那些工作。
- 现在通过一个非常简单的例子:有一个超级简单的打印 hello world 的 Ruby 文件:
#! /usr/bin/env ruby def hello puts 'hello world' end hello()
- 在仓库中,创建一个名为 whitespace 的新分支并将所有 Unix 换行符修改为 DOS 换行符,实质上虽然改变了文件的每一行,但改变的都只是空白字符,然后修改行 “hello world” 为 “hello mundo”:
git checkout -b whitespace Switched to a new branch 'whitespace' $ unix2dos hello.rb unix2dos: converting file hello.rb to DOS format ... $ git commit -am 'converted hello.rb to DOS' [whitespace 3270f76] converted hello.rb to DOS 1 file changed, 7 insertions(+), 7 deletions(-) $ vim hello.rb $ git diff -b diff --git a/hello.rb b/hello.rb index ac51efd..e85207e 100755 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ #! /usr/bin/env ruby def hello - puts 'hello world' + puts 'hello mundo'^M end hello() $ git commit -am 'hello mundo change' [whitespace 6d338d2] hello mundo change 1 file changed, 1 insertion(+), 1 deletion(-)
- 现在切换回 master 分支并为函数增加一些注释:
$ git checkout master Switched to branch 'master' $ vim hello.rb $ git diff diff --git a/hello.rb b/hello.rb index ac51efd..36c06c8 100755 --- a/hello.rb +++ b/hello.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello world' end $ git commit -am 'document the function' [master bec6336] document the function 1 file changed, 1 insertion(+)
- 现在尝试合并入 whitespace 分支,因为修改了空白字符,所以合并会出现冲突:
$ git merge whitespace Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result.
① 中断一次合并
- 首先,我们该如何摆脱这个情况呢?我们可能并不想处理冲突这种情况,完全可以通过 git merge --abort 来简单地退出合并:
$ git status -sb ## master UU hello.rb $ git merge --abort $ git status -sb ## master
- git merge --abort 选项会尝试恢复到运行合并前的状态,但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。
- 如果出于某些原因你想要重来一次,也可以运行 git reset --hard HEAD 回到上一次提交的状态。请牢记此时任何未提交的工作都会丢失,所以请确认不需要保留任何改动。
② 忽略空白
- 在这个特定的例子中,冲突与空白有关,我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易, 因为每一行都被移除而在另一边每一行又被加回来了。默认情况下,Git 认为所有这些行都改动了,所以它不会合并文件。
- 默认合并策略可以带有参数,其中的几个正好是关于忽略空白改动的,如果看到在一次合并中有大量关于空白的问题,可以直接中止它并重做一次,这次使用 -Xignore-all-space 或 -Xignore-space-change 选项,第一个选项在比较行时完全忽略空白修改,第二个选项将一个空白符与多个连续的空白字符视作等价的:
$ git merge -Xignore-space-change whitespace Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
- 因为在本例中,实际上文件修改并没有冲突,一旦我们忽略空白修改,每一行都能被很好地合并。如果团队中的某个人可能不小心重新格式化空格为制表符或者相反的操作,这会是一个救命稻草。
③ 手动文件再合并
- 虽然 Git 对空白的预处理做得很好,还有很多其他类型的修改,Git 也许无法自动处理,但是脚本可以处理它们。例如,假设 Git 无法处理空白修改,因此我们需要手动处理,真正想要做的是对将要合并入的文件在真正合并前运行 dos2unix 程序,如果那样的话,我们该如何做呢?
- 首先,进入到合并冲突状态,然后我们想要我的版本的文件,他们的版本的文件(从我们将要合并入的分支)和共同的版本的文件(从分支叉开时的位置)的拷贝,然后我们想要修复任何一边的文件,并且为这个单独的文件重试一次合并。
- 获得这三个文件版本实际上相当容易,Git 在索引中存储了所有这些版本,在 “stages” 下每一个都有一个数字与它们关联,Stage 1 是它们共同的祖先版本,stage 2 是我的版本,stage 3 来自于 MERGE_HEAD,即将要合并入的版本(“theirs”)。
- 通过 git show 命令与一个特别的语法,可以将冲突文件的这些版本释放出一份拷贝:
$ git show :1:hello.rb > hello.common.rb $ git show :2:hello.rb > hello.ours.rb $ git show :3:hello.rb > hello.theirs.rb
- 如果想要更专业一点,也可以使用 ls-files -u 底层命令来得到这些文件的 Git blob 对象的实际 SHA-1 值:
$ git ls-files -u 100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb 100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb 100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
- :1:hello.rb 只是查找那个 blob 对象 SHA-1 值的简写。
- 既然在我们的工作目录中已经有这所有三个阶段的内容,就可以手工修复它们来修复空白问题,然后使用鲜为人知的 git merge-file 命令来重新合并那个文件:
$ dos2unix hello.theirs.rb dos2unix: converting file hello.theirs.rb to Unix format ... $ git merge-file -p \ hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb $ git diff -b diff --cc hello.rb index 36c06c8,e85207e..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,8 -1,7 +1,8 @@@ #! /usr/bin/env ruby +# prints out a greeting def hello - puts 'hello world' - puts 'hello mundo' end hello()
- 在这时我们已经漂亮地合并了那个文件,实际上,这比使用 ignore-space-change 选项要更好,因为在合并前真正地修复了空白修改而不是简单地忽略它们。在使用 ignore-space-change 进行合并操作后,最终得到了有几行是 DOS 行尾的文件,从而使提交内容混乱了。
- 如果想要在最终提交前看一下我们这边与另一边之间实际的修改,你可以使用 git diff 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。
- 要在合并前比较结果与在我们的分支上的内容,换一句话说,看看合并引入了什么,可以运行 git diff --ours:
$ git diff --ours * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index 36c06c8..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -2,7 +2,7 @@ # prints out a greeting def hello * puts 'hello world' * puts 'hello mundo' end hello()
- 这里可以很容易地看到在我们的分支上发生了什么,在这次合并中实际引入到这个文件的改动,是修改了其中一行。如果想要查看合并的结果与他们那边有什么不同,可以运行 git diff --theirs。在本例及后续的例子中,我们会使用 -b 来去除空白,因为我们将它与 Git 中的, 而不是我们清理过的 hello.theirs.rb 文件比较:
$ git diff --theirs -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index e85207e..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello mundo' end
- 最终,可以通过 git diff --base 来查看文件在两边是如何改动的:
$ git diff --base -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index ac51efd..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,8 @@ #! /usr/bin/env ruby +# prints out a greeting def hello * puts 'hello world' * puts 'hello mundo' end hello()
- 在这时,我们可以使用 git clean 命令来清理为手动合并而创建但不再有用的额外文件:
$ git clean -f Removing hello.common.rb Removing hello.ours.rb Removing hello.theirs.rb
④ 检出冲突
- 也许有时我们并不满意这样的解决方案,或许有时还要手动编辑一边或者两边的冲突,但还是依旧无法正常工作,这时需要更多的上下文关联来解决这些冲突。
- 稍微改动下例子,对于本例,我们有两个长期分支,每一个分支都有几个提交,但是在合并时却创建了一个合理的冲突:
$ git log --graph --oneline --decorate --all * f1270f7 (HEAD, master) update README * 9af9d3b add a README * 694971d update phrase to hola world | * e3eb223 (mundo) add more tests | * 7cff591 add testing script | * c3ffff1 changed text to hello mundo |/ * b7dcc89 initial hello world code
- 现在有只在 master 分支上的三次单独提交,还有其他三次提交在 mundo 分支上。如果尝试将 mundo 分支合并入 master 分支,得到一个冲突:
$ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result.
- 我们想要看一下合并冲突是什么,如果打开这个文件,将会看到类似下面的内容:
#! /usr/bin/env ruby def hello <<<<<<< HEAD puts 'hola world' ======= puts 'hello mundo' >>>>>>> mundo end hello()
- 合并的两边都向这个文件增加了内容,但是导致冲突的原因是其中一些提交修改了文件的同一个地方。
- 来探索一下现在手边可用来查明这个冲突是如何产生的工具,应该如何修复这个冲突看起来或许并不明显,这时需要更多上下文。一个很有用的工具是带 --conflict 选项的 git checkout,这会重新检出文件并替换合并冲突标记,如果想要重置标记并尝试再次解决它们的话这会很有用。
- 可以传递给 --conflict 参数 diff3 或 merge(默认选项),如果传给它 diff3,Git 会使用一个略微不同版本的冲突标记:不仅仅只给 “ours” 和 “theirs” 版本,同时也会有 “base” 版本在中间来更多的上下文:
$ git checkout --conflict=diff3 hello.rb
- 一旦运行它,文件看起来会像下面这样:
#! /usr/bin/env ruby def hello <<<<<<< ours puts 'hola world' ||||||| base puts 'hello world' ======= puts 'hello mundo' >>>>>>> theirs end hello()
- 如果你喜欢这种格式,可以通过设置 merge.conflictstyle 选项为 diff3 来做为以后合并冲突的默认选项。
$ git config --global merge.conflictstyle diff3
- git checkout 命令也可以使用 --ours 和 --theirs 选项,这是一种无需合并的快速方式,可以选择留下一边的修改而丢弃掉另一边修改。
- 当有二进制文件冲突时这可能会特别有用,因为可以简单地选择一边,或者可以只合并另一个分支的特定文件,可以做一次合并然后在提交前检出一边或另一边的特定文件。
⑤ 合并日志
- 另一个解决合并冲突有用的工具是 git log,这可以帮助我们得到那些对冲突有影响的上下文,回顾一点历史来记起为什么两条线上的开发会触碰同一片代码有时会很有用。为了得到此次合并中包含的每一个分支的所有独立提交的列表,可以使用之前在Git 选择修订历史版本中的“三点”语法(具体请参考:Git之深入解析如何选择修订的版本):
$ git log --oneline --left-right HEAD...MERGE_HEAD < f1270f7 update README < 9af9d3b add a README < 694971d update phrase to hola world > e3eb223 add more tests > 7cff591 add testing script > c3ffff1 changed text to hello mundo
- 这个漂亮的列表包含 6 个提交和每一个提交所在的不同开发路径。我们可以通过更加特定的上下文来进一步简化这个列表,如果添加 --merge 选项到 git log 中,它会只显示任何一边接触了合并冲突文件的提交:
$ git log --oneline --left-right --merge < 694971d update phrase to hola world > c3ffff1 changed text to hello mundo
- 如果运行命令时用 -p 选项代替,会得到所有冲突文件的区别,快速获得需要帮助理解为什么发生冲突的上下文,以及如何聪明地解决它,这会非常有用。
⑥ 组合式差异格式
- 因为 Git 暂存合并成功的结果,当在合并冲突状态下运行 git diff 时,只会得到现在还在冲突状态的区别,当需要查看还需要解决哪些冲突时这很有用。在合并冲突后直接运行的 git diff 会有一个相当独特的输出格式:
$ git diff diff --cc hello.rb index 0399cd5,59727f0..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,11 @@@ #! /usr/bin/env ruby def hello ++<<<<<<< HEAD + puts 'hola world' ++======= + puts 'hello mundo' ++>>>>>>> mundo end hello()
- 这种叫作“组合式差异”的格式,会在每一行给两列数据,第一列显示 “ours” 分支与工作目录的文件区别(添加或删除),第二列显示 “theirs” 分支与工作目录的拷贝区别。所以在上面的例子中可以看到 <<<<<<< 与 >>>>>>> 行在工作拷贝中但是并不在合并的任意一边中,这很有意义,合并工具因为我们的上下文被困住了,它期望我们去移除它们。
- 如果我们解决冲突再次运行 git diff,将会看到同样的事情,但是它有一点帮助:
$ vim hello.rb $ git diff diff --cc hello.rb index 0399cd5,59727f0..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,7 @@@ #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello()
- 这里显示出 “hola world” 在我们这边但不在工作拷贝中,那个 “hello mundo” 在他们那边但不在工作拷贝中, 最终 “hola mundo” 不在任何一边但是现在在工作拷贝中,在提交解决方案前这对审核很有用。
- 也可以在合并后通过 git log 来获取相同信息,查看冲突是如何解决的,如果对一个合并提交运行 git show 命令 Git 将会输出这种格式,或者也可以在 git log -p(默认情况下该命令只会展示还没有合并的补丁)命令之后加上 --cc 选项:
$ git log --cc -p -1 commit 14f41939956d80b9e17bb8721354c33f8d5b5a79 Merge: f1270f7 e3eb223 Author: Scott Chacon <kody@gmail.com> Date: Fri Sep 19 18:14:49 2014 +0200 Merge branch 'mundo' Conflicts: hello.rb diff --cc hello.rb index 0399cd5,59727f0..e1d0799 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,7 @@@ #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello()
三、撤消合并
- 虽然已经知道如何创建一个合并提交,但有时出错是在所难免的。使用 Git 最棒的一件事情是犯错是可以的,因为有可能(大多数情况下都很容易)修复它们。
- 合并提交并无不同,假设现在在一个主题分支上工作,不小心将其合并到 master 中,现在提交历史看起来是这样:
- 有两种方法来解决这个问题,这取决于你想要的结果是什么。
① 修复引用
- 如果这个不想要的合并提交只存在于我们的本地仓库中,最简单且最好的解决方案是移动分支到想要它指向的地方。大多数情况下,如果在错误的 git merge 后运行 git reset --hard HEAD~,这会重置分支指向所以它们看起来像这样:
- 之前在Git之深入解析reset命令原理以及与checkout命令的区别中已经介绍了 reset,所以现在指出这里发生了什么并不是很困难。快速复习下,reset --hard 通常会经历三步:
-
- 移动 HEAD 指向的分支,在本例中,想要移动 master 到合并提交(C6)之前所在的位置;
-
- 使索引看起来像 HEAD;
-
- 使工作目录看起来像索引。
- 这个方法的缺点是它会重写历史,在一个共享的仓库中这会造成问题的。在Git之深入解析Git的杀手级特性·分支管理与分支变基的开发工作流以及远程分支的跟踪中,知道了更多可能发生的事情; 用简单的话说就是如果其他人已经有我们将要重写的提交,我们应当避免使用 reset,如果有任何其他提交在合并之后创建了,那么这个方法也会无效,移动引用实际上会丢失那些改动。
② 还原提交
- 如果移动分支指针并不适合,Git 生成一个新提交的选项,提交将会撤消一个已存在提交的所有修改,Git 称这个操作为“还原”,在这个特定的场景下,可以像这样调用它:
$ git revert -m 1 HEAD [master b1d8379] Revert "Merge branch 'topic'"
- -m 1 标记指出 “mainline” 需要被保留下来的父结点,当引入一个合并到 HEAD(git merge topic),新提交有两个父结点:第一个是 HEAD(C6),第二个是将要合并入分支的最新提交(C4)。在本例中,我们想要撤消所有由父结点 #2(C4)合并引入的修改,同时保留从父结点 #1(C6)开始的所有内容。有还原提交的历史看起来像这样:
- 新的提交 ^M 与 C6 有完全一样的内容,所以从这儿开始就像合并从未发生过,除了“现在还没合并”的提交依然在 HEAD 的历史中,如果尝试再次合并 topic 到 master Git 会感到困惑:
$ git merge topic Already up-to-date.
- topic 中并没有东西不能从 master 中追踪到达,更糟的是,如果在 topic 中增加工作然后再次合并,Git 只会引入被还原的合并之后的修改:
- 解决这个最好的方式是撤消还原原始的合并,因为现在想要引入被还原出去的修改,然后创建一个新的合并提交:
$ git revert ^M [master 09f0126] Revert "Revert "Merge branch 'topic'"" $ git merge topic
- 在本例中,M 与 ^M 抵消了,^^M 事实上合并入了 C3 与 C4 的修改,C8 合并了 C7 的修改,所以现在 topic 已经完全被合并了。
四、其他类型的合并
- 到目前为止,都是通过一个叫作 “recursive” 的合并策略来正常处理的两个分支的正常合并,然而还有其他方式来合并两个分支到一起。
① 我们的或他们的偏好
- 首先,有另一种可以通过 “recursive” 合并模式做的有用工作,之前已经看到传递给 -X 的 ignore-all-space 与 ignore-space-change 选项,但是也可以告诉 Git 当它看见一个冲突时直接选择一边。
- 默认情况下,当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到我们的代码中并标记文件为冲突状态来解决。如果希望 Git 简单地选择特定的一边并忽略另外一边而不是手动解决冲突,可以传递给 merge 命令一个 -Xours 或 -Xtheirs 参数。
- 如果 Git 看到这个,它并不会增加冲突标记,任何可以合并的区别,它会直接合并,任何有冲突的区别,它会简单地选择全局指定的一边,包括二进制文件。如果回到之前我们使用的 “hello world” 例子中,可以看到合并入我们的分支时引发了冲突:
$ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Resolved 'hello.rb' using previous resolution. Automatic merge failed; fix conflicts and then commit the result.
- 然而如果运行时增加 -Xours 或 -Xtheirs 参数就不会有冲突:
$ git merge -Xours mundo Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb | 2 +- test.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 test.sh
- 在上例中,它并不会为 “hello mundo” 与 “hola world” 标记合并冲突,它只会简单地选取 “hola world”。然而,在那个分支上所有其他非冲突的改动都可以被成功地合并入。这个选项也可以传递给我们之前看到的 git merge-file 命令, 通过运行类似 git merge-file --ours 的命令来合并单个文件。
- 如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改,有一个更严格的选项,它是 “ours” 合并 策略,这与 “ours” recursive 合并选项不同。这本质上会做一次假的合并,它会记录一个以两边分支作为父结点的新合并提交,但是它甚至根本不关注你正合并入的分支,它只会简单地把当前分支的代码当作合并结果记录下来:
$ git merge -s ours mundo Merge made by the 'ours' strategy. $ git diff HEAD HEAD~ $
- 可以看到合并后与合并前我们的分支并没有任何区别。当再次合并时从本质上欺骗 Git 认为那个分支已经合并过经常是很有用的。例如,假设有一个分叉的 release 分支并且在上面做了一些想要在未来某个时候合并回 master 的工作。与此同时 master 分支上的某些 bugfix 需要向后移植回 release 分支,可以合并 bugfix 分支进入 release 分支同时也 merge -s ours 合并进入 master 分支 (即使那个修复已经在那儿了),这样当之后再次合并 release 分支时,就不会有来自 bugfix 的冲突。
② 子树合并
- 子树合并的思想是有两个项目,并且其中一个映射到另一个项目的一个子目录,或者反过来也行,当执行一个子树合并时,Git 通常可以自动计算出其中一个是另外一个的子树从而实现正确的合并。
- 来看一个例子如何将一个项目加入到一个已存在的项目中,然后将第二个项目的代码合并到第一个项目的子目录中。
- 首先,将 Rack 应用添加到项目里,我们把 Rack 项目作为一个远程的引用添加到我们的项目里,然后检出到它自己的分支:
$ git remote add rack_remote https://github.com/rack/rack $ git fetch rack_remote --no-tags warning: no common commits remote: Counting objects: 3184, done. remote: Compressing objects: 100% (1465/1465), done. remote: Total 3184 (delta 1952), reused 2770 (delta 1675) Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done. Resolving deltas: 100% (1952/1952), done. From https://github.com/rack/rack * [new branch] build -> rack_remote/build * [new branch] master -> rack_remote/master * [new branch] rack-0.4 -> rack_remote/rack-0.4 * [new branch] rack-0.9 -> rack_remote/rack-0.9 $ git checkout -b rack_branch rack_remote/master Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master. Switched to a new branch "rack_branch"
- 现在在我们的 rack_branch 分支里就有 Rack 项目的根目录,而我们的项目则在 master 分支里。如果从一个分支切换到另一个分支,可以看到它们的项目根目录是不同的:
$ ls AUTHORS KNOWN-ISSUES Rakefile contrib lib COPYING README bin example test $ git checkout master Switched to branch "master" $ ls README
- 这个是一个比较奇怪的概念,并不是仓库中的所有分支都是必须属于同一个项目的分支,这并不常见,因为没啥用,但是却是在不同分支里包含两条完全不同提交历史的最简单的方法。
- 在这个例子中,我们希望将 Rack 项目拉到 master 项目中作为一个子目录,可以在 Git 中执行 git read-tree 来实现,现在只需要知道它会读取一个分支的根目录树到当前的暂存区和工作目录里,先切回 master 分支,将 rack_back 分支拉取到我们项目的 master 分支中的 rack 子目录。
$ git read-tree --prefix=rack/ -u rack_branch
- 当我们提交时,那个子目录中拥有所有 Rack 项目的文件,就像我们直接从压缩包里复制出来的一样。有趣的是可以很容易地将一个分支的变更合并到另一个分支里,所以,当 Rack 项目有更新时,我们可以切换到那个分支来拉取上游的变更:
$ git checkout rack_branch $ git pull
- 接着,我们可以将这些变更合并回我们的 master 分支,使用 --squash 选项和使用 -Xsubtree 选项(它采用递归合并策略),都可以用来可以拉取变更并且预填充提交信息(递归策略在这里是默认的,提到它是为了让读者有个清晰的概念)。
$ git checkout master $ git merge --squash -s recursive -Xsubtree=rack rack_branch Squash commit -- not updating HEAD Automatic merge went well; stopped before committing as requested
- Rack 项目中所有的改动都被合并了,等待被提交到本地,也可以用相反的方法,在 master 分支上的 rack 子目录中做改动然后将它们合并入 rack_branch 分支中,之后可能将其提交给项目维护着或者将它们推送到上游。
- 这给我们提供了一种类似子模块工作流的工作方式,但是它并不需要用到子模块,可以在自己的仓库中保持一些和其他项目相关的分支,偶尔使用子树合并将它们合并到我们的项目中。某些时候这种方式很有用,例如当所有的代码都提交到一个地方的时候,然而,它同时也有缺点,它更加复杂且更容易让人犯错,例如重复合并改动或者不小心将分支提交到一个无关的仓库上去。
- 另外一个有点奇怪的地方是,当想查看 rack 子目录和 rack_branch 分支的差异,来确定是否需要合并它们,不能使用普通的 diff 命令。取而代之的是,必须使用 git diff-tree 来和目标分支做比较:
$ git diff-tree -p rack_branch
- 或者,将 rack 子目和最近一次从服务器上抓取的 master 分支进行比较,可以运行:
$ git diff-tree -p rack_remote/master
-
【MySQL】单表访问之索引合并
2022-01-01 11:29:40MySQL在一般情况下执行一个查询时最多只会用到单个二级索引,但存在有特殊情况,在这些特殊情况下也可能在一个查询中使用到多个二级索引,MySQL中这种使用到多个索引来完成一次查询的执行方法称之为:索引合并/index...MySQL在一般情况下执行一个查询时最多只会用到单个二级索引,但存在有特殊情况,在这些特殊情况下也可能在一个查询中使用到多个二级索引,MySQL中这种使用到多个索引来完成一次查询的执行方法称之为:索引合并/index merge,具体的索引合并算法有下边三种。
mysql> show index from t_emp; +-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | t_emp | 0 | PRIMARY | 1 | emp_no | A | 299246 | NULL | NULL | | BTREE | | | | t_emp | 1 | udx_union_test | 1 | hire_date | A | 4998 | NULL | NULL | | BTREE | | | | t_emp | 1 | udx_union_test | 2 | birth_date | A | 297486 | NULL | NULL | | BTREE | | | | t_emp | 1 | udx_union_test | 3 | first_name | A | 299246 | NULL | NULL | | BTREE | | | | t_emp | 1 | idx_first_name | 1 | first_name | A | 1297 | NULL | NULL | | BTREE | | | | t_emp | 1 | idx_last_name | 1 | last_name | A | 1667 | NULL | NULL | | BTREE | | | +-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 6 rows in set (0.00 sec)
Intersection合并
Intersection翻译过来的意思是交集。这里是说某个查询可以使用多个二级索引,将从多个二级索引中查询到的结果取交集,比方说下边这个查询:
mysql> select * from t_emp where first_name='Georgi' and last_name='Facello';
假设这个查询使用Intersection合并的方式执行的话,那这个过程就是这样的:
- 从idx_first_name二级索引对应的B+树中取出
first_name='Georgi'
的相关记录。从idx_last_name二级索引对应的B+树中取出last_name='Facello'
的相关记录。二级索引的记录都是由索引列+主键构成的,所以我们可以计算出这两个结果集中id值的交集。 - 按照上一步生成的id值列表进行回表操作,也就是从聚簇索引中把指定id值的完整用户记录取出来,返回给用户。
为啥不直接使用idx_first_name或者idx_last_name只根据某个搜索条件去读取一个二级索引,然后回表后再过滤另外一个搜索条件呢?这里要分析一下两种查询执行方式之间需要的成本代价。
- 只读取一个二级索引的成本:按照某个搜索条件读取一个二级索引,根据从该二级索引得到的主键值进行回表操作,然后再过滤其他的搜索条件
- 读取多个二级索引之后取交集成本:按照不同的搜索条件分别读取不同的二级索引,将从多个二级索引得到的主
键值取交集,然后进行回表操作。
虽然读取多个二级索引比读取一个二级索引消耗性能,但是大部分情况下读取二级索引的操作是顺序I/O,而回表操作是随机I/O,所以如果只读取一个二级索引时需要回表的记录数特别多,而读取多个二级索引之后取交集的记录数非常少,当节省的因为回表而造成的性能损耗比访问多个二级索引带来的性能损耗更高时,读取多个二级索引后取交集比只读取一个二级索引的成本更低。
MySQL在某些特定的情况下才可能会使用到Intersection索引合并,哪些情况呢?
情况一:等值匹配
普通的二级索引列是等值匹配的情况。
对于联合索引来说,在联合索引中的每个列都必须等值匹配,不能出现只匹配部分列的情况。而下边这两个查询就不能进行Intersection索引合并:
mysql> select * from t_emp where hire_date>'1960-04-17' and birth_date='1991-11-27' and first_name='Georgi'; mysql> select * from t_emp where hire_date='1960-04-17' and birth_date='1991-11-27';
第一个查询是因为对hire_date进行了范围匹配,第二个查询是因为联合索引udx_union_test中的first_name列并没有出现在搜索条件中,所以这两个查询不能进行Intersection索引合并。
情况二:主键列可以是范围匹配
比方说下边这个查询可能用到主键和idx_first_name进行Intersection索引合并的操作:
mysql> select * from t_emp where emp_no > 493549 and first_name='Georgi';
对于InnoDB的二级索引来说,记录先是按照索引列进行排序,如果该二级索引是一个联合索引,那么会按照联合索引中的各个列依次排序。而二级索引的记录是由索引列+主键构成的,二级索引列的值相同的记录可能会有好多条,这些索引列的值相同的记录又是按照主键的值进行排序的。
所以重点来了,之所以在二级索引列都是等值匹配的情况下才可能使用Intersection索引合并,是因为只有在这种情况下根据二级索引查询出的结果集是按照主键值排序的。
Intersection索引合并会把从多个二级索引中查询出的主键值求交集,如果从各个二级索引中查询的到的结果集本身就是已经按照主键排好序的,那么求交集的过程就很容易。
假设某个查询使用Intersection索引合并的方式从idx_first_name或者idx_last_name这两个二级索引中获取到的主键值分别是:
- 从idx_first_name中获取到已经排好序的主键值:1、3、5
- 从idx_last_name中获取到已经排好序的主键值:2、3、4
那么求交集的过程就是这样:逐个取出这两个结果集中最小的主键值,如果两个值相等,则加入最后的交集结果中,否则丢弃当前较小的主键值,再取该丢弃的主键值所在结果集的后一个主键值来比较,直到某个结果集中的主键值用完了,时间复杂度是O(n)。
但是如果从各个二级索引中查询出的结果集并不是按照主键排序的话,那就要先把结果集中的主键值排序完再来做上边的那个过程,就比较耗时了。按照有序的主键值去回表取记录有个专有名词,叫:Rowid Ordered Retrieval,简称ROR。
另外,不仅是多个二级索引之间可以采用Intersection索引合并,索引合并也可以有聚簇索引参加,也就是我们上边写的情况二:在搜索条件中有主键的范围匹配的情况下也可以使用Intersection索引合并索引合并。为啥主键这就可以范围匹配了?还是得回到应用场景里:
mysql> select * from t_emp where emp_no > 493549 and first_name='Georgi';
假设这个查询可以采用Intersection索引合并,我们理所当然的以为这个查询会分别按照
emp_no > 493549
这个条件从聚簇索引中获取一些记录,在通过first_name='Georgi'
这个条件从idx_first_name二级索引中获取一些记录,然后再求交集,其实这样就把问题复杂化了,没必要从聚簇索引中获取一次记录。别忘了二级索引的记录中都带有主键值的,所以可以在从idx_first_name中获取到的主键值上直接运用条件emp_no > 493549
过滤就行了,这样多简单。所以涉及主键的搜索条件只不过是为了从别的二级索引得到的结果集中过滤记录罢了,是不是等值匹配不重要。当然,上边说的情况一和情况二只是发生Intersection索引合并的必要条件,不是充分条件。也就是说即使情况一、情况二成立,也不一定发生Intersection索引合并,这得看优化器的心情。优化器只有在单独根据搜索条件从某个二级索引中获取的记录数太多,导致回表开销太大,而通过Intersection索引合并后需要回表的记录数大大减少时才会使用Intersection索引合并。
Union合并
我们在写查询语句时经常想把既符合某个搜索条件的记录取出来,也把符合另外的某个搜索条件的记录取出来,我们说这些不同的搜索条件之间是OR关系。
有时候OR关系的不同搜索条件会使用到不同的索引,比方说这样:
mysql> select * from t_emp where first_name='Georgi' or last_name='Facello';
Intersection是交集的意思,这适用于使用不同索引的搜索条件之间使用AND连接起来的情况;Union是并集的意思,适用于使用不同索引的搜索条件之间使用OR连接起来的情况。
与Intersection索引合并类似,MySQL在某些特定的情况下才可能会使用到Union索引合并:
情况一:等值匹配
分析同Intersection合并
情况二:主键列可以是范围匹配
分析同Intersection合并
情况三:使用Intersection索引合并的搜索条件
就是搜索条件的某些部分使用Intersection索引合并的方式得到的主键集合和其他方式得到的主键集合取交集,比方说这个查询:
mysql> select * from t_emp where (hire_date='1960-04-17' and birth_date='1991-11-27' and first_name='Georgi') or (first_name='Georgi' and last_name='Facello');
优化器可能采用这样的方式来执行这个查询:
- 先按照搜索条件
first_name='Georgi' and last_name='Facello'
从索引idx_first_name和idx_last_name中使用Intersection索引合并的方式得到一个主键集合。 - 再按照搜索条件
hire_date='1960-04-17' and birth_date='1991-11-27' and first_name='Georgi'
从联合索引udx_union_test中得到另一个主键集合。 - 采用Union索引合并的方式把上述两个主键集合取并集,然后进行回表操作,将结果返回给用户。
当然,查询条件符合了这些情况也不一定就会采用Union索引合并,也得看优化器的心情。优化器只有在单独根据搜索条件从某个二级索引中获取的记录数比较少,通过Union索引合并后进行访问的代价比全表扫描更小时才会使用Union索引合并。
Sort-Union合并
Union索引合并的使用条件太苛刻,必须保证各个二级索引列在进行等值匹配的条件下才可能被用到,比方说下边这个查询就无法使用到Union索引合并:
mysql> select * from t_emp where first_name>'Georgi' and last_name<'Facello';
这是因为根据
first_name>'Georgi'
从idx_first_name索引中获取的二级索引记录的主键值不是排好序的,根据last_name<'Facello'
从idx_last_name索引中获取的二级索引记录的主键值也不是排好序的,但是first_name>'Georgi'
和last_name<'Facello'
这两个条件又特别让我们动心,所以我们可以这样:- 先根据
first_name>'Georgi'
条件从idx_first_name二级索引中获取记录,并按照记录的主键值进行排序 - 再根据
last_name<'Facello'
条件从idx_last_name二级索引中获取记录,并按照记录的主键值进行排序 - 因为上述的两个二级索引主键值都是排好序的,剩下的操作和Union索引合并方式就一样了。
上述这种先按照二级索引记录的主键值进行排序,之后按照Union索引合并方式执行的方式称之为Sort-Union索引合并,很显然,这种Sort-Union索引合并比单纯的Union索引合并多了一步对二级索引记录的主键值排序的过程。
联合索引替代Intersection索引合并
mysql> select * from t_emp where first_name='Georgi' and last_name='Facello';
这个查询之所以可能使用Intersection索引合并的方式执行,还不是因为idx_first_name和idx_last_name是两个单独的B+树索引,要是把这两个列搞一个联合索引,那直接使用这个联合索引就把事情搞定了,何必用啥索引合并呢,就像这样:
ALTER TABLE t_emp drop index idx_first_name, idx_last_name, add index idx_full_name(first_name, last_name);
这样我们把idx_first_name、idx_last_name都干掉,再添加一个联合索引idx_fullname,使用这个联合索引进行查询简直是又快又好,既不用多读一棵B+树,也不用合并结果。
- 从idx_first_name二级索引对应的B+树中取出
-
Git 工具 - 高级合并
2021-08-15 22:48:24高级合并 在 Git 中合并是相当容易的。 因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支, 经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。 然而,有时也会有...高级合并
在 Git 中合并是相当容易的。 因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支, 经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。
然而,有时也会有棘手的冲突。 不像其他的版本控制系统,Git 并不会尝试过于聪明的合并冲突解决方案。 Git 的哲学是聪明地决定无歧义的合并方案,但是如果有冲突,它不会尝试智能地自动解决它。 因此,如果很久之后才合并两个分叉的分支,你可能会撞上一些问题。
在本节中,我们将会仔细查看那些问题是什么以及 Git 给了我们什么工具来帮助我们处理这些更难办的情形。 我们也会了解你可以做的不同的、非标准类型的合并,也会看到如何后退到合并之前。
合并冲突
我们在 遇到冲突时的分支合并 介绍了解决合并冲突的一些基础知识, 对于更复杂的冲突,Git 提供了几个工具来帮助你指出将会发生什么以及如何更好地处理冲突。
首先,在做一次可能有冲突的合并前尽可能保证工作目录是干净的。 如果你有正在做的工作,要么提交到一个临时分支要么储藏它。 这使你可以撤消在这里尝试做的 任何事情 。 如果在你尝试一次合并时工作目录中有未保存的改动,下面的这些技巧可能会使你丢失那些工作。
让我们通过一个非常简单的例子来了解一下。 我们有一个超级简单的打印 hello world 的 Ruby 文件。
#! /usr/bin/env ruby def hello puts 'hello world' end hello()
在我们的仓库中,创建一个名为
whitespace
的新分支并将所有 Unix 换行符修改为 DOS 换行符, 实质上虽然改变了文件的每一行,但改变的都只是空白字符。 然后我们修改行 “hello world” 为 “hello mundo”。$ git checkout -b whitespace Switched to a new branch 'whitespace' $ unix2dos hello.rb unix2dos: converting file hello.rb to DOS format ... $ git commit -am 'converted hello.rb to DOS' [whitespace 3270f76] converted hello.rb to DOS 1 file changed, 7 insertions(+), 7 deletions(-) $ vim hello.rb $ git diff -b diff --git a/hello.rb b/hello.rb index ac51efd..e85207e 100755 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ #! /usr/bin/env ruby def hello - puts 'hello world' + puts 'hello mundo'^M end hello() $ git commit -am 'hello mundo change' [whitespace 6d338d2] hello mundo change 1 file changed, 1 insertion(+), 1 deletion(-)
现在我们切换回我们的
master
分支并为函数增加一些注释。$ git checkout master Switched to branch 'master' $ vim hello.rb $ git diff diff --git a/hello.rb b/hello.rb index ac51efd..36c06c8 100755 --- a/hello.rb +++ b/hello.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello world' end $ git commit -am 'document the function' [master bec6336] document the function 1 file changed, 1 insertion(+)
现在我们尝试合并入我们的
whitespace
分支,因为修改了空白字符,所以合并会出现冲突。$ git merge whitespace Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result.
中断一次合并
我们现在有几个选项。 首先,让我们介绍如何摆脱这个情况。 你可能不想处理冲突这种情况,完全可以通过
git merge --abort
来简单地退出合并。$ git status -sb ## master UU hello.rb $ git merge --abort $ git status -sb ## master
git merge --abort
选项会尝试恢复到你运行合并前的状态。 但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。如果出于某些原因你想要重来一次,也可以运行
git reset --hard HEAD
回到上一次提交的状态。 请牢记此时任何未提交的工作都会丢失,所以请确认你不需要保留任何改动。忽略空白
在这个特定的例子中,冲突与空白有关。 我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易, 因为每一行都被移除而在另一边每一行又被加回来了。 默认情况下,Git 认为所有这些行都改动了,所以它不会合并文件。
默认合并策略可以带有参数,其中的几个正好是关于忽略空白改动的。 如果你看到在一次合并中有大量关于空白的问题,你可以直接中止它并重做一次, 这次使用
-Xignore-all-space
或-Xignore-space-change
选项。 第一个选项在比较行时 完全忽略 空白修改,第二个选项将一个空白符与多个连续的空白字符视作等价的。$ git merge -Xignore-space-change whitespace Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
因为在本例中,实际上文件修改并没有冲突,一旦我们忽略空白修改,每一行都能被很好地合并。
如果你的团队中的某个人可能不小心重新格式化空格为制表符或者相反的操作,这会是一个救命稻草。
手动文件再合并
虽然 Git 对空白的预处理做得很好,还有很多其他类型的修改,Git 也许无法自动处理,但是脚本可以处理它们。 例如,假设 Git 无法处理空白修改因此我们需要手动处理。
我们真正想要做的是对将要合并入的文件在真正合并前运行
dos2unix
程序。 所以如果那样的话,我们该如何做?首先,我们进入到了合并冲突状态。 然后我们想要我的版本的文件,他们的版本的文件(从我们将要合并入的分支)和共同的版本的文件(从分支叉开时的位置)的拷贝。 然后我们想要修复任何一边的文件,并且为这个单独的文件重试一次合并。
获得这三个文件版本实际上相当容易。 Git 在索引中存储了所有这些版本,在 “stages” 下每一个都有一个数字与它们关联。 Stage 1 是它们共同的祖先版本,stage 2 是你的版本,stage 3 来自于
MERGE_HEAD
,即你将要合并入的版本(“theirs”)。通过
git show
命令与一个特别的语法,你可以将冲突文件的这些版本释放出一份拷贝。$ git show :1:hello.rb > hello.common.rb $ git show :2:hello.rb > hello.ours.rb $ git show :3:hello.rb > hello.theirs.rb
如果你想要更专业一点,也可以使用
ls-files -u
底层命令来得到这些文件的 Git blob 对象的实际 SHA-1 值。$ git ls-files -u 100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb 100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb 100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
:1:hello.rb
只是查找那个 blob 对象 SHA-1 值的简写。既然在我们的工作目录中已经有这所有三个阶段的内容,我们可以手工修复它们来修复空白问题,然后使用鲜为人知的
git merge-file
命令来重新合并那个文件。$ dos2unix hello.theirs.rb dos2unix: converting file hello.theirs.rb to Unix format ... $ git merge-file -p \ hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb $ git diff -b diff --cc hello.rb index 36c06c8,e85207e..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,8 -1,7 +1,8 @@@ #! /usr/bin/env ruby +# prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello()
在这时我们已经漂亮地合并了那个文件。 实际上,这比使用
ignore-space-change
选项要更好,因为在合并前真正地修复了空白修改而不是简单地忽略它们。 在使用ignore-space-change
进行合并操作后,我们最终得到了有几行是 DOS 行尾的文件,从而使提交内容混乱了。如果你想要在最终提交前看一下我们这边与另一边之间实际的修改, 你可以使用
git diff
来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。 让我们看看它们。要在合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么,可以运行
git diff --ours
$ git diff --ours * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index 36c06c8..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -2,7 +2,7 @@ # prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello()
这里我们可以很容易地看到在我们的分支上发生了什么,在这次合并中我们实际引入到这个文件的改动,是修改了其中一行。
如果我们想要查看合并的结果与他们那边有什么不同,可以运行
git diff --theirs
。 在本例及后续的例子中,我们会使用-b
来去除空白,因为我们将它与 Git 中的, 而不是我们清理过的hello.theirs.rb
文件比较。$ git diff --theirs -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index e85207e..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello mundo' end
最终,你可以通过
git diff --base
来查看文件在两边是如何改动的。$ git diff --base -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index ac51efd..44d0a25 100755 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,8 @@ #! /usr/bin/env ruby +# prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello()
在这时我们可以使用
git clean
命令来清理我们为手动合并而创建但不再有用的额外文件。$ git clean -f Removing hello.common.rb Removing hello.ours.rb Removing hello.theirs.rb
检出冲突
也许有时我们并不满意这样的解决方案,或许有时还要手动编辑一边或者两边的冲突,但还是依旧无法正常工作,这时我们需要更多的上下文关联来解决这些冲突。
让我们来稍微改动下例子。 对于本例,我们有两个长期分支,每一个分支都有几个提交,但是在合并时却创建了一个合理的冲突。
$ git log --graph --oneline --decorate --all * f1270f7 (HEAD, master) update README * 9af9d3b add a README * 694971d update phrase to hola world | * e3eb223 (mundo) add more tests | * 7cff591 add testing script | * c3ffff1 changed text to hello mundo |/ * b7dcc89 initial hello world code
现在有只在
master
分支上的三次单独提交,还有其他三次提交在mundo
分支上。 如果我们尝试将mundo
分支合并入master
分支,我们得到一个冲突。$ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result.
我们想要看一下合并冲突是什么。 如果我们打开这个文件,我们将会看到类似下面的内容:
#! /usr/bin/env ruby def hello <<<<<<< HEAD puts 'hola world' ======= puts 'hello mundo' >>>>>>> mundo end hello()
合并的两边都向这个文件增加了内容,但是导致冲突的原因是其中一些提交修改了文件的同一个地方。
让我们探索一下现在你手边可用来查明这个冲突是如何产生的工具。 应该如何修复这个冲突看起来或许并不明显。 这时你需要更多上下文。
一个很有用的工具是带
--conflict
选项的git checkout
。 这会重新检出文件并替换合并冲突标记。 如果想要重置标记并尝试再次解决它们的话这会很有用。可以传递给
--conflict
参数diff3
或merge
(默认选项)。 如果传给它diff3
,Git 会使用一个略微不同版本的冲突标记: 不仅仅只给你 “ours” 和 “theirs” 版本,同时也会有 “base” 版本在中间来给你更多的上下文。$ git checkout --conflict=diff3 hello.rb
一旦我们运行它,文件看起来会像下面这样:
#! /usr/bin/env ruby def hello <<<<<<< ours puts 'hola world' ||||||| base puts 'hello world' ======= puts 'hello mundo' >>>>>>> theirs end hello()
如果你喜欢这种格式,可以通过设置
merge.conflictstyle
选项为diff3
来做为以后合并冲突的默认选项。$ git config --global merge.conflictstyle diff3
git checkout
命令也可以使用--ours
和--theirs
选项,这是一种无需合并的快速方式,你可以选择留下一边的修改而丢弃掉另一边修改。当有二进制文件冲突时这可能会特别有用,因为可以简单地选择一边,或者可以只合并另一个分支的特定文件——可以做一次合并然后在提交前检出一边或另一边的特定文件。
合并日志
另一个解决合并冲突有用的工具是
git log
。 这可以帮助你得到那些对冲突有影响的上下文。 回顾一点历史来记起为什么两条线上的开发会触碰同一片代码有时会很有用。为了得到此次合并中包含的每一个分支的所有独立提交的列表, 我们可以使用之前在 三点 学习的“三点”语法。
$ git log --oneline --left-right HEAD...MERGE_HEAD < f1270f7 update README < 9af9d3b add a README < 694971d update phrase to hola world > e3eb223 add more tests > 7cff591 add testing script > c3ffff1 changed text to hello mundo
这个漂亮的列表包含 6 个提交和每一个提交所在的不同开发路径。
我们可以通过更加特定的上下文来进一步简化这个列表。 如果我们添加
--merge
选项到git log
中,它会只显示任何一边接触了合并冲突文件的提交。$ git log --oneline --left-right --merge < 694971d update phrase to hola world > c3ffff1 changed text to hello mundo
如果你运行命令时用
-p
选项代替,你会得到所有冲突文件的区别。 快速获得你需要帮助理解为什么发生冲突的上下文,以及如何聪明地解决它,这会 非常 有用。组合式差异格式
因为 Git 暂存合并成功的结果,当你在合并冲突状态下运行
git diff
时,只会得到现在还在冲突状态的区别。 当需要查看你还需要解决哪些冲突时这很有用。在合并冲突后直接运行的
git diff
会给你一个相当独特的输出格式。$ git diff diff --cc hello.rb index 0399cd5,59727f0..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,11 @@@ #! /usr/bin/env ruby def hello ++<<<<<<< HEAD + puts 'hola world' ++======= + puts 'hello mundo' ++>>>>>>> mundo end hello()
这种叫作“组合式差异”的格式会在每一行给你两列数据。 第一列为你显示 “ours” 分支与工作目录的文件区别(添加或删除), 第二列显示 “theirs” 分支与工作目录的拷贝区别。
所以在上面的例子中可以看到
<<<<<<<
与>>>>>>>
行在工作拷贝中但是并不在合并的任意一边中。 这很有意义,合并工具因为我们的上下文被困住了,它期望我们去移除它们。如果我们解决冲突再次运行
git diff
,我们将会看到同样的事情,但是它有一点帮助。$ vim hello.rb $ git diff diff --cc hello.rb index 0399cd5,59727f0..0000000 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,7 @@@ #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello()
这里显示出 “hola world” 在我们这边但不在工作拷贝中,那个 “hello mundo” 在他们那边但不在工作拷贝中, 最终 “hola mundo” 不在任何一边但是现在在工作拷贝中。在提交解决方案前这对审核很有用。
也可以在合并后通过
git log
来获取相同信息,查看冲突是如何解决的。 如果你对一个合并提交运行git show
命令 Git 将会输出这种格式, 或者你也可以在git log -p
(默认情况下该命令只会展示还没有合并的补丁)命令之后加上--cc
选项。$ git log --cc -p -1 commit 14f41939956d80b9e17bb8721354c33f8d5b5a79 Merge: f1270f7 e3eb223 Author: Scott Chacon <schacon@gmail.com> Date: Fri Sep 19 18:14:49 2014 +0200 Merge branch 'mundo' Conflicts: hello.rb diff --cc hello.rb index 0399cd5,59727f0..e1d0799 --- a/hello.rb +++ b/hello.rb @@@ -1,7 -1,7 +1,7 @@@ #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello()
撤消合并
虽然你已经知道如何创建一个合并提交,但有时出错是在所难免的。 使用 Git 最棒的一件事情是犯错是可以的,因为有可能(大多数情况下都很容易)修复它们。
合并提交并无不同。 假设现在在一个主题分支上工作,不小心将其合并到
master
中,现在提交历史看起来是这样:Figure 138. 意外的合并提交
有两种方法来解决这个问题,这取决于你想要的结果是什么。
修复引用
如果这个不想要的合并提交只存在于你的本地仓库中,最简单且最好的解决方案是移动分支到你想要它指向的地方。 大多数情况下,如果你在错误的
git merge
后运行git reset --hard HEAD~
,这会重置分支指向所以它们看起来像这样:Figure 139. 在
git reset --hard HEAD~
之后的历史我们之前在 重置揭密 已经介绍了
reset
,所以现在指出这里发生了什么并不是很困难。 让我们快速复习下:reset --hard
通常会经历三步:-
移动 HEAD 指向的分支。 在本例中,我们想要移动
master
到合并提交(C6
)之前所在的位置。 -
使索引看起来像 HEAD。
-
使工作目录看起来像索引。
这个方法的缺点是它会重写历史,在一个共享的仓库中这会造成问题的。 查阅 变基的风险 来了解更多可能发生的事情; 用简单的话说就是如果其他人已经有你将要重写的提交,你应当避免使用
reset
。 如果有任何其他提交在合并之后创建了,那么这个方法也会无效;移动引用实际上会丢失那些改动。还原提交
如果移动分支指针并不适合你,Git 给你一个生成一个新提交的选项,提交将会撤消一个已存在提交的所有修改。 Git 称这个操作为“还原”,在这个特定的场景下,你可以像这样调用它:
$ git revert -m 1 HEAD [master b1d8379] Revert "Merge branch 'topic'"
-m 1
标记指出 “mainline” 需要被保留下来的父结点。 当你引入一个合并到HEAD
(git merge topic
),新提交有两个父结点:第一个是HEAD
(C6
),第二个是将要合并入分支的最新提交(C4
)。 在本例中,我们想要撤消所有由父结点 #2(C4
)合并引入的修改,同时保留从父结点 #1(C6
)开始的所有内容。有还原提交的历史看起来像这样:
Figure 140. 在
git revert -m 1
后的历史新的提交
^M
与C6
有完全一样的内容,所以从这儿开始就像合并从未发生过,除了“现在还没合并”的提交依然在HEAD
的历史中。 如果你尝试再次合并topic
到master
Git 会感到困惑:$ git merge topic Already up-to-date.
topic
中并没有东西不能从master
中追踪到达。 更糟的是,如果你在topic
中增加工作然后再次合并,Git 只会引入被还原的合并 之后 的修改。Figure 141. 含有坏掉合并的历史
解决这个最好的方式是撤消还原原始的合并,因为现在你想要引入被还原出去的修改,然后 创建一个新的合并提交:
$ git revert ^M [master 09f0126] Revert "Revert "Merge branch 'topic'"" $ git merge topic
Figure 142. 在重新合并一个还原合并后的历史
在本例中,
M
与^M
抵消了。^^M
事实上合并入了C3
与C4
的修改,C8
合并了C7
的修改,所以现在topic
已经完全被合并了。其他类型的合并
到目前为止我们介绍的都是通过一个叫作 “recursive” 的合并策略来正常处理的两个分支的正常合并。 然而还有其他方式来合并两个分支到一起。 让我们来快速介绍其中的几个。
我们的或他们的偏好
首先,有另一种我们可以通过 “recursive” 合并模式做的有用工作。 我们之前已经看到传递给
-X
的ignore-all-space
与ignore-space-change
选项, 但是我们也可以告诉 Git 当它看见一个冲突时直接选择一边。默认情况下,当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到你的代码中并标记文件为冲突状态来让你解决。 如果你希望 Git 简单地选择特定的一边并忽略另外一边而不是让你手动解决冲突,你可以传递给
merge
命令一个-Xours
或-Xtheirs
参数。如果 Git 看到这个,它并不会增加冲突标记。 任何可以合并的区别,它会直接合并。 任何有冲突的区别,它会简单地选择你全局指定的一边,包括二进制文件。
如果我们回到之前我们使用的 “hello world” 例子中,我们可以看到合并入我们的分支时引发了冲突。
$ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Resolved 'hello.rb' using previous resolution. Automatic merge failed; fix conflicts and then commit the result.
然而如果我们运行时增加
-Xours
或-Xtheirs
参数就不会有冲突。$ git merge -Xours mundo Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb | 2 +- test.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 test.sh
在上例中,它并不会为 “hello mundo” 与 “hola world” 标记合并冲突,它只会简单地选取 “hola world”。 然而,在那个分支上所有其他非冲突的改动都可以被成功地合并入。
这个选项也可以传递给我们之前看到的
git merge-file
命令, 通过运行类似git merge-file --ours
的命令来合并单个文件。如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改, 有一个更严格的选项,它是 “ours” 合并 策略。 这与 “ours” recursive 合并 选项 不同。
这本质上会做一次假的合并。 它会记录一个以两边分支作为父结点的新合并提交,但是它甚至根本不关注你正合并入的分支。 它只会简单地把当前分支的代码当作合并结果记录下来。
$ git merge -s ours mundo Merge made by the 'ours' strategy. $ git diff HEAD HEAD~ $
你可以看到合并后与合并前我们的分支并没有任何区别。
当再次合并时从本质上欺骗 Git 认为那个分支已经合并过经常是很有用的。 例如,假设你有一个分叉的
release
分支并且在上面做了一些你想要在未来某个时候合并回master
的工作。 与此同时master
分支上的某些 bugfix 需要向后移植回release
分支。 你可以合并 bugfix 分支进入release
分支同时也merge -s ours
合并进入你的master
分支 (即使那个修复已经在那儿了)这样当你之后再次合并release
分支时,就不会有来自 bugfix 的冲突。子树合并
子树合并的思想是你有两个项目,并且其中一个映射到另一个项目的一个子目录,或者反过来也行。 当你执行一个子树合并时,Git 通常可以自动计算出其中一个是另外一个的子树从而实现正确的合并。
我们来看一个例子如何将一个项目加入到一个已存在的项目中,然后将第二个项目的代码合并到第一个项目的子目录中。
首先,我们将 Rack 应用添加到你的项目里。 我们把 Rack 项目作为一个远程的引用添加到我们的项目里,然后检出到它自己的分支。
$ git remote add rack_remote https://github.com/rack/rack $ git fetch rack_remote --no-tags warning: no common commits remote: Counting objects: 3184, done. remote: Compressing objects: 100% (1465/1465), done. remote: Total 3184 (delta 1952), reused 2770 (delta 1675) Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done. Resolving deltas: 100% (1952/1952), done. From https://github.com/rack/rack * [new branch] build -> rack_remote/build * [new branch] master -> rack_remote/master * [new branch] rack-0.4 -> rack_remote/rack-0.4 * [new branch] rack-0.9 -> rack_remote/rack-0.9 $ git checkout -b rack_branch rack_remote/master Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master. Switched to a new branch "rack_branch"
现在在我们的
rack_branch
分支里就有 Rack 项目的根目录,而我们的项目则在master
分支里。 如果你从一个分支切换到另一个分支,你可以看到它们的项目根目录是不同的:$ ls AUTHORS KNOWN-ISSUES Rakefile contrib lib COPYING README bin example test $ git checkout master Switched to branch "master" $ ls README
这个是一个比较奇怪的概念。 并不是仓库中的所有分支都是必须属于同一个项目的分支. 这并不常见,因为没啥用,但是却是在不同分支里包含两条完全不同提交历史的最简单的方法。
在这个例子中,我们希望将 Rack 项目拉到
master
项目中作为一个子目录。 我们可以在 Git 中执行git read-tree
来实现。 你可以在 Git 内部原理 中查看更多read-tree
的相关信息,现在你只需要知道它会读取一个分支的根目录树到当前的暂存区和工作目录里。 先切回你的master
分支,将rack_back
分支拉取到我们项目的master
分支中的rack
子目录。$ git read-tree --prefix=rack/ -u rack_branch
当我们提交时,那个子目录中拥有所有 Rack 项目的文件 —— 就像我们直接从压缩包里复制出来的一样。 有趣的是你可以很容易地将一个分支的变更合并到另一个分支里。 所以,当 Rack 项目有更新时,我们可以切换到那个分支来拉取上游的变更。
$ git checkout rack_branch $ git pull
接着,我们可以将这些变更合并回我们的
master
分支。 使用--squash
选项和使用-Xsubtree
选项(它采用递归合并策略), 都可以用来可以拉取变更并且预填充提交信息。 (递归策略在这里是默认的,提到它是为了让读者有个清晰的概念。)$ git checkout master $ git merge --squash -s recursive -Xsubtree=rack rack_branch Squash commit -- not updating HEAD Automatic merge went well; stopped before committing as requested
Rack 项目中所有的改动都被合并了,等待被提交到本地。 你也可以用相反的方法——在
master
分支上的rack
子目录中做改动然后将它们合并入你的rack_branch
分支中,之后你可能将其提交给项目维护着或者将它们推送到上游。这给我们提供了一种类似子模块工作流的工作方式,但是它并不需要用到子模块 (有关子模块的内容我们会在 子模块 中介绍)。 我们可以在自己的仓库中保持一些和其他项目相关的分支,偶尔使用子树合并将它们合并到我们的项目中。 某些时候这种方式很有用,例如当所有的代码都提交到一个地方的时候。 然而,它同时也有缺点,它更加复杂且更容易让人犯错,例如重复合并改动或者不小心将分支提交到一个无关的仓库上去。
另外一个有点奇怪的地方是,当你想查看
rack
子目录和rack_branch
分支的差异—— 来确定你是否需要合并它们——你不能使用普通的diff
命令。 取而代之的是,你必须使用git diff-tree
来和你的目标分支做比较:$ git diff-tree -p rack_branch
或者,将你的
rack
子目和最近一次从服务器上抓取的master
分支进行比较,你可以运行:$ git diff-tree -p rack_remote/master
-
-
Java使用POI操作Excel合并单元格
2022-04-07 15:32:30合并单元格,主要是采用数据模型,先把数据处理好,从中找规律 -
各类Excel表格批量合并问题的实现思路与案例
2021-06-26 22:01:39在日常工作中,网友、朋友或同事总是会遇到各类表格合并的需求,其实这类需求呢只要搞懂核心需求都非常简单,万变不离其宗。 基本思路: 遍历需要被合并的文件 pandas读取数据,并合并数据 保存数据 对样式无要求... -
antd table合并行或者列(动态添加合并行、列)
2022-02-25 11:50:53antd table合并行或者列(动态添加合并行、列) -
web前端高级JavaScript - 对象的深合并与浅合并
2020-11-18 21:28:48在讲对象的深合并与浅合并前,我们先来封装几个数据类型检测的方法,便于后面使用。 以下所有封装的方法在 JQuery源码分析 - 数据类型检测方法封装(数字、对象、数组类数组)中已经有详细讲解。这里就不再多述。 ... -
区域合并方法
2021-12-11 19:45:00区域合并 区域合并方法将合并具有相似性的相邻区域。 步骤如下: (1)图像初始区域分割:在极端情况下,可以认为每个像素为一个小区域。 (2)确定相似性准则:可以基于相邻区域的灰度、颜色、纹理等参量来比较。 ... -
java jdk8 使用stream实现两个list集合合并成一个list集合(对象属性的合并)
2022-01-14 16:38:11目录一、前言二、示例示例1:java8 合并两个 list示例2:java8 合并两个 list示例3:java8 合并两个 list,集合个数前者小于后者,要后者示例4:java8 合并两个 list,集合个数前者大于后者,要后者 一、前言 为什么... -
python DataFrame数据合并 merge()、concat()方法
2022-03-09 21:04:32常规合并①方法1②方法2重要参数合并方式 left right outer inner准备数据‘inner(默认)outerleftright2.多对一合并3.多对多合并concat()1.相同字段的表首位相连2.横向表合并3.交叉合并4.指定表对其(行对齐) merge... -
HBase合并详解
2020-09-05 21:02:38本文介绍了HBase的合并原因、合并原理、合并分类、何时触发合并,以及对大合并的性能优化等内容,这也是HBase不同于其他关系型数据库的一种新特性,也是HBase能存储大量数据的写入不会慢,而且随着数据量的增加,... -
Git合并分支超详细解释
2021-02-07 13:02:13在执行"合并"操作时,我们可以根据具体情况,选择不同的合并模式去合并,不同的合并模式对应了不同的命令参数,而且在合并的过程中,还可能遇到"冲突"。 为了说明白原理,我们先来看一些示意图 此示意图并没有涉及... -
git 两个分支合并部分文件,合并某个分支上的单个commit
2022-03-24 16:32:34git 合并分支的部分文件,git合并分支的某一次commit -
git之如何合并部分提交
2022-05-18 11:43:05在日常工作中经常会有这样的场景,在正常的功能开发中突然有紧急的bug修改,或者是紧急功能开发上线,在上线时又不能将正在开发中的功能合并到主干,此时我们就需要只合并需要紧急上线的代码到主干,接下来我们就看... -
element ui 的table单元格合并
2022-04-12 17:08:56elementUI的table表格实现单元格合并 -
Bootstrap 合并单元格
2022-03-06 15:49:48合并效果图 Bootstrap其他请求的属性我们就不写了 和平常的一样 重点是columns var columns = [ [ { field : 'merchantId', title : '商户ID', align : 'center', colspan : 1, rowspan : 2 }, { ... -
利用Python pandas对Excel进行合并的方法示例
2020-12-22 11:08:35博主也是新手一枚,代码肯定有很多需要优化的地方,欢迎各位大佬提出建议~代码我自己已经用了一段时间,可以直接拿去用主要功能按行合并 ,即保留固定的表头(如前几行),实现多个Excel相同格式相同名字的表单按纵轴... -
MMGG聚焦 | Kiln测试进行时,5分钟带你了解ETH合并
2022-03-30 11:24:27聊一聊ETH合并,我们需要了解什么和关注什么? 先了解一下当前市场背景: 发文时:ETH目前价格3316附近,近七日上涨15% 1.官方没有具体的合并时间,但开发者和社区认为 6 月中旬将会是一个“合并”的时间。 2.... -
git 合并分支
2021-12-12 20:21:37现在想要把one分支合并到master分支下 首先切换到master分支下 git checkout master 把远程master上的代码pull下来 git pull origin master 查看状态 git status 执行提交命令 git push origin ... -
python合并视频
2022-01-28 16:05:19安装包 pip install moviepy -i https://pypi.tuna.tsinghua.edu.cn/simple 视频数据 ...from moviepy.editor import VideoFileClip,concatenate_videoclips ...save_path="./合并结果.mp4"#合并后的视频名称 .. -
【Python】openpyxl单元格合并
2022-04-06 09:17:21# 需要合并的左上方和右下方单元格坐标 ws.merge_cells(range_string='A1:B3') ws.merge_cells(start_row=5, start_column=4, end_row=8, end_column=8) # 单元格取消合并使用unmerge_cells ws.unmerge_cells(range... -
硬盘分区进行合并 硬盘分区后怎么合并
2021-07-10 00:15:05原标题:"小编教你将硬盘分区进行合并"关于电脑问题教程分享。 - 来源:191路由网 - 编辑:小元。大家经常会遇到电脑系统C盘空间不够用,往往通过清理C盘的垃圾文件来保持容量,但这样电脑使用不久后又会产生很多垃圾... -
如何合并pdf文件,pdf合并方法
2022-04-01 10:24:31如何合并pdf文件,pdf合并方法。今天我就在这里教大家pdf合并的方法,工作中经常需要合并pdf文件的小伙伴请仔细阅读哦,争取一次性学会。 -
antd Table 组件动态合并单元格
2020-08-28 16:00:35使用 antd 开发 PC端应用的人肯定知道的 Table 组件。一个功能很完善,界面很优化的表格组件。通过查阅官方文档,你可以很轻松地使用这个组件。但是如果表格中涉及到合并单元格昵? -
空值合并运算符
2021-12-21 14:59:33空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。 -
table标签合并单元格(包括行和列的合并)的方法
2021-06-09 17:04:47刘代码已经在table标签的使用方法中介绍过该标签的一些具体用法,但这次,我们要讲解的是如何合并table表格中的单元格,包括行的合并以及列的合并方法table表格单元格的合并,主要分为两种:跨行合并单元格和跨列... -
wps两列合并成一列(表格中怎么把两列内容合并)
2021-06-13 01:16:24B2 ",在单元格联合体中输入" = "后,用鼠标选择第一列要合并的单元格,然后输入合并符号&,再用鼠标选择第二列单元格。3.输入公式后,按回车键,合并成功。然后选中单元格右下角的十字填充标记,直接填充下拉... -
Pandas怎样实现DataFrame的合并
2022-05-10 21:20:58一、merge(合并)的语法: pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=True, suffixes=('_x', '_y'), copy=True, indicator=False, ...