这是对上一次提交的修正,amend:修正修订的意思。
比如:我代码的注释写错了,但我已经commit了,这时我当然可以把注释改掉,再commit一次。但这样的活再log日志里就会有两次很相近的(甚至可以说一模一样的提交)提交,这没有必要因为我只是改了个注释,那么使用git commit --amend再提交一次,这次提交不仅会覆盖上次错误的提交,而且上次错误的提交不会存在在日志中。
git reset HEAD readme.txt
用来将readme文件从暂存区放回工作区。
git reset HEAD commit
git reset 确实是个危险的命令,如果加上了 --hard 选项则更是如此。 然而在上述场景中,工作目录中的文件尚未修改,因此相对安全一些。
请务必记得 git checkout – 是一个危险的命令。 你对那个文件在本地的任何修改都会消失——Git 会用最近提交的版本覆盖掉它。 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。
git restore 也有这样的功能。
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了:
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master 5c7e5ea] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
现在,文件就从版本库中被删除了。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本,这就用到上面的git checkout –
git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
会将工作区的版本库设置成你commit_id的那个版本库。慎用。
为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。
前面我们了解到,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照 。
在进行提交操作时,Git 会保存一个提交对象(commit object)。 知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。
提交记录
为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和,然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
$ git add readme.txt test.md LICENSE
$ git commit -m 'The initial commit of my project'
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。
Git 会把仓库中的每次提交串成一条时间线,这条时间线就是一个分支。
在 Git 里,每个仓库都会有一个主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git 用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git 新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
你看,Git 创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git 怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以 Git 合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
$ git switch -c dev
直接切换到已有的master分支,可以使用
git switch master
创建新的分支可以使用:
git branch dev
不加文件名,可以查看有哪些分支。(带 * 的当前所在分支)
$ git branch
* dev
master
$ git merge dev
Updating 599dbdb..4aac6c7
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
注意到上面的Fast-forward信息,Git 告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。我们后面会讲其他方式的合并。
通常,合并分支时,如果可能,Git 会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git 就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
git merge --no-ff -m "merge with no-ff" dev
再打印一下log
$ git log --graph --pretty=oneline --abbrev-commit
* fc76cf7 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
可以看到,不使用Fast forward模式,merge后就像这样:m
$ git branch -d dev
Deleted branch dev (was 4aac6c7).
因为创建、合并和删除分支非常快,所以 Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
其次,干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,并在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git 有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
在 Git 中创建标签非常简单,首先,切换到需要打标签的分支上:
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
然后,敲命令git tag 就可以打一个新标签:
git tag v1.0
git tag -a v0.1 -m "version 0.1 released" 187f963
可以用命令git tag查看所有标签:
git tag
v1.0
默认标签是打在这个分支最新提交的commit上的。如果我们想为之前的commit打标签,可以在后面添加这个commit的commit_id。
git tag v0.9 fb8b190
git tag
v0.9
v1.0
注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show 查看标签信息
$ git show v0.9
commit fb8b190b016f5f19c7758ca21df0a5798a7a5fe1 (tag: v0.9)
Merge: 06e5e3a 2443c95
Author: Miykael_xxm <[email protected]>
Date: Sun Nov 29 20:18:26 2020 +0800
conflict fixed
(END)
注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
git tag -d <tagname>
$ git tag -d v0.1
Deleted tag 'v0.1' (was 94e0f81)