当你想开源某个内部开发很久的项目或者有一些脚本想要上传到 github
,但是这个项目的 git
版本管理中包含了你以前填入的数据库密码,而且是明文,这时候就非常麻烦,这种情况需要如何处理保证项目的 git
能够保证最小修改,尽可能的不去修改过多 commit
?看如下操作
PS: 只是单纯删掉密码并新提交一个 commit 是无法修改掉 git
版本管理中的历史的
新建临时分支覆盖
这个办法的主旨思路是基于 git
的 cherry-pick
和rebase
和 命令赋能临时分支,将需要涉及泄密的 commit
重新拿出来然后再合并,操作如下
- 第一步,基于修改密码的
commit
创建临时分支temp
,假设下面的a
是初次提交时携带了数据库密码的commit
git
操作
# 查看你的 commit id(SHA)
git log
git checkout commit-id
git branch temp
git checkout temp
PS: 你可以在 VS Code 的 gitlen 插件提供的 graph 来获取 commit id 而不是查看终端或者命令行的 commit id
分支状态
old
a - b - c - d master
new
a - b - c - d master
a temp
- 第二步,删除密码或者改为环境变量(比如
dotenv
),并提交一个新的commit
分支状态
a - b - c - d master
a - e temp
- 第三步,使用
git rebase
合并两个提交,git rebase
合并两个分支的操作请参考这篇文章,使用git rebase合并多次commit - zuopf769 - 掘金
# commit a 是初始提交,HEAD 指针在 commit e 上
# 所以是 --root
# 一般情况是 HEAD~n, n 指需要合并的 commit 数目
# https://stackoverflow.com/questions/26174757/git-needed-a-single-revision-error
git rebase -i --root
进到 vim
交互操作界面,windows/mac
通用,无需下载
也就是 s c31a5ed init
,其中 s
是对需要合并的 commit
执行的命令,s
包括其它命令信息如下
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d)
在 vim
中执行 esc -> :wq
保存退出,进入 commit message
操作部分,保留你需要提交信息
合并后
分支状态
a - b - c - d master
f temp
- 第四步,
cherry-pick
原分支的剩下的commit
# 注意 b 和 d 是 master 分支 commit 的 hash 值,也就是 commit id
# b^..d 表示迁移包含 b 在内到 d 的 commit
# 使用 windows 的 cmd 需要使用 "b^..d" 因为 ^ 需要转义
# 参考:https://stackoverflow.com/questions/1670970/how-to-cherry-pick-multiple-commits
git cherry-pick b^..d
- 第五步,将临时分支转正,替换成
master
分支,完成此步可以修改完成
git checkout temp
git branch -d master
git branch -M master
# 如果还有远程仓库并想同步,需要强制推送并覆盖
# git push -f origin
实际应用
出于教学目的,上面的处理例子是根据实际情况模拟的,实际过程中会
基于 master
分支新建一个 temp1
分支,然后再基于 temp1
分支新建 temp2
分支,在 temp2
分支执行 rebase
操作,目的是为了提高容错,保证后续出错以后可以回到 master
而在实际应用时还遇到了一些问题如下
cherry-pick 多个 commit 中间遇到错误
举个例子,分支状态如下
a - b - c - d master
f temp
操作如下
# windows
git cherry-pick "b^..d"
# 如果在 cherry-pick 到 c 的过程中出错了怎么办?
上面引出了两个问题
git cherry-pick c
出错了如何解决git cherry-pick c
导致后面的 commitd
要如何继续合并?d
后面有更多的commit
怎么办?
首先解决第一个问题,出错问题可以百度或者谷歌,案例很多,举一个本文章实际例子
上面这个就是合并 2adf3c9
这个 commit
过程中遇到了冲突,所以遇到这种情况即证明 cherry-pick
中途遇到了错误,没有完成全部 cherry-pick
,而对于冲突的解决请查阅文档
解决完冲突之后就可以处理第二个问题,继续 cherry-pick
剩下的 commit
,这个时候有两种方法
- 第一种方法,
git cherry-pick --continue
,继续剩下的合并,如果再出错,先根据提示信息处理错误,然后再次进行git cherry-pick --continue
,直到出现下图两种情况之一
- 第二种方法,根据中断的
commit
再进行一次合并,比如上面的例子,在c
部分出错,就可以在解决错误之后执行git cherry-pick "c..d"
("c..d"
指合并不包含 commitc
),命令如下
# 先中止上一次的 cherry-pick
git cherry-pick --abort
git cherry-pick "c..d"
未跟踪文件导致 cherry-pick 失败
这个问题比较特殊,错误信息如下
error: The following untracked working tree files would be overwritten by merge:
.vscode/settings.json
Please move or remove them before you merge.
Aborting
fatal: cherry-pick failed
意思就是 cherry-pick
的某次提交中存在和当前没有被提交到暂存区(即没有被跟踪)的文件的名称一致的文件,解决方法很简单,就是在 cherry-pick
之前本地这个没有被提交到暂存区(即没有被跟踪)的文件,即 .vscode/settings.json
即可
但是注意,你解决完错误以后就不能使用上面的方法 cherry-pick 多个 commit 中间遇到错误
因为这个意味着你的 git cherry-pick "b^..d"
已经失败了,没有 git cherry-pick --continue
的机会了
继续举 commit c
出错的例子,再次强行 git cherry-pick --continue
的结果就会使 c
会丢失,如下图
总结
使用临时仓库覆盖是非常侵入性的操作,看完整篇文章后你可以比较前后 master
分支的 commit id
或者说 HASH
值,已经完全改变,如果这是一个多人操作的库,这样的操作是毁灭性的,必须通知其它人,否则会导致难以挽回的冲突