|
git
每个团队都有适合各自的管理方法,本文仅记录我现在的团队所使用的项目管理方法。本文主要聊的是通过 gitlab 的里程碑以及 git 的分支管理项目的开发和送测的代码合并问题
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.
我 Fork 了小伙伴的 ant-design-blazor 仓库,这个仓库设置了每天自动同步样式,这个 Action 用到了源仓库的密钥,在我 Fork 的仓库一定跑不通过,于是每天我就收到一次构建不通过的信息。本文告诉大家如何禁用自己 Fork 的某个仓库的 Action 的执行
我有一个 GitHub 项目,这个项目配置了仅需要在源仓库才能执行的 Action 如推送 NuGet 等发布动作。如何在 Action 里面设置让 Fork 的仓库不执行 Action 的步骤
被微软收购的 GitHub 越来越好用,拥有大量免费的工具资源和构建服务器资源,再加上私有项目的无限制使用,我有大量的项目都在向 GitHub 迁移。通过 GitHub 的 Action 的自动构建,可以用上微软土豪的服务器资源,进行自动化测试和构建。对于 CBB 来说,发布就是打出 NuGet 包然后上传到内部 NuGet 服务器。此时遇到的问题是,如何在 GitHub 上执行打包,打包的时候如何指定 NuGet 包的版本号。因为 CBB 的特殊性,我要求每个 NuGet 正式发布的包都应该有一个对应的 Tag 号,这样将 NuGet 库安装到项目里面,之后发现问题了还能找到对应版本的代码
我现在的团队内部用的是 Gitlab 工具,在此工具上提供了 Gitlab CI CD 用于做自动化测试和构建。对于 CBB 来说,发布就是打出 NuGet 包然后上传到内部 NuGet 服务器。此时遇到的问题是,如何在 Gitlab 上执行打包,打包的时候如何指定 NuGet 包的版本号。因为 CBB 的特殊性,我要求每个 NuGet 正式发布的包都应该有一个对应的 Tag 号,这样将 NuGet 库安装到项目里面,之后发现问题了还能找到对应版本的代码 本文告诉大家如何配合 Gitlab 做自动推 Tag 时打包 NuGet 包。也就是本地打一个 Tag 号,推送到 Gitlab 上,就会出发 Gitlab 的自动构建,自动构建里面将会获取 Tag 版本号,然后打出 NuGet 包推送到服务器
在 github 上提 MR 在合并之后的分支,很少需要继续保存,如果真的有需求,建议使用 tag 保存,而在合并之后不需要这个分支,默认的 github 不会删除这个分支
我们的项目中会包含有很多文件,但是可能我们没有注意到的,我们的文件的编码不一定是 UTF-8 编码,这就可能让构建出来的应用程序在别人电脑运行时出现乱码,或者别人拉下来代码,却发现代码里面的中文都是乱码。为了解决文件编码的问题,咱需要一个编码规范工具,本文将告诉大家在 GitHub 上仓库,可以利用 GitHub 的 Action 部署自动代码文件编码规范的机器人,这个机器人可以自动协助咱规范文件的编码规范。可以设置为每次上传代码的时候,自动帮忙设置文件编码为 UTF-8 编码。或者在每次代码合并到主分支之后,机器人将会尝试修复文件的编码,如存在文件需要修复的,那机器人将会创建一个代码审查
是不是大家也会觉得代码审查里面审查代码格式化问题是无意义的,但是不审查又觉得过不去?是否有个专门的工具人,用来协助修复代码格式化的问题?本文来安利大家一个特别好用的方法,使用 dotnet 完全开源的专业格式化工具 dotnet format 配合 GitHub 的自动构建 Action 做的自动代码格式化机器人,这个机器人可以被指定到特定时机,如每天晚上或者每次代码合并等,进行代码格式化,格式化完成之后,可以选择直接推送或者提代码审查
在 VisualStudio 的团队管理功能,提供了方便的添加 Tag 的方法,可以新建一个 Tag 添加 Tag 信息,同时推送某个特定的 Tag 到服务器。配合推 Tag 打包 NuGet 的方法,将可以让整套工具用起来特别爽,完全本地化打 Tag 推送就完成了 NuGet 服务器打包推送
使用 Gitlab 的 CI 但是任务没有执行,提示找不到 Runner 或者找错了 Runner 服务器,请看本文,从上到下看,是否有坑
本文告诉大家在一个连续的 commit 树中统计两个 commit 之间的差异的 commit 数量,也就是存在 A commit 存在而 B commit 不存在的 commit 的数量
在很土豪的微软免费给大家提供 GitHub 的构建服务器受到了小伙伴们的一堆好评之后,微软最近推出了 WPF 的 .NET Core 版本的模板,可以快速上手 WPF 项目的自动构建,支持自动进行单元测试和打包,同时输出打包的文件
今天在少珺小伙伴的协助下,使用了 gitlab 的 runner 给全组的项目做自动的构建。为什么需要使用 Gitlab 的 Runner 做自动构建,原因是之前是用的是 Jenkins 而新建一个底层库项目想要接入自动构建等,需要来回在 Gitlab 和 Jenkins 上配置,大概步骤差不多有 20 步,同时还有一堆 Jenkins 的坑。另外服务器是共有的,有其他组的小伙伴安装了诡异的工具让我的打包不断炸掉。于是我就和头像大人商量使用虚拟机环境的方法,我在空闲的服务器上安装了 VirtualBox 虚拟机,然后在虚拟机部署 Runner 接着在项目接入,这样就可以确定打包的环境,同时迁移服务器也比较方便
其实这两篇博客我都写过,但是放在一起方便我新建项目的时候复制代码。在 GitHub 的首页上,很多开源项目都会写出当前构建是通过还是不通过,如果是提供 NuGet 包的还添加 NuGet 版本图标
在 WPF 开源仓库里面有大量的机器人的 MR 但是我想要了解现在 WPF 仓库有多少开发者在贡献代码,此时如何在 GitHub 中过滤某个作者的 MR 内容
在发布 CBB 作为 NuGet 包的时候,我期望开发者在使用我的库进行调试,可以自动链接代码到对应打包的 GitHub 上的代码,可以从本地拿到对应的源代码进行调试。这样的调试方式对于开源项目来说,将会很方便
在 Gitlab 上有 MatterMost 插件可以用于订阅 Gitlab 上的事件,本文告诉大家如何使用插件只需要三步就可以关联 Gitlab 和 MatterMost 使用机器人订阅事件
早上小伙伴告诉我,他无法拉下代码,我没有在意。在我开始写代码的时候,发现我的 C 盘炸了。因为我的磁盘是苏菲只有 256G 放了代码就没空间了,于是我查找到了原来是我的代码占用了居然有 2000+M ,寻找了很久才发现,原来我小伙伴JAKE传了一个压缩包上去,一个1G的包。 那么如何把这个压缩包彻底从 git 删除?
本文讲的是把git在最新2.9.2,合并pull两个不同的项目,出现的问题
如何去解决 fatal: refusing to merge unrelated histories
合并两个不同历史的仓库
我在之前修改了一个文件,但是没有commit,现在我想要commit,日期为那天的日期
git 修改日期的方法很简单,因为有一个命令--date
可以设置 git 提交时间。
默认的 git 的提交时间会受到系统的时间的影响,如果想要系统的时间不会影响到 git 的提交时间,请使用本文的方式,自己指定提交的时间
有时候我们会把一些仓库放到本地,当他更新的时候,可以使用简单命名更新他。 不是所有时间我们都有网,所以把远程的仓库作为镜像,可以方便我们查看 普通的git clone不能下载所有分支,想要简单的git clone所有分支,可以用镜像方法
在使用 Git 的时候,如果是多个小伙伴开发,那么如果同时修改一个文件将出现冲突。也就是在自动合并的时候不知道使用哪个代码才对,此时就需要合并工具的协助。我找了很久发现 SublimeMerge 是界面最好看的,同时快捷键和 SublimeText 一样多也好用的工具
有时候需要比较两个分支的不同,这时如果提交到 github ,那么默认就可以看到。但是这时因为没有ide的高亮或者其他的功能,看起来觉得不好。 默认的 VisualStudio 比较文件比 github 的用起来好很多,那么如何使用 VisualStudio 作为代码比较?
我把仓库上传到 gogs 出现错误,提示如下 remote: hooks/update: line 2: E:/gogs/gogs.exe: No such file or directory
gogs 仓库无法上传,一个原因是移动了gogs,如果把gogs放在移动U盘,插入时,上传经常出现这个问题。
rebase可以修改记录,我总是做小更改就提交,仓库有好多看起来很乱的 git没有可以把最后一个提交提交到服务器的能力,可以用rebase来做到把多个提交合并为一个。使用这个命令很简单,下面就来告诉大家如何使用这个命令
如果在 git 准备下载仓库的时候,出现下面的错误 cannot lock ref ‘refs/remotes/origin/xx’:’refs/remotes/origin/xx/xx’ exists cannot create ‘ref/remotes/origin/xx’ 那么请看本文,本文提供了一个解决方法。
在 VisualStudio 2017 在新建项目的时候给出创建 git 仓库的选项,但是在 VisualStudio 2019 去掉了新建项目的页面,默认新建的项目都是没有带仓库。本文告诉大家如何在 vs2019 里面添加版本管理仓库
在现代化开发工具链里面就包含了自动化的通讯工具,而日志写代码我是推到 Gitlab 平台上,我今天听了郭锐大佬的分享之后,感觉我现在的团队的自动化做的远远不够。我在他的课程上学到的最重要一句话就是做工具不是从零到一最难,有很多非常厉害好用的工具最后都没用上的原因是没有加入到开发链条上。所以我用最简单的工具做实践,在 Gitlab 上的代码审查每次都需要自己手动将代码审查链接发给对应的审查者,这样的效率很低,于是我就打通了通讯工具和代码平台之间的联系,开始一步步打造适合自己团队的工具
你可能接触过 git-filter-branch
来清理 git 仓库,不过同时也能体会到这个命令使用的繁琐,以及其超长的执行时间。
现在,你可以考虑使用 bfg
来解决问题了!
这里并不推荐使用传统方式安装,因为传统方式安装后,bfg
不会成为你计算机的命令。在实际使用工具的时候,你必须为你的每一句命令加上 java -jar bfg.jar
前缀来使用 Java 运行时间接运行。
如果你使用包管理器 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!
当你准备好清理你的仓库的时候,需要进行一些准备。
git clone
命令加上 --mirror
参数)
git push
的时候,会更新远端仓库的所有引用cd
到你要清理的仓库路径的根目录
--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
参考资料
我在使用 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
前往仓库路径,然后删除这些分支对应的文件。
.git\refs\remotes
;比如在我的错误例子中,要删除的文件分别是:
.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
如果你对外开源的代码中出现了敏感信息(例如你将私钥上传到了仓库中),你可能需要考虑将这个文件从 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
无论你使用原生的 git 命令行,还是使用其他的 GUI 客户端来管理你的 git 仓库,都会遇到 git 远程仓库的身份认证机制。如果在某个远程仓库第一次认证的时候输入了错误的信息,那么 git 以及一部分 git GUI 客户端会记住这个错误的身份认证信息,使得以后也不能继续与远程仓库进行交互了。
本文介绍如何清除 git 的身份认证信息,以便你可以重新获得输入正确身份认证的机会。
如果你使用基于 https 的身份认证方式操作 git 远端,并且输入了错误的密码,那么这部分密码将保存在 Windows 的凭据管理器中。
在 Windows 搜索框中搜索“凭据管理器”或者在控制面板中进入“用户账户”->“凭据管理器”可以打开凭据管理界面。我们需要选择右边的“Windows 凭据”标签。
随后,在下方的“普通凭据”中,找到出现问题的 git 远程仓库地址,然后展开,将其删除。
删除之后,再次在 git 命令行或者基于 git 命令行的客户端的 GUI 客户端中使用 git 操作远端仓库将会重新提示输入这个远端仓库的用户名和密码。
基于 SSH 的身份认证方式需要自己手工方式都是需要自己手动配置好才可以正常使用的,不会给你像 https 那样输错密码的机会。如果配置错误则不能操作远端仓库。当然,配错了直接删掉重新再来一次就好了。参见网上一大堆的配置方法:git-ssh 配置和使用 - fedl - SegmentFault 思否。
另外,有一些客户端如 Tortoise 会自带一份认证管理工具。TortoiseGit 自带了 TortoiseGitPlink,它声称比自带的 SSH 要好用但问题是你得单独为它配置一遍……(逃
命名 SSH 配好了而没有配 TortoiseGitPlink 的时候,它分分钟挂给你看:
那么如何修复呢?
替换为与 git 命令行相同的 SSH 客户端可以避免重复配置公私钥对。
打开 TortoiseGit 的设置页面,切换到“网络”标签,然后将 SSH 客户端改为 SSH。通常在 C:\Program Files\Git\usr\bin
目录中,如果没找到,也可以去 C:\Program Files (x86)\Git\bin\ssh.exe
目录寻找。
打开 C:\Program Files\TortoiseGit\bin\puttygen.exe
程序,然后点击“Load”,选择 git 客户端早已配好的 ssh 私钥。如果打开文件对话框中你找不到密钥文件,可能需要将过滤器设置为所有文件(*.*
)。(如果之前没配好 SSH,那么建议去配置一下,不然 SSH 的认证方式将只有 TortoiseGit 客户端工具可用。本节接下来的内容将默认你已经配好 SSH,在远端仓库添加了公钥。)
导入成功之后,点击保存私钥,选择一个合适的路径存下来。
随后,打开 C:\Program Files\TortoiseGit\bin\puttygen.exe
程序。打开之后,你会在任务栏通知区域看到它的图标,右键点击 Add Key
然后选择我们刚刚保存的私钥。
随后,你需要保持 puttygen.exe
一直处于运行状态,以便 TortoiseGit 可以一直使用。
参考资料
一般情况下不建议修改 git 仓库的历史。
但是现在我计划开源我的一个项目,于是自己个人使用的姓名和邮箱就需要在开源的时候改为使用我公开的姓名和邮箱。对于旧仓库,我将废弃,将来所有的精力都将在开源版本的仓库中;而对于开源版本的新仓库,由于此前没有人克隆过,所以也不会因为历史的修改产生问题。所以,我可以很放心地更改全部的 git 仓库历史。
我打算将整个 Git 仓库历史中的名称和邮箱。
进入本地的 Git 仓库目录,然后打开 Git Bash。
接下来,我们需要输入一段多行命令。请先复制以下命令到你的临时编辑器中,然后修改这段多行命令中的几个变量的值。
多行命令:
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/*'
参考资料
我们可以在命令行中操作 git,但是作为一名程序员,如果在大量重复的时候还手动敲命令行,那就太笨了。
本文介绍使用 C# 编写一个 .NET 程序来自动化地使用 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
表示不要为这个命令单独创建一个控制台窗口
RedirectStandardOutput
进行输出的重定向
true
的属性,因为我们希望拿到命令的输出结果。WorkingDirectory
设置工作路径
git
命令来说,一般都是对一个已有的 git 仓库进行操作,所以当然要指定一个合理的 git 仓库了。UseShellExecute
设置为 false
表示不要使用 ShellExecute
函数创建进程
false
,因为要重定向输出的话,这是唯一有效值。顺便一提,此属性如果不设置,默认值是 true
。为了方便起见,我将全部运行一个命令的代码封装到了一个 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();
}
}
}
本文收集 git subtree 的使用。
在 A 仓库的根目录输入命令:
$ git subtree add --prefix=SubFolder/B https://github.com/walterlv/walterlv.git master
这样,B 仓库的整体,会被作为 A 仓库中一个 SubFolder/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
后面,我们命令都会使用新的远端名称。
$ git subtree pull --prefix=SubFolder/B walterlv master
git 支持一个本地仓库包含多个远端(remote),这对于开源社区来说是一个很重要的功能,可以实时获取到最新的开源代码且能推送到自己的仓库中提交 pull request。
有时候多个远端都是自己的,典型的就是 GitHub Pages 服务了,推送总是希望这几个远端能够始终和本地仓库保持一致。本文将介绍一个命令推送到所有远端的方法。
我的博客同时发布在 GitHub 仓库 https://github.com/walterlv/walterlv.github.io 和 Gitee 仓库 http://gitee.com/walterlv/walterlv。由于这两个远端的 Pages 服务没有打通,所以我总是需要同时将博客推送到两个不同的远端中。
使用你平常使用的方法添加多个 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 的新远端,并且在里面添加两个 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
我现在自己的博客仓库就是这样的推送方式。于是你可以在以下多个地址打开阅读我的博客:
我找到了一个很久很久以前编写的项目,然而当时是使用 svn 进行版本管理的。然而现在的版本管理全部是 git,不愿意再装一个 svn 工具来管理这些古老的项目,于是打算将其迁移到 git 中。
本文介绍如何将古老的 svn 项目迁移到 git。
如果你能记得你 svn 仓库的 url,或者这个仓库是一个纯本地仓库,那么你直接复制这个 url 就好了。
然而如果这是一个有 svn 远程服务器的仓库,那么你可能依然需要临时安装一下 svn 工具。我们只是为了拿回 url 而已。
这里我使用当时使用的小乌龟 TortoiseSVN。在 svn 仓库空白处右击选择版本库浏览器(Repo-browser),小乌龟会自动定位到当前仓库所在的远程 svn 服务器的对应文件夹。
我们所要做的只有一件事——复制顶部那个 url。
得到了这个 url 后,像我这种洁癖就卸载 TortoiseSVN 了。
在一个新的文件夹中,我们输入如下命令:
git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn"
如果那个 svn 目录中包含 trunk
、branches
和 tags
结构,那么可以在后面添加相应的参数以便在 clone 完成后保留分支和标签信息。
git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn" -T trunk -b branches -t tags
需要注意的是,上面的 Walterlv.RepoFromSvn
文件夹是不允许提前存在的,如果存在将无法迁移成功。
这里特地照顾一下从 TortoiseSVN 迁移来继续考虑 TortoiseGit 的小伙伴。在 TortoiseGit 中的操作是:
参考资料
不清楚 git 冲突的表示方法,不了解 git 的合并原理,不知道 git 解冲突的多种策略。即便如此,大多数人依然可以正常使用 git 完成合并、拉取操作,并且解一些冲突。这得益于 git 默认情况下的合并方式可以处理大多数情况下的正常合并。
然而,你是否遭遇 git 自动合并炸掉的情况?命名提示没有冲突,代码却早已无法编译通过。
本文将介绍 git 的合并策略,你可能可以更好的使用不同的策略来解决冲突。
典型的使用指定 git 合并策略的命令这么写:
$ git merge 要合并进来的分支名 --strategy=合并策略
例如:
$ git merge origin/master --strategy=resolve
或者使用简写 -s
,例如:
$ git merge origin/master -s resolve
可以指定的合并策略有:
这使用的是三路合并算法。不过我们在 git 的合并原理(递归三路合并算法) 中说过,普通的三路合并算法会存在发现多个共同祖先的问题。此策略会“仔细地”寻找其中一个共同祖先。
由于不需要递归合并出虚拟节点,所以此方法合并时会比较快速,但也可能会带来更多冲突。不敢说带来更多冲突是好事还是坏事,因为自动合并成功并不一定意味着在代码含义上也算是正确的合并。所以如果自动合并总是成功但代码含义上会失败,可以考虑此合并策略,这将让更多的冲突变成手工合并而不是自动合并。
这是默认的合并策略,如果你不指定策略参数,那么将使用这个合并策略。这将直接使用递归三路合并算法进行合并,详见: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 相反。如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用来自其他人的修改(也就是 merge 参数中指定的那个分支的修改)。
此策略的名称叫“耐心”,因为 git 将话费更多的时间来进行合并一些看起来不怎么重要的行,合并的结果也更加准确。当然,使用的算法是 recursive 即递归三路合并算法。
不过此名称也难以准确描述到底如何准确,不过可以举一个例子来说明:
int Foo()
{
// 一些省略的代码。
}
int Baz()
{
// 一些省略的代码。
}
然后在这两个函数中增加另一个函数:
int Bar()
{
// 一些省略的代码。
}
默认情况下 git 会认为修改是这样的:
+ }
+
+ int Bar()
+ {
+ // 一些省略的代码。
然而使用 patience
策略后,git 将认为修改是这样的:
+ int Bar()
+ {
+ // 一些省略的代码。
+ }
+
如果你经常合并出现这些括号丢失或者符号不再匹配的问题,可以考虑使用 patience
策略进行合并。
默认情况下 git 会识别出你重命名或者移动了文件,以便在你移动了文件之后依然可以与原文件进行合并。如果指定此策略,那么 git 将不再识别重命名,而是当作增加和删除了文件。
diff-algorithm=[patience|minimal|histogram|myers]
renormalize
no-renormalize
find-renames[=<n>]
rename-threshold=<n>
subtree[=<path>]
又是一个奇怪的名字——章鱼。章鱼有很多的触手,此合并策略就像这么多的触手一样。
此策略允许合并多个 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.
在合并的时候,无论有多少个合并分支,当前分支就直接是最终的合并结果。无论其他人有多少修改,在此次合并之后,都将不存在(当然历史里面还有)。
你可能觉得这种丢失改动的合并策略没有什么用。但如果你准备重新在你的仓库中进行开发(程序员最喜欢的重构),那么当你的修改与旧分支合并时,采用此合并策略就非常有用,你新的重构代码将完全不会被旧分支的改动所影响。
注意 recursive 策略中也有一个 ours 参数,与这个不同的。
此策略使用的是修改后的递归三路合并算法。与 recursive 不同的是,此策略会将合并的两个分支的其中一个视为另一个的子树,就像 git subtree 中使用的子树一样。
参考资料
-X patience
vs -X diff-algorithm=patience
with git merge-recursive
- Stack Overflow如果 git 只是一行行比较,然后把不同的行报成冲突,那么你在合并的时候可能会遇到大量的冲突;这显然不是一个好的版本管理工具。
本文介绍 git 合并分支的原理。
例如我们有这样的三个提交 a、b、c。a、b 是在 master 上的其他修改,c 是我自己基于 master 上的 a 的修改。
现在,将 master 分支合并到我自己的 t/walterlv 分支:
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 提交树会更加复杂,就像下图那样纵横交错:
相比于本文一开始,我们只是新增了两个提交而已,现在 f 提交是我们正在合并的提交。
如果现在找 e 和 d 的共同祖先,你会发现并不唯一,b 和 c 都是。那么此时怎么合并呢?
我们这里的 a、b、c 只是个比较简单的例子,实际上提交树往往更加复杂,这就需要不断重复以上操作以便找到一个真实存在的共同祖先,而这个操作是递归的。这便是“递归三路合并”的含义。
这是 git 合并时默认采用的策略。
git 还有非常简单的快进式(Fast-Forward)合并。快进式合并要求合并的两个分支(或提交)必须是祖孙/父子关系。例如上面的 e 和 d 并不满足此关系,所以无法进行快进式合并。
在上面的例子合并出了 f 之后,如果将 t/walterlv 合并到 master,那么就可以使用快进式合并。这时,直接将 master 分支的 HEAD 指向 f 提交即完成了合并。当然,可以生成也可以不生成新的 g 提交,但内容与 f 的内容完全一样。
参考资料