dotnet 职业技术学院

Git

dotnet 职业技术学院

| git

按类别查找文章:Git


uwp dotnet dotnet-core dotnet-standard csharp 技术 gif解析 wpf windows C# WPF D2D DirectX UWP DirectComposition win2d SharpDX VisualStudio dotnetremoting rpc visualstudio Win10 编程 slack web team msbuild linux xamarin ios resharper nuget xaml algorithm powershell ime vscode directx roslyn markdown ui ux miscellaneous cpp sharpdx algorithms JavaScript Unicode c# 工具 系统 架构 编译 数学 几何 C#入门 原理 Powershell 性能测试 命令行 asp aspdotnetcore dotnetcore 控制台 WMI .net framework .net源代码 源代码分析 Latex C 算法 Emit Roslyn MSBuild 编译器 调试 渲染 Resharper 打包 Pandoc PowerShell VisualStudio插件 TotalCommander 软件 Jenkins gif await 安装包 InnoSetup 触摸 WPF调试 图片处理 黑科技 UI sublimetext usb 笔迹 输入法 数据库 sqlite Framework dotnetframework remoting 布局 mvvm frp Avalonia 设计规范 规范 反射 jekyll DevOps AzureDevOps 源代码 多线程 VisualStudio调试 doevents 性能优化 水印 uwp文件 pip python 软件设计 文档 docfx 资源分享 p2p 爬虫 SublimeText AE dotpeek 反编译 btsync pandoc Telegram 聊天软件 微信 P2P PPT v8 .NET JVM Direct2D MobaXterm 代理 ssh vps 代理服务器 mock 单元测试 NuGet dnc 进程通信 IPC pipe ScrollViewer WPF源代码 ink dotnettool tool Github GithubAction Diagnostics DUMP Xamarin GTK
2020
12-03 2020

从 gitlab 配置管理聊聊团队项目管理

每个团队都有适合各自的管理方法,本文仅记录我现在的团队所使用的项目管理方法。本文主要聊的是通过 gitlab 的里程碑以及 git 的分支管理项目的开发和送测的代码合并问题

12-03 2020

How to generate NuGet package with Git Tag version using GitHub Action

We can trigger the GitHub Action by Git tag pushed and we can read the Git tag name as the version. And then we can generate the NuGet package with this version.

12-03 2020

GitHub 的 Action 如何禁用

我 Fork 了小伙伴的 ant-design-blazor 仓库,这个仓库设置了每天自动同步样式,这个 Action 用到了源仓库的密钥,在我 Fork 的仓库一定跑不通过,于是每天我就收到一次构建不通过的信息。本文告诉大家如何禁用自己 Fork 的某个仓库的 Action 的执行

12-03 2020

GitHub 的 Action 判断仅在主仓库才执行脚本

我有一个 GitHub 项目,这个项目配置了仅需要在源仓库才能执行的 Action 如推送 NuGet 等发布动作。如何在 Action 里面设置让 Fork 的仓库不执行 Action 的步骤

12-03 2020

dotnet 配合 GitHub 的 Action 做自动推 Tag 时打包 NuGet 包

被微软收购的 GitHub 越来越好用,拥有大量免费的工具资源和构建服务器资源,再加上私有项目的无限制使用,我有大量的项目都在向 GitHub 迁移。通过 GitHub 的 Action 的自动构建,可以用上微软土豪的服务器资源,进行自动化测试和构建。对于 CBB 来说,发布就是打出 NuGet 包然后上传到内部 NuGet 服务器。此时遇到的问题是,如何在 GitHub 上执行打包,打包的时候如何指定 NuGet 包的版本号。因为 CBB 的特殊性,我要求每个 NuGet 正式发布的包都应该有一个对应的 Tag 号,这样将 NuGet 库安装到项目里面,之后发现问题了还能找到对应版本的代码

12-03 2020

dotnet 配合 Gitlab 做自动推 Tag 时打包 NuGet 包

我现在的团队内部用的是 Gitlab 工具,在此工具上提供了 Gitlab CI CD 用于做自动化测试和构建。对于 CBB 来说,发布就是打出 NuGet 包然后上传到内部 NuGet 服务器。此时遇到的问题是,如何在 Gitlab 上执行打包,打包的时候如何指定 NuGet 包的版本号。因为 CBB 的特殊性,我要求每个 NuGet 正式发布的包都应该有一个对应的 Tag 号,这样将 NuGet 库安装到项目里面,之后发现问题了还能找到对应版本的代码 本文告诉大家如何配合 Gitlab 做自动推 Tag 时打包 NuGet 包。也就是本地打一个 Tag 号,推送到 Gitlab 上,就会出发 Gitlab 的自动构建,自动构建里面将会获取 Tag 版本号,然后打出 NuGet 包推送到服务器

12-03 2020

github 设置自动删除合并的分支

在 github 上提 MR 在合并之后的分支,很少需要继续保存,如果真的有需求,建议使用 tag 保存,而在合并之后不需要这个分支,默认的 github 不会删除这个分支

12-03 2020

Github 添加 Action 编译图标

在我的仓库里面,可以在首页添加图标显示当前是否编译通过

12-03 2020

dotnet 在 GitHub 的 Action 上部署自动代码编码规范机器人

我们的项目中会包含有很多文件,但是可能我们没有注意到的,我们的文件的编码不一定是 UTF-8 编码,这就可能让构建出来的应用程序在别人电脑运行时出现乱码,或者别人拉下来代码,却发现代码里面的中文都是乱码。为了解决文件编码的问题,咱需要一个编码规范工具,本文将告诉大家在 GitHub 上仓库,可以利用 GitHub 的 Action 部署自动代码文件编码规范的机器人,这个机器人可以自动协助咱规范文件的编码规范。可以设置为每次上传代码的时候,自动帮忙设置文件编码为 UTF-8 编码。或者在每次代码合并到主分支之后,机器人将会尝试修复文件的编码,如存在文件需要修复的,那机器人将会创建一个代码审查

12-03 2020

dotnet 基于 dotnet format 的 GitHub Action 自动代码格式化机器人

是不是大家也会觉得代码审查里面审查代码格式化问题是无意义的,但是不审查又觉得过不去?是否有个专门的工具人,用来协助修复代码格式化的问题?本文来安利大家一个特别好用的方法,使用 dotnet 完全开源的专业格式化工具 dotnet format 配合 GitHub 的自动构建 Action 做的自动代码格式化机器人,这个机器人可以被指定到特定时机,如每天晚上或者每次代码合并等,进行代码格式化,格式化完成之后,可以选择直接推送或者提代码审查

12-03 2020

VisualStudio 如何快速添加一个 Git Tag 推送

在 VisualStudio 的团队管理功能,提供了方便的添加 Tag 的方法,可以新建一个 Tag 添加 Tag 信息,同时推送某个特定的 Tag 到服务器。配合推 Tag 打包 NuGet 的方法,将可以让整套工具用起来特别爽,完全本地化打 Tag 推送就完成了 NuGet 服务器打包推送

12-03 2020

dotnet 配置 Gitlab 的 CI 找不到 Runner 或找错的可能原因

使用 Gitlab 的 CI 但是任务没有执行,提示找不到 Runner 或者找错了 Runner 服务器,请看本文,从上到下看,是否有坑

12-03 2020

git 统计两个 commit 之间相差的次数

本文告诉大家在一个连续的 commit 树中统计两个 commit 之间的差异的 commit 数量,也就是存在 A commit 存在而 B commit 不存在的 commit 的数量

12-03 2020

GitHub Action 新上线 WPF .NET Core 自动构建模板

在很土豪的微软免费给大家提供 GitHub 的构建服务器受到了小伙伴们的一堆好评之后,微软最近推出了 WPF 的 .NET Core 版本的模板,可以快速上手 WPF 项目的自动构建,支持自动进行单元测试和打包,同时输出打包的文件

12-03 2020

dotnet 配置 Gitlab 的 Runner 做 CI 自动构建

今天在少珺小伙伴的协助下,使用了 gitlab 的 runner 给全组的项目做自动的构建。为什么需要使用 Gitlab 的 Runner 做自动构建,原因是之前是用的是 Jenkins 而新建一个底层库项目想要接入自动构建等,需要来回在 Gitlab 和 Jenkins 上配置,大概步骤差不多有 20 步,同时还有一堆 Jenkins 的坑。另外服务器是共有的,有其他组的小伙伴安装了诡异的工具让我的打包不断炸掉。于是我就和头像大人商量使用虚拟机环境的方法,我在空闲的服务器上安装了 VirtualBox 虚拟机,然后在虚拟机部署 Runner 接着在项目接入,这样就可以确定打包的环境,同时迁移服务器也比较方便

12-03 2020

在 GitHub 仓库添加 NuGet 版本图标和构建图标

其实这两篇博客我都写过,但是放在一起方便我新建项目的时候复制代码。在 GitHub 的首页上,很多开源项目都会写出当前构建是通过还是不通过,如果是提供 NuGet 包的还添加 NuGet 版本图标

12-03 2020

GitHub 如何过滤某个作者的 MR 内容

在 WPF 开源仓库里面有大量的机器人的 MR 但是我想要了解现在 WPF 仓库有多少开发者在贡献代码,此时如何在 GitHub 中过滤某个作者的 MR 内容

12-03 2020

dotnet 使用 SourceLink 将 NuGet 链接源代码到 GitHub 等仓库

在发布 CBB 作为 NuGet 包的时候,我期望开发者在使用我的库进行调试,可以自动链接代码到对应打包的 GitHub 上的代码,可以从本地拿到对应的源代码进行调试。这样的调试方式对于开源项目来说,将会很方便

07-04 2020

在 Gitlab 开启 MatterMost 机器人

在 Gitlab 上有 MatterMost 插件可以用于订阅 Gitlab 上的事件,本文告诉大家如何使用插件只需要三步就可以关联 Gitlab 和 MatterMost 使用机器人订阅事件

03-05 2020

自动更新所有 Git 仓库

我在本地添加了很多开源项目,我写了一个脚本可以每天自动从开源项目更新代码

03-05 2020

如何删除错误提交的 git 大文件

早上小伙伴告诉我,他无法拉下代码,我没有在意。在我开始写代码的时候,发现我的 C 盘炸了。因为我的磁盘是苏菲只有 256G 放了代码就没空间了,于是我查找到了原来是我的代码占用了居然有 2000+M ,寻找了很久才发现,原来我小伙伴JAKE传了一个压缩包上去,一个1G的包。 那么如何把这个压缩包彻底从 git 删除?

03-05 2020

git 合并两个仓库

好了还是回到问题,我想把两个git合并

03-05 2020

git无法pull仓库refusing to merge unrelated histories

本文讲的是把git在最新2.9.2,合并pull两个不同的项目,出现的问题 如何去解决 fatal: refusing to merge unrelated histories 合并两个不同历史的仓库

03-05 2020

git 修改commit日期为之前的日期

我在之前修改了一个文件,但是没有commit,现在我想要commit,日期为那天的日期 git 修改日期的方法很简单,因为有一个命令--date 可以设置 git 提交时间。 默认的 git 的提交时间会受到系统的时间的影响,如果想要系统的时间不会影响到 git 的提交时间,请使用本文的方式,自己指定提交的时间

03-05 2020

git镜像仓库

有时候我们会把一些仓库放到本地,当他更新的时候,可以使用简单命名更新他。 不是所有时间我们都有网,所以把远程的仓库作为镜像,可以方便我们查看 普通的git clone不能下载所有分支,想要简单的git clone所有分支,可以用镜像方法

03-05 2020

git 通过 SublimeMerge 处理冲突

在使用 Git 的时候,如果是多个小伙伴开发,那么如果同时修改一个文件将出现冲突。也就是在自动合并的时候不知道使用哪个代码才对,此时就需要合并工具的协助。我找了很久发现 SublimeMerge 是界面最好看的,同时快捷键和 SublimeText 一样多也好用的工具

03-05 2020

git 上传当前分支

因为我现在的分支是的名很长,每次需要上次当前分支需要写很多代码,是不是有很简单方法上传当前分支。

03-05 2020

git 分支改名

给一个git分支改名的方法很简单

03-05 2020

git 提交添加 emoij 文字

可能看到 git 提交是文本,就认为他无法使用表情图片,实际上 git 提交是可以添加表情。 本文告诉大家如何做出下面图片提交

03-05 2020

git 使用 VisualStudio 比较分支更改

有时候需要比较两个分支的不同,这时如果提交到 github ,那么默认就可以看到。但是这时因为没有ide的高亮或者其他的功能,看起来觉得不好。 默认的 VisualStudio 比较文件比 github 的用起来好很多,那么如何使用 VisualStudio 作为代码比较?

03-05 2020

git push 错误 hook declined

我把仓库上传到 gogs 出现错误,提示如下 remote: hooks/update: line 2: E:/gogs/gogs.exe: No such file or directory gogs 仓库无法上传,一个原因是移动了gogs,如果把gogs放在移动U盘,插入时,上传经常出现这个问题。

03-05 2020

git rebase 合并多个提交

rebase可以修改记录,我总是做小更改就提交,仓库有好多看起来很乱的 git没有可以把最后一个提交提交到服务器的能力,可以用rebase来做到把多个提交合并为一个。使用这个命令很简单,下面就来告诉大家如何使用这个命令

03-05 2020

git cannot lock ref

如果在 git 准备下载仓库的时候,出现下面的错误 cannot lock ref ‘refs/remotes/origin/xx’:’refs/remotes/origin/xx/xx’ exists cannot create ‘ref/remotes/origin/xx’ 那么请看本文,本文提供了一个解决方法。

03-05 2020

Github 给仓库上传 NuGet 库

在 Github 可以发布自己的 NuGet 库,本文将告诉大家如何发布

03-05 2020

VisualStudio 2019 新创建项目添加 git 仓库

在 VisualStudio 2017 在新建项目的时候给出创建 git 仓库的选项,但是在 VisualStudio 2019 去掉了新建项目的页面,默认新建的项目都是没有带仓库。本文告诉大家如何在 vs2019 里面添加版本管理仓库

03-05 2020

ASP.NET Core 连接 GitLab 与 MatterMost 打造 devops 工具

在现代化开发工具链里面就包含了自动化的通讯工具,而日志写代码我是推到 Gitlab 平台上,我今天听了郭锐大佬的分享之后,感觉我现在的团队的自动化做的远远不够。我在他的课程上学到的最重要一句话就是做工具不是从零到一最难,有很多非常厉害好用的工具最后都没用上的原因是没有加入到开发链条上。所以我用最简单的工具做实践,在 Gitlab 上的代码审查每次都需要自己手动将代码审查链接发给对应的审查者,这样的效率很低,于是我就打通了通讯工具和代码平台之间的联系,开始一步步打造适合自己团队的工具

03-05 2020

3分钟教你搭建 gitea 在 Centos 服务器

本文告诉大家如何在一个 Centos 服务器上搭建 gitea 然后在 gitea 创建帐号上传代码

2019
11-05 2019

清理 git 仓库太繁琐?试试 bfg!删除敏感信息删除大文件一句命令搞定(比官方文档还详细的使用说明)

你可能接触过 git-filter-branch 来清理 git 仓库,不过同时也能体会到这个命令使用的繁琐,以及其超长的执行时间。

现在,你可以考虑使用 bfg 来解决问题了!


安装 bfg

传统方式安装(不推荐)

  1. 下载安装 Java 运行时
  2. 下载安装 bfg.jar

这里并不推荐使用传统方式安装,因为传统方式安装后,bfg 不会成为你计算机的命令。在实际使用工具的时候,你必须为你的每一句命令加上 java -jar bfg.jar 前缀来使用 Java 运行时间接运行。

使用包管理器 scoop 安装

如果你使用包管理器 scoop,那么安装将会非常简单,只需要以下几个命令。

  • scoop install bfg
  • scoop bucket add java
  • scoop install java/openjdk

安装 bfg:

PS C:\Users\lvyi> scoop install bfg
Installing 'bfg' (1.13.0) [64bit]
bfg-1.13.0.jar (12.8 MB) [============================================================================================================================] 100%
Checking hash of bfg-1.13.0.jar ... ok.
Linking ~\scoop\apps\bfg\current => ~\scoop\apps\bfg\1.13.0
Creating shim for 'bfg'.
'bfg' (1.13.0) was installed successfully!
'bfg' suggests installing 'java/oraclejdk' or 'java/openjdk'.

安装 Java 源:

PS C:\Users\lvyi> scoop bucket add java
Checking repo... ok
The java bucket was added successfully.

安装 Jdk:

PS C:\Users\lvyi> scoop install java/openjdk
Installing 'openjdk' (13.0.1-9) [64bit]
openjdk-13.0.1_windows-x64_bin.zip (186.9 MB) [=======================================================================================================] 100%
Checking hash of openjdk-13.0.1_windows-x64_bin.zip ... ok.
Extracting openjdk-13.0.1_windows-x64_bin.zip ... done.
Linking ~\scoop\apps\openjdk\current => ~\scoop\apps\openjdk\13.0.1-9
'openjdk' (13.0.1-9) was installed successfully!

准备工作

当你准备好清理你的仓库的时候,需要进行一些准备。

  1. 克隆一个镜像仓库(git clone 命令加上 --mirror 参数)
    • 这样,当你 git push 的时候,会更新远端仓库的所有引用
  2. cd 到你要清理的仓库路径的根目录
    • 如果你没有前往根目录,那么本文后面的所有命令的最后面你都应该加上路径
  3. 可能需要解除保护
    • 如果本文后面的命令你遇到了受保护的提交,那么需要在所有命令的后面加上 --no-blob-protection 参数

常见用法

使用 bfg 来清理仓库比 git 原生的 git-filter-branch 快得多。官方说法是,10-720 倍:

turning an overnight job into one that takes less than ten minutes.

将一整夜的工作缩减到不到十分钟。

删除误上传的大文件

使用下面的命令,可以将仓库历史中大于 500M 的文件都删除掉。

> bfg --strip-blobs-bigger-than 500M

删除特定的一个或多个文件

删除 walterlv.snk 文件:

> bfg --delete-files walterlv.snk

删除 walterlv.snk 或 lindexi.snk 文件:

> bfg --delete-files {walterlv,lindexi}.snk

比如原来仓库结构是这样的:

- README.md
- Security.md
- walterlv.snk
+ test
    - lindexi.snk

那么删除完后,根目录的 walterlv.snk 和 test 子目录下的 lindexi.snk 就都删除了。

删除文件夹

删除名字为 walterlv 的文件夹:

> bfg --delete-folders walterlv

此命令可以与上面的 --delete-files 放在一起执行:

> bfg --delete-folders walterlv --delete-files walterlv.snk

删除敏感的密码信息

> bfg --replace-text expression-file.txt

注意,这里的 expression-file.txt 名称是随便取的,你可以取其他任何名称,只要在命令里输入正确的名称(可能需要包含路径)就行。

但是 expression-file.txt 里面的内容却是我们需要关注的重点。

此文件中的每一行是一个匹配表达式。默认情况下,每一个表达式被视为一段文本常量,但你可以通过指定 regex: 前缀来说明此表达式是一个正则表达式,或者指定 glob: 前缀。每一个表达式的后面可以加上 ‘==>’ 来指定匹配的文件应该被替换成什么(如果没有指定,就会被替换成默认值 ***REMOVED***

下面这个例子示例将 git 仓库中所有文件中的 密码:123456 字符串替换成 ***REMOVED***

密码:123456

更复杂一点的,下面的例子示例将 git 仓库中所有文件中的 密码:123456 字符串替换成 密码:******

密码:123456 ==> 密码:******

还可以使用正则表达式:

regex:密码:\d+ ==> 密码:******

推回远端仓库

当你在本地操作完镜像仓库之后,可以将其推回原来的远端仓库了。

> git push

最后,有一个不必要的操作。就是回收已经没有引用的旧提交,这可以减小本地仓库的大小:

> git reflog expire --expire=now --all && git gc --prune=now --aggressive

附命令行用法输出

直接在命令行输入 bfg 可以看 bfg 命令行的用法。我贴在下面可以让还没安装的小伙伴感受一下它的功能:

PS C:\Users\lvyi\Desktop\BfgDemoRepo> bfg
bfg 1.13.0
Usage: bfg [options] [<repo>]

  -b, --strip-blobs-bigger-than <size>
                           strip blobs bigger than X (eg '128K', '1M', etc)
  -B, --strip-biggest-blobs NUM
                           strip the top NUM biggest blobs
  -bi, --strip-blobs-with-ids <blob-ids-file>
                           strip blobs with the specified Git object ids
  -D, --delete-files <glob>
                           delete files with the specified names (eg '*.class', '*.{txt,log}' - matches on file name, not path within repo)
  --delete-folders <glob>  delete folders with the specified names (eg '.svn', '*-tmp' - matches on folder name, not path within repo)
  --convert-to-git-lfs <value>
                           extract files with the specified names (eg '*.zip' or '*.mp4') into Git LFS
  -rt, --replace-text <expressions-file>
                           filter content of files, replacing matched text. Match expressions should be listed in the file, one expression per line - by default, each expression is treated as a literal, but 'regex:' & 'glob:' prefixes are supported, with '==>' to specify a replacement string other than the default of '***REMOVED***'.
  -fi, --filter-content-including <glob>
                           do file-content filtering on files that match the specified expression (eg '*.{txt,properties}')
  -fe, --filter-content-excluding <glob>
                           don't do file-content filtering on files that match the specified expression (eg '*.{xml,pdf}')
  -fs, --filter-content-size-threshold <size>
                           only do file-content filtering on files smaller than <size> (default is 1048576 bytes)
  -p, --protect-blobs-from <refs>
                           protect blobs that appear in the most recent versions of the specified refs (default is 'HEAD')
  --no-blob-protection     allow the BFG to modify even your *latest* commit. Not recommended: you should have already ensured your latest commit is clean.
  --private                treat this repo-rewrite as removing private data (for example: omit old commit ids from commit messages)
  --massive-non-file-objects-sized-up-to <size>
                           increase memory usage to handle over-size Commits, Tags, and Trees that are up to X in size (eg '10M')
  <repo>                   file path for Git repository to clean

我觉得你可能需要中文版,于是自己翻译了一下:

PS C:\Users\lvyi\Desktop\BfgDemoRepo> bfg
bfg 1.13.0
用法: bfg [options] [<repo>]

  -b, --strip-blobs-bigger-than <size>
                           移除大于 <size> 大小的文件(<size> 可填写诸如 '128K''1M'
  -B, --strip-biggest-blobs NUM
                           从大到小移除 NUM 数量的文件
  -bi, --strip-blobs-with-ids <blob-ids-file>
                           移除具有指定 git 对象 id 的文件
  -D, --delete-files <glob>
                           移除具有指定名称的文件(例如 '*.class''*.{txt,log}',仅匹配文件名而不能匹配路径)
  --delete-folders <glob>  移除具有指定名称的文件夹(例如 '.svn''*-tmp',仅匹配文件夹名而不能匹配路径)
  --convert-to-git-lfs <value>
                           将指定名称的文件(例如 '*.zip'  '*.mp4')解压到 Git LFS
  -rt, --replace-text <expressions-file>
                           查找文件内容,并替换其中匹配的文本。<expressions-file> 是一个包含一个或多个匹配表达式的文件,文件中每一行是一个匹配表达式。
                           默认情况下,每一个表达式被视为一段文本常量,但你可以通过指定 'regex:' 前缀来说明此表达式是一个正则表达式,或者指定 'glob:' 前缀。
                           每一个表达式的后面可以加上 '==>' 来指定匹配的文件应该被替换成什么(如果没有指定,就会被替换成默认值 '***REMOVED***'
  -fi, --filter-content-including <glob>
                           指定文件名(例如 '*.{txt,properties}'),在进行内容替换的时候只对这些文件进行处理。
  -fe, --filter-content-excluding <glob>
                           指定文件名(例如 '*.{xml,pdf}'),在进行内容替换的时候不对这些文件进行处理。
  -fs, --filter-content-size-threshold <size>
                           仅对小于 <size> 指定的大小的文件替换内容。(默认值为 1048576 字节)
  -p, --protect-blobs-from <refs>
                           protect blobs that appear in the most recent versions of the specified refs (default is 'HEAD')
  --no-blob-protection     allow the BFG to modify even your *latest* commit. Not recommended: you should have already ensured your latest commit is clean.
  --private                仅将本次操作视为个人数据的修改(这样生成的新提交会使用旧提交的 Id,其他人拉取仓库的时候因为这些 Id 已经存在于是不会更新,以至于此更改实际上只影响自己)。
  --massive-non-file-objects-sized-up-to <size>
                           increase memory usage to handle over-size Commits, Tags, and Trees that are up to X in size (eg '10M')
  <repo>                   file path for Git repository to clean

参考资料

09-05 2019

git fetch 失败,因为 unable to resolve reference 'refs/remotes/origin/xxx': reference broken

我在使用 git fetch 命令的时候,发现竟然会失败,提示错误 error: cannot lock ref 'refs/remotes/origin/xxx': unable to resolve reference 'refs/remotes/origin/xxx': reference broken

本文介绍如何修复这样的错误,并探索此错误产生的原因。


错误

在使用 git fetch 命令之后,发现竟然出现了错误,错误输出如下:

$ git fetch --all --prune
Fetching origin
error: cannot lock ref 'refs/remotes/origin/next/release': unable to resolve reference 'refs/remotes/origin/next/release': reference broken
From git***.***.com:walterlv/demo-project
 ! [new branch]            next/release        -> origin/next/release  (unable to update local ref)
error: cannot lock ref 'refs/remotes/origin/feature/ai': unable to resolve reference 'refs/remotes/origin/feature/ai': reference broken
 ! [new branch]            feature/ai          -> origin/feature/ai  (unable to update local ref)
error: cannot lock ref 'refs/remotes/origin/release': unable to resolve reference 'refs/remotes/origin/release': reference broken
 ! [new branch]            release             -> origin/release  (unable to update local ref)
error: Could not fetch origin

修复

前往仓库路径,然后删除这些分支对应的文件。

  1. 前往仓库所在的本地文件夹;
  2. 进入子目录 .git\refs\remotes
  3. 一个个对着上面失败的分支,将其删除。

删除错误的分支

比如在我的错误例子中,要删除的文件分别是:

  • .git\refs\remotes\origin\next\release
  • .git\refs\remotes\origin\feature\ai
  • .git\refs\remotes\origin\release

随后,重新尝试 git fetch,git 会重新生成这些分支文件,因此不用担心会删出问题:

$ git fetch --all --prune
Fetching origin
From git***.***.com:walterlv/demo-project
   a1fd2551f7..cfb662e870  next/release  -> origin/next/release
 * [new branch]            feature/ai    -> origin/feature/ai
   97d72dfc8f..ceb346c8e2  release       -> origin/release

06-23 2019

从 git 的历史记录中彻底删除文件或文件夹

如果你对外开源的代码中出现了敏感信息(例如你将私钥上传到了仓库中),你可能需要考虑将这个文件从 git 的历史记录中完全删除掉。

本文介绍如何从 git 的历史记录中彻底删除文件或文件夹。


第一步:修改本地历史记录

彻底删除文件:

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch walterlv.xml' --prune-empty --tag-name-filter cat -- --all

其中 walterlv.xml 是本来不应该上传的私钥文件,于是使用此命令彻底删除。后面的命令 --tag-name-filter 指所有相关的标签都需要更新。

彻底删除文件夹:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch WalterlvDemoFolder' --prune-empty --tag-name-filter cat -- --all

删除文件夹时需要额外带一个 -r 选项,并指定文件夹名称,这里的例子是 WalterlvDemoFolder

第二步:强制推送到远端仓库

刚刚我们的操作仅仅发生在本地仓库,敏感信息需要删除的仓库通常都在远端,于是我们一定要将修改推送到远端仓库。

需要推送的目标分支包括我们所有长期维护的分支,这通常就包括了 master 分支和所有的标签。

于是使用推送命令:

git.exe push origin master:master --tags --force

06-04 2019

git 配置错误导致无法推送远端仓库?本文介绍各种修复方式

无论你使用原生的 git 命令行,还是使用其他的 GUI 客户端来管理你的 git 仓库,都会遇到 git 远程仓库的身份认证机制。如果在某个远程仓库第一次认证的时候输入了错误的信息,那么 git 以及一部分 git GUI 客户端会记住这个错误的身份认证信息,使得以后也不能继续与远程仓库进行交互了。

本文介绍如何清除 git 的身份认证信息,以便你可以重新获得输入正确身份认证的机会。


凭据管理器

如果你使用基于 https 的身份认证方式操作 git 远端,并且输入了错误的密码,那么这部分密码将保存在 Windows 的凭据管理器中。

在 Windows 搜索框中搜索“凭据管理器”或者在控制面板中进入“用户账户”->“凭据管理器”可以打开凭据管理界面。我们需要选择右边的“Windows 凭据”标签。

随后,在下方的“普通凭据”中,找到出现问题的 git 远程仓库地址,然后展开,将其删除。

凭据管理器

删除之后,再次在 git 命令行或者基于 git 命令行的客户端的 GUI 客户端中使用 git 操作远端仓库将会重新提示输入这个远端仓库的用户名和密码。

.ssh

基于 SSH 的身份认证方式需要自己手工方式都是需要自己手动配置好才可以正常使用的,不会给你像 https 那样输错密码的机会。如果配置错误则不能操作远端仓库。当然,配错了直接删掉重新再来一次就好了。参见网上一大堆的配置方法:git-ssh 配置和使用 - fedl - SegmentFault 思否

配置好的 SSH

另外,有一些客户端如 Tortoise 会自带一份认证管理工具。TortoiseGit 自带了 TortoiseGitPlink,它声称比自带的 SSH 要好用但问题是你得单独为它配置一遍……(逃

命名 SSH 配好了而没有配 TortoiseGitPlink 的时候,它分分钟挂给你看:

TortoiseGitPlink

那么如何修复呢?

方法一:替换 SSH 客户端

替换为与 git 命令行相同的 SSH 客户端可以避免重复配置公私钥对。

打开 TortoiseGit 的设置页面,切换到“网络”标签,然后将 SSH 客户端改为 SSH。通常在 C:\Program Files\Git\usr\bin 目录中,如果没找到,也可以去 C:\Program Files (x86)\Git\bin\ssh.exe 目录寻找。

SSH 客户端

方法二:导入已有的 SSH 配置

打开 C:\Program Files\TortoiseGit\bin\puttygen.exe 程序,然后点击“Load”,选择 git 客户端早已配好的 ssh 私钥。如果打开文件对话框中你找不到密钥文件,可能需要将过滤器设置为所有文件(*.*)。(如果之前没配好 SSH,那么建议去配置一下,不然 SSH 的认证方式将只有 TortoiseGit 客户端工具可用。本节接下来的内容将默认你已经配好 SSH,在远端仓库添加了公钥。)

puttygen

导入成功

导入成功之后,点击保存私钥,选择一个合适的路径存下来。

随后,打开 C:\Program Files\TortoiseGit\bin\puttygen.exe 程序。打开之后,你会在任务栏通知区域看到它的图标,右键点击 Add Key 然后选择我们刚刚保存的私钥。

Add Key

随后,你需要保持 puttygen.exe 一直处于运行状态,以便 TortoiseGit 可以一直使用。


参考资料

05-23 2019

在整个 Git 仓库的历史(包括所有分支和标签)中修改提交作者的信息(姓名和邮箱)

一般情况下不建议修改 git 仓库的历史。

但是现在我计划开源我的一个项目,于是自己个人使用的姓名和邮箱就需要在开源的时候改为使用我公开的姓名和邮箱。对于旧仓库,我将废弃,将来所有的精力都将在开源版本的仓库中;而对于开源版本的新仓库,由于此前没有人克隆过,所以也不会因为历史的修改产生问题。所以,我可以很放心地更改全部的 git 仓库历史。


我打算将整个 Git 仓库历史中的名称和邮箱。

第一步:打开 Git Bash

进入本地的 Git 仓库目录,然后打开 Git Bash。

第二步:输入 Git 命令

接下来,我们需要输入一段多行命令。请先复制以下命令到你的临时编辑器中,然后修改这段多行命令中的几个变量的值。

多行命令:

git filter-branch --env-filter '

OLD_EMAIL="your-old-email@example.com"
CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com"

if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

请注意上面那几个变量:

  • OLD_EMAIL 修改为你的旧邮箱(也就是需要替换掉的 Git 历史中的邮箱)
  • CORRECT_NAME 修改为你的新名称
  • CORRECT_EMAIL 修改为你的新邮箱

对我来说,新名称也就是我在 GitHub 上的名称 walterlv,新邮箱也就是我在 GitHub 上公开使用的提交邮箱。

将以上修改后的命令粘贴到 Git Bash 中,然后按下回车键执行命令:

执行命令

等待命令执行结束,你就能看到你的仓库中所有的分支(Branches)、所有的标签(Tags)中的旧作者信息全部被替换为了新作者信息了。

执行结果

第三步:推送仓库

如果你只是准备开源这个仓库,还没开始推送,那么直接推送即可。使用以下命令推送所有的分支和所有的标签。

git push --force --tags origin 'refs/heads/*'

如果你已经将仓库推送出去了,那么就需要强制推送来覆盖远端的仓库。使用以下命令推送所有的分支和所有的标签。

git push --tags origin 'refs/heads/*'

参考资料

04-21 2019

C#/.NET 使用 git 命令行来操作 git 仓库

我们可以在命令行中操作 git,但是作为一名程序员,如果在大量重复的时候还手动敲命令行,那就太笨了。

本文介绍使用 C# 编写一个 .NET 程序来自动化地使用 git 命令行来操作 git 仓库。

这是一篇很基础的入门文章。


最简单的运行 git 命令的代码

在 .NET 中,运行一个命令只需要使用 Process.Start 开启一个子进程就好了。于是要运行一个 git 命令,我们其实只需要这句足以:

Process.Start("git", "status");

当然,直接能简写成 git 是因为 git.exe 在我的环境变量里面,一般开发者在安装 Git 客户端的时候,都会自动将此命令加入到环境变量。如果没有,你需要使用完整路径 C:\Program Files\Git\mingw64\bin\git.exe 只是每个人的路径可能不同,所以这是不靠谱的。

允许获得命令的输出

对于上节中写的 Process.Start,你一眼就能看出来这是完全没有用的代码。因为 git status 命令只是获得仓库当前的状态,这个命令完全不影响仓库,只是为了看状态的。

所以,命令最好要能够获得输出。

而要获得输出,你需要使用 ProcessStartInfo 来指定如何启动一个进程。

var info = new ProcessStartInfo(ExecutablePath, arguments)
{
    CreateNoWindow = true,
    RedirectStandardOutput = true,
    UseShellExecute = false,
    WorkingDirectory = WorkingDirectory,
};

需要设置至少这四个属性:

  • CreateNoWindow 表示不要为这个命令单独创建一个控制台窗口
    • 实际上如果使用此代码的程序也是一个控制台程序,这句是没有必要的,因为子进程会共用父进程的控制台窗口;但是对于 GUI 程序来说,这句还是很重要的,这可以避免在执行命令的过程中意外弹出一个黑色的控制台窗口出来。
  • RedirectStandardOutput 进行输出的重定向
    • 这是一定要设置为 true 的属性,因为我们希望拿到命令的输出结果。
  • WorkingDirectory 设置工作路径
    • 本来这是一个可选设置,不过对于 git 命令来说,一般都是对一个已有的 git 仓库进行操作,所以当然要指定一个合理的 git 仓库了。
  • UseShellExecute 设置为 false 表示不要使用 ShellExecute 函数创建进程
    • 此属性的详细说明,请阅读我的另一篇博客:ProcessStartInfo 中的 UseShellExecute - 吕毅
    • 这里我们必须指定为 false,因为要重定向输出的话,这是唯一有效值。顺便一提,此属性如果不设置,默认值是 true

CommandRunner

为了方便起见,我将全部运行一个命令的代码封装到了一个 CommandRunner 的类当中。

using System;
using System.Diagnostics;
using System.IO;

namespace Walterlv.GitDemo
{
    public class CommandRunner
    {
        public string ExecutablePath { get; }
        public string WorkingDirectory { get; }

        public CommandRunner(string executablePath, string? workingDirectory = null)
        {
            ExecutablePath = executablePath ?? throw new ArgumentNullException(nameof(executablePath));
            WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(executablePath);
        }

        public string Run(string arguments)
        {
            var info = new ProcessStartInfo(ExecutablePath, arguments)
            {
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                UseShellExecute = false,
                WorkingDirectory = WorkingDirectory,
            };
            var process = new Process
            {
                StartInfo = info,
            };
            process.Start();
            return process.StandardOutput.ReadToEnd();
        }
    }
}

测试与结果

以上 CommandRunner 命令的使用非常简单,new 出来之后,得到一个可以用来执行命令的实例,然后每次执行调用 Run 方法传入参数即可。

var git = new CommandRunner("git", @"D:\Developments\Blogs\walterlv.github.io");
git.Run("add .");
git.Run(@"commit -m ""这是自动提交的""");

如果需要获得命令的执行结果,直接使用 Run 方法的返回值即可。

比如下面我贴了 Main 函数的完整代码,可以输出我仓库的当前状态:

using System;

namespace Walterlv.GitDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("walterlv 的自动 git 命令");

            var git = new CommandRunner("git", @"D:\Developments\Blogs\walterlv.github.io");
            var status = git.Run("status");

            Console.WriteLine(status);
            Console.WriteLine("按 Enter 退出程序……");
            Console.ReadLine();
        }
    }
}

运行结果

03-10 2019

git subtree 的使用

本文收集 git subtree 的使用。


将 B 仓库添加为 A 仓库的一个子目录

在 A 仓库的根目录输入命令:

$ git subtree add --prefix=SubFolder/B https://github.com/walterlv/walterlv.git master

这样,B 仓库的整体,会被作为 A 仓库中一个 SubFolder/B 的子文件夹,同时保留 B 仓库中的整个日志记录。

将 A 仓库中的 B 子目录推送回 B 仓库

$ git subtree push --prefix=SubFolder/B https://github.com/walterlv/walterlv.git master

当然,如果你经常需要使用 subtree 命令,还是建议将那个远端设置一个别名,例如设置 walterlv

$ git remote add walterlv https://github.com/walterlv/walterlv.git

那么,上面的命令可以简单一点:

$ git subtree push --prefix=SubFolder/B walterlv master

后面,我们命令都会使用新的远端名称。

将 B 仓库中的新内容拉回 A 仓库的子目录

$ git subtree pull --prefix=SubFolder/B walterlv master

03-09 2019

使用一句 git 命令将仓库的改动推送到所有的远端

git 支持一个本地仓库包含多个远端(remote),这对于开源社区来说是一个很重要的功能,可以实时获取到最新的开源代码且能推送到自己的仓库中提交 pull request。

有时候多个远端都是自己的,典型的就是 GitHub Pages 服务了,推送总是希望这几个远端能够始终和本地仓库保持一致。本文将介绍一个命令推送到所有远端的方法。


我的博客同时发布在 GitHub 仓库 https://github.com/walterlv/walterlv.github.io 和 Gitee 仓库 http://gitee.com/walterlv/walterlv。由于这两个远端的 Pages 服务没有打通,所以我总是需要同时将博客推送到两个不同的远端中。

第一步:设置多个远端(remote)

使用你平常使用的方法添加多个 git 远端。

例如:

git remote add github https://github.com/walterlv/walterlv.github.io.git --no-tags

需要注意,对于不是 origin 的远端,建议不要拉取 tags,所以我加了 --no-tags 选项。

我添加了两个新的远端(github 和 gitee)之后,打开你仓库 .git 文件夹中的 config 文件,应该可以看到如下的内容:

[remote "origin"]
	url = https://github.com/walterlv/walterlv.github.io.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master
[remote "github"]
	url = https://github.com/walterlv/walterlv.github.io.git
	fetch = +refs/heads/*:refs/remotes/github/*
	tagopt = --no-tags
[remote "gitee"]
	url = https://gitee.com/walterlv/walterlv.git
	fetch = +refs/heads/*:refs/remotes/gitee/*
	tagopt = --no-tags

第二步:添加一个名为 all 的新远端

现在,我们要添加一个名为 all 的新远端,并且在里面添加两个 url。由于这个步骤没有 git 命令行的帮助,所以你需要手工修改 config 文件中的内容。

[remote "all"]
	url = https://github.com/walterlv/walterlv.github.io.git
	url = https://gitee.com/walterlv/walterlv.git
	tagopt = --no-tags

如果你有更多需要同步的远端,那么就在里面添加更多的 url。

开始使用一个命令同步所有的仓库

现在,你可以使用一句命令将本地的修改推送到所有的远端了。

git push all

我现在自己的博客仓库就是这样的推送方式。于是你可以在以下多个地址打开阅读我的博客:

02-20 2019

将 svn 仓库迁移到 git 仓库

我找到了一个很久很久以前编写的项目,然而当时是使用 svn 进行版本管理的。然而现在的版本管理全部是 git,不愿意再装一个 svn 工具来管理这些古老的项目,于是打算将其迁移到 git 中。

本文介绍如何将古老的 svn 项目迁移到 git。


找回 svn 仓库的 url

如果你能记得你 svn 仓库的 url,或者这个仓库是一个纯本地仓库,那么你直接复制这个 url 就好了。

然而如果这是一个有 svn 远程服务器的仓库,那么你可能依然需要临时安装一下 svn 工具。我们只是为了拿回 url 而已。

这里我使用当时使用的小乌龟 TortoiseSVN。在 svn 仓库空白处右击选择版本库浏览器(Repo-browser),小乌龟会自动定位到当前仓库所在的远程 svn 服务器的对应文件夹。

版本库浏览器

我们所要做的只有一件事——复制顶部那个 url。

得到了这个 url 后,像我这种洁癖就卸载 TortoiseSVN 了。

将 svn 仓库迁移到 git 仓库

命令行

在一个新的文件夹中,我们输入如下命令:

git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn"

如果那个 svn 目录中包含 trunkbranchestags 结构,那么可以在后面添加相应的参数以便在 clone 完成后保留分支和标签信息。

git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn" -T trunk -b branches -t tags

需要注意的是,上面的 Walterlv.RepoFromSvn 文件夹是不允许提前存在的,如果存在将无法迁移成功。

TortoiseGit

这里特地照顾一下从 TortoiseSVN 迁移来继续考虑 TortoiseGit 的小伙伴。在 TortoiseGit 中的操作是:

  1. 在某个文件夹中右键(或者 Shift+右键)
  2. 选择克隆
  3. 按照下图填写来自 url 的远程服务器 url 和本地文件夹,并打勾“从SVN版本库”

TortoiseGit 上的迁移 SVN 操作


参考资料

02-15 2019

git 合并策略

不清楚 git 冲突的表示方法,不了解 git 的合并原理,不知道 git 解冲突的多种策略。即便如此,大多数人依然可以正常使用 git 完成合并、拉取操作,并且解一些冲突。这得益于 git 默认情况下的合并方式可以处理大多数情况下的正常合并。

然而,你是否遭遇 git 自动合并炸掉的情况?命名提示没有冲突,代码却早已无法编译通过。

本文将介绍 git 的合并策略,你可能可以更好的使用不同的策略来解决冲突。


git 合并策略

典型的使用指定 git 合并策略的命令这么写:

$ git merge 要合并进来的分支名 --strategy=合并策略

例如:

$ git merge origin/master --strategy=resolve

或者使用简写 -s,例如:

$ git merge origin/master -s resolve

可以指定的合并策略有:

  • resolve
  • recursive
  • octopus
  • ours
  • subtree

resolve

这使用的是三路合并算法。不过我们在 git 的合并原理(递归三路合并算法) 中说过,普通的三路合并算法会存在发现多个共同祖先的问题。此策略会“仔细地”寻找其中一个共同祖先。

由于不需要递归合并出虚拟节点,所以此方法合并时会比较快速,但也可能会带来更多冲突。不敢说带来更多冲突是好事还是坏事,因为自动合并成功并不一定意味着在代码含义上也算是正确的合并。所以如果自动合并总是成功但代码含义上会失败,可以考虑此合并策略,这将让更多的冲突变成手工合并而不是自动合并。

recursive

这是默认的合并策略,如果你不指定策略参数,那么将使用这个合并策略。这将直接使用递归三路合并算法进行合并,详见:git 的合并原理(递归三路合并算法)

当指定为此策略时,可以额外指定下面的这些参数,方法是:

$ git merge 要合并进来的分支名 --strategy=合并策略 -X diff-algorithm=参数

例如:

$ git merge origin/master -s recursive -X diff-algorithm=patience

由于 recursive 是默认的合并策略,所以可以简化成:

$ git merge origin/master -X diff-algorithm=patience

ours

如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用自己这一方的修改。

注意策略里面也有一个 ours,与这个不同的。

theirs

这与 ours 相反。如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用来自其他人的修改(也就是 merge 参数中指定的那个分支的修改)。

patience

此策略的名称叫“耐心”,因为 git 将话费更多的时间来进行合并一些看起来不怎么重要的行,合并的结果也更加准确。当然,使用的算法是 recursive 即递归三路合并算法。

不过此名称也难以准确描述到底如何准确,不过可以举一个例子来说明:

int Foo()
{
    // 一些省略的代码。
}

int Baz()
{
    // 一些省略的代码。
}

然后在这两个函数中增加另一个函数:

int Bar()
{
    // 一些省略的代码。
}

默认情况下 git 会认为修改是这样的:

+ }
+
+ int Bar()
+ {
+     // 一些省略的代码。

然而使用 patience 策略后,git 将认为修改是这样的:

+ int Bar()
+ {
+     // 一些省略的代码。
+ }
+

如果你经常合并出现这些括号丢失或者符号不再匹配的问题,可以考虑使用 patience 策略进行合并。

no-renames

默认情况下 git 会识别出你重命名或者移动了文件,以便在你移动了文件之后依然可以与原文件进行合并。如果指定此策略,那么 git 将不再识别重命名,而是当作增加和删除了文件。

其他的参数

  • diff-algorithm=[patience|minimal|histogram|myers]
  • renormalize
  • no-renormalize
  • find-renames[=<n>]
  • rename-threshold=<n>
  • subtree[=<path>]

octopus

又是一个奇怪的名字——章鱼。章鱼有很多的触手,此合并策略就像这么多的触手一样。

此策略允许合并多个 git 提交节点(分支)。不过,如果会出现需要手工解决的冲突,那么此策略将不会执行。

此策略就是用来把多个分支聚集在一起的。

$ git merge t/lvyi t/walterlv -s octopus
error: Merge requires file-level merging
Trying really trivial in-index merge...
Nope.
Merge with strategy octopus failed.

ours

在合并的时候,无论有多少个合并分支,当前分支就直接是最终的合并结果。无论其他人有多少修改,在此次合并之后,都将不存在(当然历史里面还有)。

你可能觉得这种丢失改动的合并策略没有什么用。但如果你准备重新在你的仓库中进行开发(程序员最喜欢的重构),那么当你的修改与旧分支合并时,采用此合并策略就非常有用,你新的重构代码将完全不会被旧分支的改动所影响。

注意 recursive 策略中也有一个 ours 参数,与这个不同的。

subtree

此策略使用的是修改后的递归三路合并算法。与 recursive 不同的是,此策略会将合并的两个分支的其中一个视为另一个的子树,就像 git subtree 中使用的子树一样。


参考资料

02-14 2019

git 的合并原理(递归三路合并算法)

如果 git 只是一行行比较,然后把不同的行报成冲突,那么你在合并的时候可能会遇到大量的冲突;这显然不是一个好的版本管理工具。

本文介绍 git 合并分支的原理。


git 的冲突表示

例如我们有这样的三个提交 a、b、c。a、b 是在 master 上的其他修改,c 是我自己基于 master 上的 a 的修改。

现在,将 master 分支合并到我自己的 t/walterlv 分支:

git 提交树

a 提交:

Console.WriteLine("Hello World!");

b 提交:

Console.WriteLine("Hello Master!");

c 提交:

Console.WriteLine("Hello Walterlv!");

于是现在将 c 提交合并到 master 的时候就会出现冲突。冲突的表示会是这样:

<<<<<<< HEAD
Console.WriteLine("Hello Walterlv!");
=======
Console.WriteLine("Hello Master!");
>>>>>>> master

<<<<<<< 表示冲突开头,>>>>>>> 表示冲突结尾,======= 分隔冲突的不同修改。上面是 HEAD,也就是在合并之前的工作目录上的最近提交;下面是合并进来的分支,通常是来自其他人的修改。

三路合并

加入上面的 b 提交修改的是其他文件。然后依然按照前面的方式进行合并。

当出现冲突时,如果你只能看到不同的两行,那么你根本不知道究竟应该如何修改的。就像下面这样:

<<<<<<< HEAD
Console.WriteLine("Hello Walterlv!");
=======
Console.WriteLine("Hello World!");
>>>>>>> master

只看这点你怎么知道两行应该采用哪一行?这是二路合并算法带来的问题。在此算法下,你的每次拉取代码可能都会带来大量的冲突;这显然是不能接受的。

三路合并算法会找到合并的这两个提交的共同祖先。在这里也就是 a 提交。master 的此文件对 a 没有修改,而当前分支 t/walterlv 对此文件有修改,于是就会应用此分支的修改。

当然,前一节的问题依然会冲突,因为两个分支相对于共同的祖先节点 a 对同一个文件都有修改。

递归三路合并

从上面我们可以看到三路合并解决了二路合并中对于相同行不知道用哪一个的问题。不过实际的 git 提交树会更加复杂,就像下图那样纵横交错:

纵横交错的 git 提交树

相比于本文一开始,我们只是新增了两个提交而已,现在 f 提交是我们正在合并的提交。

如果现在找 e 和 d 的共同祖先,你会发现并不唯一,b 和 c 都是。那么此时怎么合并呢?

  1. git 会首先将 b 和 c 合并成一个虚拟的提交 x,这个 x 当作 e 和 d 的共同祖先。
  2. 而要合并 b 和 c,也需要进行同样的操作,即找到一个共同的祖先 a。

我们这里的 a、b、c 只是个比较简单的例子,实际上提交树往往更加复杂,这就需要不断重复以上操作以便找到一个真实存在的共同祖先,而这个操作是递归的。这便是“递归三路合并”的含义。

这是 git 合并时默认采用的策略。

快进式合并

git 还有非常简单的快进式(Fast-Forward)合并。快进式合并要求合并的两个分支(或提交)必须是祖孙/父子关系。例如上面的 e 和 d 并不满足此关系,所以无法进行快进式合并。

在上面的例子合并出了 f 之后,如果将 t/walterlv 合并到 master,那么就可以使用快进式合并。这时,直接将 master 分支的 HEAD 指向 f 提交即完成了合并。当然,可以生成也可以不生成新的 g 提交,但内容与 f 的内容完全一样。


参考资料