git中的每个节点都是一个提交记录(提交记录非常轻量,可以快速地在这些提交记录之间切换!),可以通过命令在不同节点间切换。
执行git commit命令时,条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。
执行git commit之后会生成新的节点,(learngitbranch)中为了方便理解节点默认命名为Cx,在实际应用中对应一串哈希值。
在进一步学习之前,还需知道工作区、暂存区和版本库的具体含义:
git commit -m [message]
[message] 可以是一些备注信息。
git commit [file1] [file2] ... -m [message]
git commit -a
checkout 本意是检出的意思,也就是将某次commit的状态检出到工作区;所以它的过程是先将HEAD
指向某个分支的最近一次commit,然后从commit恢复index,最后从index恢复工作区。
git checkout branchname
git checkout -b branchname
# 相当于以下两条命令
git branch branchname
git checkout branchname
# 放弃工作区中的全部修改
git checkout .
# 放弃工作区某个文件的修改
git checkout --filename
# 强制放弃index和工作区的修改
git checkout -f
如果不指定切换到哪个分支,那就是切换到当前分支,虽然HEAD的指向没有变化,但是后面的两个恢复过程依然会执行,于是就可以理解为放弃index和工作区的变动。但是出于安全考虑 git 会保持 index 的变动不被覆盖。
待补充:
使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作。”
git branch test
git branch -f [branchA] [branchB]
git branch -d test
git branch -v
# 输出分别为 分支名 提交(commit)id 提交时间
git branch --merged
git branch --no-merged
$ mkdir gitdemo
$ cd gitdemo/
$ git init
Initialized empty Git repository...
$ touch README
$ git add README
$ git commit -m '第一次版本提交'
[master (root-commit) 3b58100] 第一次版本提交
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README
$ git branch
* master
没有参数时,git branch 会列出你在本地的分支。
此例的意思就是,我们有一个叫做 master 的分支,并且该分支是当前分支。
当你执行 git init 的时候,默认情况下 Git 就会为你创建 master 分支。
如果我们要手动创建一个分支。执行 git branch (branchname) 即可。
$ git branch testing
$ git branch
* master
testing
现在我们可以看到,有了一个新分支 testing。
$ ls
README
$ echo 'runoob.com' > test.txt
$ git add .
$ git commit -m 'add test.txt'
[master 3e92c19] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
$ ls
README test.txt
$ git checkout testing
Switched to branch 'testing'
$ ls
README
$ git checkout master
Switched to branch 'master'
$ ls
README test.txt
当我们切换到 testing 分支的时候,我们添加的新文件 test.txt 被移除了。切换回 master 分支的时候,它们又重新出现了。
$ git checkout -b newtest
Switched to a new branch 'newtest'
$ git rm test.txt
rm 'test.txt'
$ ls
README
$ touch runoob.php
$ git add .
$ git commit -am 'removed test.txt、add runoob.php'
[newtest c1501a2] removed test.txt、add runoob.php
2 files changed, 1 deletion(-)
create mode 100644 runoob.php
delete mode 100644 test.txt
$ ls
README runoob.php
$ git checkout master
Switched to branch 'master'
$ ls
README test.txt
**官方解释:**衍合是合并命令的另一种选择。合并把两个父分支合并进行一次提交,提交历史不是线性的。衍合在当前分支上重演另一个分支的历史,提交历史是线性的。 本质上,这是线性化的自动的 cherry-pick
**通俗解释:**rebase,变基,可以直接理解为改变基底。feature分支是基于master分支的B拉出来的分支,feature的基底是B。而master在B之后有新的提交,就相当于此时要用master上新的提交来作为feature分支的新基底。实际操作为把B之后feature的提交存下来,然后删掉原来这些提交,再找到master的最新提交位置,把存下来的提交再接上去(新节点新commit id),如此feature分支的基底就相当于变成了M而不是原来的B了。(注意,如果master上在B以后没有新提交,那么就还是用原来的B作为基,rebase操作相当于无效,此时和git merge就基本没区别了,差异只在于git merge会多一条记录Merge操作的提交记录)
此时执行
git checkout feature
git rebase master
git rebase [Abranch] [Bbranch]
将A分支合并到B分支,合并方式为变基合并。(具体可参考()[https://learngitbranching.js.org/]->高级话题->3.纠缠不清的分支)
按照上面的图解构造了提交记录,如下图所示:(ABM是master分支线,ABCD是feature分支线。这里画成了master变色分叉出来,这不影响理解,知道是表示两个分支两条线即可!)
此时,在feature分支上执行git rebase master
变基完成以后,ABCD是原来的feature分支线,ABMC’D’是新的feature分支线,ABM是master分支线(没有变化)
如果想看 HEAD 指向,可以通过 cat .git/HEAD
查看
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用 git log
来查查看提交记录的哈希值。并且哈希值在真实的 Git 世界中也会更长。因此需要一种相对引用方法:
^
向上移动 1 个提交记录git checkout bugFix^
# 等效于git checkout c3 (这里的c3对应learngitbranch相对引用题目里的c3)
~
向上移动多个提交记录,如 ~3
git checkout bugFix~3
**官方解释:**在 Git 里撤销变更的方法很多。和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。我们这个应用主要关注的是后者。
撤销修改分为以下三种情况:
push
推送到远程仓库。commit
提交到版本库。add
提交到暂存区。**学习重点:**要搞清楚git reset与git reserve的区别
已修改,未暂存
如果我们只是在编辑器里修改了文件,但还没有执行git add .
,这时候我们的文件还在工作区
,并没有进入暂存区
,我们可以用:
git checkout .
或者
git reset --hard
来进行撤销操作。
可以看到,在执行完git checkout .
之后,修改已被撤销,git diff
没有任何内容了。
一对反义词
git add .
的反义词是git checkout .
。做完修改之后,如果你想向前走一步,让修改进入暂存区
,就执行git add .
,如果你想向后退一步,撤销刚才的修改,就执行git checkout .
。
已暂存,未提交
你已经执行了git add .
,但还没有执行git commit -m "comment"
。这时候你意识到了错误,想要撤销,你可以执行:
git reset
git checkout .
或者
git reset --hard
git reset
只是把修改退回到了git add .
之前的状态,也就是说文件本身还处于已修改未暂存
状态,你如果想退回未修改
状态,还需要执行git checkout .
。
或许你已经注意到了,以上两个步骤都可以用同一个命令git reset --hard
来完成。是的,就是这个强大的命令,可以一步到位地把你的修改完全恢复到未修改
的状态。
已提交,未推送
你的手太快,你既执行了git add .
,又执行了git commit
,这时候你的代码已经进入了你的本地仓库
,然而你后悔了,怎么办?不要着急,还有办法。
git reset --hard origin/master
还是这个git reset --hard
命令,只不过这次多了一个参数origin/master
,正如我们上面讲过的,origin/master
代表远程仓库
,既然你已经污染了你的本地仓库
,那么就从远程仓库
把代码取回来吧。
已推送
很不幸,你的手实在是太快了,你既git add
了,又git commit
了,并且还git push
了,这时你的代码已经进入远程仓库
。如果你想恢复的话,还好,由于你的本地仓库
和远程仓库
是等价的,你只需要先恢复本地仓库
,再强制push
到远程仓库
就好了:
git reset --hard HEAD^
git push -f
参考2.4.2相关用法。
revert 可以取消指定的某次提交内容。当讨论 revert 时,需要分两种情况,因为 commit 分为两种:⼀种是常规的commit
,也就是使⽤提交的commit
;另⼀种是 merge commit
,在使⽤git commit
合并两个分⽀之后,你将会得到⼀个新的merge commit
。
使用git show
命令可以查看两种commit
的不同。(具体可以在练手小项目中,尝试查看下)。
revert 之后重新上线 (参考一篇比较好理解的文章)
假设狗蛋在⾃⼰分⽀ goudan/a-cool-feature 上开发了⼀个功能,并合并到了 master 上,之后 master 上⼜提交了⼀个修改 h,这时提交历史如下:
a -> b -> c -> f – g -> h (master)
\ /
d -> e (goudan/a-cool-feature)
突然,⼤家发现狗蛋的分⽀存在严重的 bug,需要 revert 掉,于是⼤家把 g 这个 merge commit revert 掉了,记为 G,如下:
a -> b -> c -> f – g -> h -> G (master)
\ /
d -> e (goudan/a-cool-feature)
然后狗蛋回到⾃⼰的分⽀进⾏ bugfix,修好之后想重新合并到 master,直觉上只需要再 merge 到 master 即可(或者使⽤ cherry-pick),像这样:
a -> b -> c -> f – g -> h -> G -> i (master)
\ / /
d -> e -> j -> k ---- (goudan/a-cool-feature)
i 是新的 merge commit。但需要注意的是,这不能得到我们期望的结果。因为 d 和 e 两个提交曾经被丢弃过,如此合并到 master 的代码,并不会重新包含 d 和 e 两个提交的内容,相当于只有 goudan/a-cool-feature 上的新 commit 被合并了进来,⽽ goudan/a-cool-feature 分⽀之前的内容,依然是被 revert 掉了。所以,如果想恢复整个 goudan/a-cool-feature 所做的修改,应该先把 G revert 掉:
a -> b -> c -> f – g -> h -> G -> G’ -> i (master)
\ / /
d -> e -> j -> k ---------- (goudan/a-cool-feature)
其中 G’ 是对 G 的 revert 操作⽣成的 commit,把之前撤销合并时丢弃的代码恢复了回来,然后再 merge 狗蛋的分⽀,把解决 bug 写的新代码合并到 master 分⽀。
对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。
这时分两种情况。一种情况是,你需要另一个分支的所有代码变动,那么就采用合并(git merge
)。另一种情况是,你只需要部分代码变动(某几个提交),这时可以采用 Cherry pick。
cherry-pick命令"复制"一个提交节点并在当前分支做一次完全一样的新提交。
git cherry-pick
命令的作用,就是将指定的提交(commit)应用于其他分支。
$ git cherry-pick <commitHash>
上面命令就会将指定的提交
commitHash
,应用于当前分支。这会在当前分支产生一个新的提交,当然它们的哈希值会不一样。
举例来说,代码仓库有master
和feature
两个分支。
a - b - c - d Master \ e - f - g Feature
现在将提交f
应用到master
分支。
# 切换到 master 分支 $ git checkout master # Cherry pick 操作 $ git cherry-pick f
上面的操作完成以后,代码库就变成了下面的样子。
a - b - c - d - f Master \ e - f - g Feature
从上面可以看到,master
分支的末尾增加了一个提交f
。
git cherry-pick
命令的参数,不一定是提交的哈希值,分支名也是可以的,表示转移该分支的最新提交。
$ git cherry-pick feature
上面代码表示将feature
分支的最近一次提交,转移到当前分支。
Cherry pick 支持一次转移多个提交。
$ git cherry-pick <HashA> <HashB>
上面的命令将 A 和 B 两个提交应用到当前分支。这会在当前分支生成两个对应的新提交。
git cherry-pick
命令的常用配置项如下。
(1)-e
,--edit
打开外部编辑器,编辑提交信息。
(2)-n
,--no-commit
只更新工作区和暂存区,不产生新的提交。
(3)-x
在提交信息的末尾追加一行(cherry picked from commit ...)
,方便以后查到这个提交是如何产生的。
(4)-s
,--signoff
在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。
(5)-m parent-number
,--mainline parent-number
如果原始提交是一个合并节点,来自于两个分支的合并,那么 Cherry pick 默认将失败,因为它不知道应该采用哪个分支的代码变动。
-m
配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number
是一个从1
开始的整数,代表原始提交的父分支编号。
$ git cherry-pick -m 1 <commitHash>
上面命令表示,Cherry pick 采用提交commitHash
来自编号1的父分支的变动。
如果操作过程中发生代码冲突,Cherry pick 会停下来,让用户决定如何继续操作。
(1)--continue
用户解决代码冲突后,第一步将修改的文件重新加入暂存区(git add .
),第二步使用下面的命令,让 Cherry pick 过程继续执行。
$ git cherry-pick --continue
(2)--abort
发生代码冲突后,放弃合并,回到操作前的样子。
(3)--quit
发生代码冲突后,退出 Cherry pick,但是不回到操作前的样子。
在rebase
命令后增加了-i
这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,提交说明有助于你理解这个提交进行了哪些更改。
在实际使用时,所谓的 UI 窗口一般会在文本编辑器 —— 如 Vim —— 中打开一个文件。
当你运行 git rebase -i
时,你会进入一个编辑器会话,其中列出了所有正在被变基的提交,以及可以对其执行的操作的多个选项。默认的选择是选择(Pick
)。
Pick
:会在你的历史记录中保留该提交。Reword
:允许你修改提交信息,可能是修复一个错别字或添加其它注释。Edit
:允许你在重放分支的过程中对提交进行修改。Squash
:可以将多个提交合并为一个。当你完成后,只需保存最终结果,变基操作就会执行。在你选择修改提交的每个阶段(无论是用 reword
、edit
、squash
还是发生冲突时),变基都会停止,并允许你在继续提交之前进行适当的修改。
# 我们初始化一个项目
git init
## 制造一些提交
touch base.txt
git add .
git commit -m "add base"
touch 1.txt
git add .
git commit -m "add 1"
touch 2.txt
git add .
git commit -m "add 2"
touch 3.txt
git add .
git commit -m "add 3"
touch 4.txt
git add .
git commit -m "add 4"
touch 5.txt
git add .
git commit -m "add 5"
## 查看现在的提交
git log
commit a75ed742838ebc1ef1073502623478f73e1ec21f
Author:
Date: Wed Mar 4 10:02:51 2020 +0800
add 5
commit 8b485bb4768b2abf8f6400dcba069f1a650ed5ec
Author:
Date: Wed Mar 4 09:59:27 2020 +0800
add 4
commit 63ce9fb010da550c668aca66758c45fbfad46e2b
Author:
Date: Wed Mar 4 09:59:04 2020 +0800
add 3
commit 9cd34c4d42f52cfb40026dae613c8ad29d7cbc66
Author:
Date: Wed Mar 4 09:58:45 2020 +0800
add 2
commit 77bd0eb1a97e1676367ea236c1c47c155eaa8430
Author:
Date: Wed Mar 4 09:58:23 2020 +0800
add 1
现在我们已经有了一些提交了,接下来开始练习:
pick 8b485bb add 4
pick a75ed74 add 5
# Rebase 63ce9fb..a75ed74 onto 63ce9fb (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
~
~
~
解释:我们刚刚执行的命令中
HEAD~2
代表选择离HEAD最近的两条提交下面注释的是提示,我们不需要管,只要专注前两行就ok
pick a75ed74 add 5
pick 8b485bb add 4
接着
Esc
,:wq
保存退出.
git log
查看,4 和 5 的顺序改变了
我们来删除 add 4 的那条提交
git rebase -i HEAD~2
出现如下:
pick a75ed74 add 5
pick 8b485bb add 4
# Rebase 575fd8b..bb2a77d onto 575fd8b (1 command(s))
# .....略
接着
Esc
,:wq
保存退出
git log
查看
**说明:**由于杂项中用到的命令都是前文介绍过的,因此本节主要分析https://learngitbranching.js.org/中的解题思路。
本题可以分别使用cherry-pick
和rebase -i
命令完成。
git checkout main
git cherry-pick bugFix
rebase -i
命令,注意搞清楚需要在第几个节点终止!然后将main分支移动至当前位置。git rebase -i HEAD~3
git branch -f main HEAD
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前git commit --amend
来进行一些小修改git rebase -i
来将他们调回原来的顺序git rebase -i HEAD~2
git commit --amend # 可用git rebase -i HEAD~1 替换
git rebase -i HEAD~2
git branch -f main caption
cherry-pick
命令提取newImage分支的提交c2'
和c3
节点git checkout main
git cherry-pick newImage
git branch -f main c1
git cherry-pick c2' c3
GIT 中的标签分为两种,一种是轻量标签(lightweight tag),一种是附注标签(annotated tag)。
tag 的存在,是因为我们需要这种标记的功能。目前的项目开发中,当发布版本时 tag 就派上用场了。例如 v1.0.1,v1.0.2…
另外,git 提供了 tag 的增删改查一系列操作,在 tag 的使用上,可谓非常之方便。
tag 对应某次commit, 是一个点,是不可移动的。
branch 对应一系列 commit,是很多点连成的一根线,有一个HEAD 指针,是可以依靠 HEAD 指针移动的。
所以,两者的区别决定了使用方式,改动代码用 branch ,不改动只查看用 tag。
该章相当于对前文所学知识的总结和运用,因此不在赘述。
Learn Git Branching
git rebase详解:https://blog.csdn.net/weixin_42310154/article/details/119004977
git checkout详解:https://blog.csdn.net/raoxiaoya/article/details/111321583
菜鸟教程:https://www.runoob.com/git/git-tutorial.html
Learn Git Branching 总结:https://www.jianshu.com/p/6e94b5592c40
保姆级 Git 入门教程:https://baijiahao.baidu.com/s?id=1700195384333056004&wfr=spider&for=pc
git指令学习:https://www.cnblogs.com/qxlzzj/p/15037178.html#1%E5%BC%80%E5%8F%91%E6%9C%89%E6%B2%A1%E6%9C%89%E5%8F%AF%E8%83%BD%E6%98%AF%E4%B8%80%E4%B8%AA%E4%BA%BA
git撤销merge,彻底学会gitrevert的⽤法:https://wenku.baidu.com/view/b2050a36f28583d049649b6648d7c1c708a10b0b.html
git cherry-pick 教程:https://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html
git rebase -i 命令详解:(https://blog.csdn.net/the_power/article/details/104651772/
git tag创建、远程推送、回退以及强推:https://blog.csdn.net/QH_JAVA/article/details/77979622