[TOC]
0x00 三步骤四个区五状态 描述:下面所有讨论我们都假设只使用一个分支,也就是主分支master的情况
,虽然这种作法并不符合git规范,但是现实情况中绝大部分用户是直接在master分支上进行工作的,所以在这里我们不去引入更加复杂的各种分支的情况,也不涉及标签tag的操作,只讲在最简单的主分支上如何回退。
Git 是一个分布式的版本控制工具,因此远程和本地可以视为两个独立的 Git 仓库,下图是一张经典的 Git 中的数据流与存储级别的介绍,其中储存级别主要包含几部分:
上图是一张经典的 Git 中的数据流与存储级别的介绍,其中储存级别主要包含几部分:
weiyigeek.top-工作流程
注意:
工作区有一个隐藏目录.git,这个不算工作区而是Git的版本库(Repository):。
正常情况下我们的工作流就是3个步骤
:1 2 3 git add . git commit -m "comment" git push
四个区
和其他版本控制管理工具SVN的一个不同之处,多引入了一个暂存区(Stage)的概念
工作区(Working Area) :
就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区
暂存区(Stage) :
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
本地仓库(Local Repository)
远程仓库(Remote Repository)
五种状态
,以上4个区进入每一个区成功之后会产生一个状态,再加上最初始的一个状态,一共是5种状态1 2 3 4 5 未修改(Origin) 已修改(Modified) 已暂存(Staged) 已提交(Committed) 已推送(Pushed)
检查状态与对比 如何查看我们进行到上面哪一个区,哪一个状态? 答:git status告诉我们,将要被提交的修改包括readme.txt,下一步就可以放心地提交了;1 2 3 4 5 6 7 8 9 $ git status $ git add . $ git commit -m "add distributed" $ git status
weiyigeek.top-git status
采用git diff可以查看不同区里面的提交的数据,告诉我们更改了什么内容,检查修改的二级命令都相同都是diff,只是参数有所不同
。1 2 3 4 5 6 7 8 git diff git diff --cached git diff master origin/master
weiyigeek.top-git diff
总结:
要随时掌握工作区的状态,使用git status命令。git status告诉你有文件被修改过,用git diff可以查看修改内容。
0x01 撤销与丢弃 撤销修改 了解清楚如何检查各种修改之后,我们开始尝试各种撤销操作。
1 2 3 4 5 6 7 git checkout . git reset --hard
1 2 3 4 5 6 git reset git checkout . git reset --hard
1 2 git reset --hard origin/master
1 2 3 git reset --hard HEAD^ git push -f
_总结_:
以上4种状态的撤销我们都用到了同一个命令git reset –hard,前2种状态的用法甚至完全一样,所以只要掌握了git reset –hard这个命令的用法,从此你再也不用担心提交错误了。
管理修改 为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。你会问,什么是修改? 答:比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
示例:1 2 3 4 5 6 7 8 $ echo "change 1 id" > readme.txt $ git add readme.txt $ git status $ echo "change 2 id" >> readme.txt $ git commit -m "git tracks changes" $ git status
咦,怎么第二次的修改没有被提交? 答:我们回顾一下操作过程:第一次修改 -> git add -> 第二次修改 -> git commit
,Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是在工作区的第二次修改并没有放入暂存区,所以git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交
。
那怎么提交第二次修改呢? 你可以继续git add再git commit,也可以别着急提交第一次修改,先git add第二次修改,再git commit,就相当于把两次修改合并后一块提交了1 2 第一次修改 -> git add -> 第二次修改 -> git add -> git commit -> git status
丢弃修改 比如:您在您的项目中修改了文件并添加文件到了暂存区,却发现错误需要进行更正; 既然错误发现得很及时,就可以很容易地纠正它,你可以删掉最后一行手动把文件恢复到上一个版本的状态。1 2 3 4 5 6 git checkout -- file $ git checkout -- readme.txt * 一种是readme.txt自修改后还没有被放到暂存区,现在撤销修改就回到和版本库一模一样的状态; * 一种是readme.txt已经添加到暂存区后,又作了修改,现在撤销修改就回到添加到暂存区后的状态(即没有更改)。
总之,就是让这个文件回到最近一次git commit或git add时的状态。git checkout -- file命令中的--很重要
,没有–,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令
1 2 3 4 $ git reset HEAD readme.txt Unstaged changes after reset: M readme.txt
_总结_:
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout – file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
时光机穿梭 描述:当然了在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统干什么。 版本控制系统肯定有某个命令可以告诉我们历史记录即提交说明执行git log
,你看到的一大串类似3628164...882e1e0的是commit id(版本号)
,和SVN不一样Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字用十六进制表示
1 2 3 $git log --pretty=oneline c75b5b8afcff97b3c37c4d57d574b0bc868f6f2f (HEAD -> master, origin/master, origin/HEAD) git study c1ee2c4ec8f7d6a8e9b44f91b5fa59ea3aec13d8 all
为什么commit id需要用这么一大串数字表示呢? 答:因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线;
如果准备把readme.txt回退到上一个版本,也就是“distributed”的那个版本,怎么做呢? 首先,Git必须知道当前版本是哪个版本,在Git中用HEAD表示当前版本,也就是最新的提交3628164…882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来所以写成HEAD~100。
现在,我们要把当前版本“append GPL”回退到上一个版本“distributed”,就可以使用git reset命令1 2 3 4 5 6 7 8 9 10 11 12 13 14 git log HEAD^ git log HEAD^^ git log HEAD^^^ git log HEAD~3 git reset --hard HEAD^ HEAD is now at ea34578 add distributed
还可以继续回退到上一个版本wrote a readme file,不过且慢,然我们用git log再看看现在版本库的状态:
weiyigeek.top-gitlog回退版本查看
最新的那个版本append GPL已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办? 答:在命令窗口没有关闭的情况下,往上查找 append GPL的commit id是3628164,于是就可以指定回到未来的某个版本1 2 3 $ git reset --hard 3628164
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL,改为指向add distributed,然后顺便把工作区的文件更新了;所以你让HEAD指向哪个版本号,你就把当前版本定位在哪
。
weiyigeek.top-HEAD版本回退
现在你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?
答:当你用$git reset --hard HEAD^
回退到add distributed版本时,再想恢复到append GPL,就必须找到append GPL的commit id。1 2 3 4 5 $ git reflog ea34578 HEAD@{0}: reset: moving to HEAD^ 3628164 HEAD@{1}: commit: append GPL ea34578 HEAD@{2}: commit: add distributed cb926e7 HEAD@{3}: commit (initial): wrote a readme file
_总结_:
HEAD指向的版本就是当前版本,因此Git允许我们在版本的历史之间穿梭,使用命令git reset –hard commit_id。
穿梭前用git log可以查看提交历史
,以便确定要回退到哪个版本,要重返未来用git reflog查看命令历史,以便确定要回到未来的哪个版本。
删除文件 在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交:1 2 3 4 5 6 7 8 $ git add test.txt $ git commit -m "add test.txt" [master 94cdc44] add test.txt $ rm test.txt $ git status
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit,现在文件就从版本库中被删除了。1 2 3 4 $ git rm test.txt rm 'test.txt' $ git rm --cached test.txt $ git commit -m "remove test.txt"
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:1 2 $ git checkout -- test.txt
利用文本过滤方式,这些脚本会在文件签出前(”smudge”)和提交到暂存区前(”clean”)被调用。这些过滤器能够做各种有趣的事。
weiyigeek.top-checkout与add
0x02 分支管理 描述:分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过在某个时间点,两个平行宇宙合并了,结果你既学会了Git又学会了SVN!
weiyigeek.top-漫画图
分支在实际中有什么用呢? 假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支你就创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样既安全,又不影响别人工作。
Git的分支是与众不同的,无论创建、切换和删除分支
,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
专有名词:
跟踪分支:tracking branch
远程跟踪分支: remote tracking branch
创建与合并分支 在版本回退里,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。只有一条时间线,在Git里,这个分支叫主分支即master分支
。
HEAD严格来说不是指向提交,而是指向master才是指向提交的,所以HEAD指向的地址就是当前分支。
weiyigeek.top-创建分支
(3) 不过从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
(4) 假如我们在dev上的工作完成了,就可以把dev合并到master上。
Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并
weiyigeek.top-分支合并
(5) 所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支
weiyigeek.top-主分支
开始实战演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 git checkout -b dev $ git branch dev $ git checkout dev git branch * dev master echo "qweqwe" > readme.txtgit add . git commit -m "branch test" $ git checkout master git merge dev ubuntu@WeiyiGeek:/mnt/e/githubProject/test $ git branch -D dev Deleted branch dev (was 50aad8f). ubuntu@WeiyiGeek:/mnt/e/githubProject/test $ git branch * master
当然也不是每次合并都能Fast-forward,
我们后面会讲其他方式的合并。
因为创建、合并和删除分支非常快,所以Git鼓励你 使用分支完成某个任务,合并后再删掉分支
,这和直接在master分支上工作效果是一样的,但过程更安全。
冲突解决 人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
实际案例:1 2 3 4 5 6 7 8 9 10 11 12 13 $ git checkout -b feature1 $ echo "Creating a new branch is quick AND simple." >> readme.txt $ git add readme.txt $ git commit -m "feature1 AND simple" $ git checkout master $ echo "Creating a new branch is quick & simple." >> readme.txt $ git add readme.txt $ git commit -m "Master & simple"
现在,master分支和feature1分支各自都分别有新的提交,变成了这样:
weiyigeek.top-无法快速合并
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result. $ git status Git tracks changes of files. \<<<<<<< HEAD Creating a new branch is quick & simple. ======= Creating a new branch is quick AND simple. \>>>>>>> feature1 $ git add readme.txt $ git commit -m "conflict fixed" $ git branch -d feature1 Deleted branch feature1 (was 75a857c).
现在,master分支和feature1分支变成了下图所示:
weiyigeek.top-主从分支更改同一文件合并冲突解决
用带参数的git log也可以看到分支的合并情况:1 2 3 4 5 6 7 8 9 $ git log --graph --pretty=oneline --abbrev-commit * 59bc1cb conflict fixed //冲突解决 |\ | * 75a857c AND simple * | 400b400 & simple |/ * fec145a branch test ... $ git log --oneline --decorate --graph
总结:
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
分支管理策略 描述:通常合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式
,Git就会在merge时生成一个新的commit,这样从分支历史上就可以看出分支信息。
实际案例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ git checkout -b dev $ git add readme.txt $ git commit -m "add merge" $ git checkout master $ git merge --no-ff -m "merge with no-ff" dev Merge made by the 'recursive' strategy. readme.txt | 1 + 1 file changed, 1 insertion(+) $ git log --graph --pretty=oneline --abbrev-commit * 7825a50 merge with no-ff |\ | * 6224937 add merge |/ * 59bc1cb conflict fixed ...
分支策略在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说dev分支是不稳定的,到某个时候,比如1.0版本发布时再把dev分支合并到master上,再到master分支发布1.0版本;每个人都有自己的分支,时不时地往dev分支上合并就可以了。
_总结_:
Git分支十分强大,在团队开发中应该充分应用。
合并分支时加上--no-ff参数就可以用普通模式合并
,合并后的历史有分支能看出来曾经做过合并
,而fast forward合并就看不出来曾经做过合并。
缺陷BUG分支 软件开发中bug就像家常便饭一样。有了bug就需要修复在Git中,由于分支是如此的强大,所以每个bug都可以通过一个新的临时分支来修复,修复后合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时很自然地,你想创建一个分支issue-101来修复它,但是等等,当前正在dev上进行的工作还没有提交:1 2 3 4 5 6 7 8 9 10 11 12 13 $ git status
并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好Git还提供了一个stash功能
,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作;1 2 3 $ git stash Saved working directory and index state WIP on dev: 6224937 add merge HEAD is now at 6224937 add merge
现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。 首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:1 2 3 4 5 6 $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. $ git checkout -b issue-101 Switched to a new branch 'issue-101'
现在修复bug,需要把“Git is free software …”改为“Git is a free software …”,然后提交:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git add readme.txt $ git commit -m "fix bug 101" [issue-101 cc17032] fix bug 101 1 file changed, 1 insertion(+), 1 deletion(-) $ git checkout master $ git merge --no-ff -m "merged bug fix 101" issue-101 $ git branch -d issue-101
现在,是时候接着回到dev分支干活了!1 2 3 4 5 6 7 8 9 10 $ git checkout dev Switched to branch 'dev' $ git status $ git stash list stash@{0}: WIP on dev: 6224937 add merge
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
(1)用git stash apply恢复但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
(2)用git stash pop,恢复的同时把stash内容也删了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ git stash pop $ git stash list
总结:
修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash一下然后去修复bug,修复后,再git stash pop回到工作现场。
未合并分支 软件开发中,总有无穷无尽的新的功能要不断添加进来。 添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以每添加一个新功能,最好新建一个feature分支,在上面开发,完成后合并,最后删除该feature分支。
现在你终于接到了一个新任务,开发代号为Vulcan的新功能,于是准备开发:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ git checkout -b feature-vulcan $ git add vulcan.py $ git status $ git commit -m "add feature vulcan" $ git checkout dev $ git branch -d feature-vulcan error: The branch 'feature-vulcan' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature-vulcan' . $ git branch -D feature-vulcan
0x03 远程分支关联 多人协作分支 当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且远程仓库的默认名称是origin。1 2 3 4 5 6 $ git remote origin $ git remote -v origin git@github.com:michaelliao/learngit.git (fetch) origin git@github.com:michaelliao/learngit.git (push)
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:1 2 3 $ git push origin master $ git push origin dev
但是并不是一定要把本地分支往远程推送,那么哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature(n. 特色,特征;容貌;特写或专题节目)分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。现在模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
1 2 $ git clone git@github.com:michaelliao/learngit.git Cloning into 'learngit' ...
当你的小伙伴从远程库clone时,默认情况下你的小伙伴只能看到本地的master分支。 用git branch命令看看:
现在你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:1 2 $ git checkout -b dev origin/dev
现在他就可以在dev上继续修改,然后时不时地把dev分支push到远程:1 2 3 4 5 6 $ git commit -m "add /usr/bin/env" [dev 291bea8] add /usr/bin/env 1 file changed, 1 insertion(+) $ git push origin dev fc38031..291bea8
你的小伙伴已经向origin/dev分支推送了他的提交
,而碰巧你也对同样的文件作了修改,并试图推送:1 2 3 4 5 6 7 8 9 10 11 $ git add hello.py $ git commit -m "add coding: utf-8" [dev bd6ae48] add coding: utf-8 1 file changed, 1 insertion(+) $ git push origin dev To git@github.com:michaelliao/learngit.git ! [rejected] dev -> dev (non-fast-forward) error: failed to push some refs to hint: its remote counterpart. Merge the remote changes (e.g. 'git pull' )
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突
,解决办法也很简单Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突再推送:
但是 git pull 也失败了,原因是没有指定本地dev分支与远程origin/dev分支的关联,根据提示设置dev和origin/dev的链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ git branch --set -upstream dev origin/dev Branch dev set up to track remote branch dev from origin. $ git pull Auto-merging hello.py CONFLICT (content): Merge conflict in hello.py Automatic merge failed; fix conflicts and then commit the result. $ git commit -m "merge & fix hello.py" [dev adca45d] merge & fix hello.py $ git push origin dev 291bea8..adca45d dev -> dev
因此多人协作的工作模式通常是这样,一旦熟悉了就非常简单:
首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name
总结:
查看远程库信息,使用git remote -v;
本地新建的分支如果不推送到远程,对其他人就是不可见的;
从本地推送分支,使用git push origin branch-name,如果推送失败先用git pull抓取远程的新提交;
在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
建立本地分支和远程分支的关联,使用git branch –set-upstream branch-name origin/branch-name;
从远程抓取分支,使用git pull,如果有冲突,要先处理冲突,最后在git push origin branch-name推送
分支之间commit 实际案例:1 2 git checkout <branch-name> && git cherry-pick <commit-id>
0x04 标签管理 描述:发布一个版本时我们通常先在版本库中打一个标签(tag),这样就唯一确定了打标签时刻的版本
。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来,所以标签也是版本库的一个快照
。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动 ),所以创建和删除标签都是瞬间完成的,所以tag就是一个让人容易记住的有意义的名字,它跟某个commitID绑在一起。
Git有commit,为什么还要引入tag? “请把上周一的那个版本打包发布,commit号是6a5819e…” / “一串乱七八糟的数字不好找!” 如果换一个办法:“请把上周一的那个版本打包发布,版本号是v1.2” / “好的,按照tag v1.2查找commit就行!”
创建标签 在Git中打标签非常简单,首先切换到需要打标签的分支上:1 2 3 4 5 6 $ git branch * dev master $ git checkout master Switched to branch 'master'
然后敲命令git tag <name>
就可以打一个新标签:1 2 3 4 5 $ git tag v1.0 $ git tag v1.0
有时候如果忘了打标签,比如现在已经是周五了但应该在周一打的标签没有打怎么办?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 $ git log --pretty=oneline --abbrev-commit 6a5819e merged bug fix 101 cc17032 fix bug 101 7825a50 merge with no-ff 6224937 add merge 59bc1cb conflict fixed 400b400 & simple 75a857c AND simple fec145a branch test d17efd8 remove test.txt $ git tag v0.9 6224937 $ git tag v0.9 v1.0 $ git show v0.9 commit 622493706ab447b6bb37e4e2a2f276a20fed2ab4 Author: Michael Liao <askxuefeng@gmail.com> Date: Thu Aug 22 11:22:08 2013 +0800 add merge $ git tag -a v0.1 -m "version 0.1 released" 3628164 $ git show v0.1 tag v0.1 Tagger: Michael Liao <askxuefeng@gmail.com> Date: Mon Aug 26 07:28:11 2013 +0800 version 0.1 released commit 3628164fb26d48395383f8f31179f24e0882e1e0 Author: Michael Liao <askxuefeng@gmail.com> Date: Tue Aug 20 15:11:49 2013 +0800 append GPL
签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对就会报错:1 2 3 gpg: signing failed: secret key not available error: gpg failed to sign the data error: unable to sign the tag
用PGP签名的标签是不可伪造的,因为可以验证PGP签名,通用git tag 可以通过-s用私钥签名一个标签:1 $ git tag -s v0.2 -m "signed version 0.2 released" fec145a
用命令git show 可以看到PGP签名信息:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git show v0.2 tag v0.2 Tagger: Michael Liao <askxuefeng@gmail.com> Date: Mon Aug 26 07:28:33 2013 +0800 signed version 0.2 released -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (Darwin) iQEcBAABAgAGBQJSGpMhAAoJEPUxHyDAhBpT4QQIAKeHfR3bo... -----END PGP SIGNATURE----- commit fec145accd63cdc9ed95a2f557ea0658a2a6537f Author: Michael Liao <askxuefeng@gmail.com> Date: Thu Aug 22 10:37:30 2013 +0800 branch test
总结:
命令git tag 用于新建一个标签,默认为HEAD,也可以指定一个commit id,git tag可以查看所有标签。
git tag -a -m “blablabla…”可以指定标签信息;
git tag -s -m “blablabla…”可以用PGP签名标签;
操作标签 如果标签打错了也可以删除指定的标签,因为创建的标签都只存储在本地,不会自动推送到远程;所以打错的标签可以在本地安全删除。1 2 $ git tag -d v0.1 Deleted tag 'v0.1' (was e078af9)
如果要推送某个标签到远程,使用命令git push origin <tagname>
:1 2 3 4 5 6 7 8 9 10 11 12 13 $ git push origin v1.0 Total 0 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git * [new tag] v1.0 -> v1.0 $ git push origin --tags Counting objects: 1, done . Writing objects: 100% (1/1), 554 bytes, done . Total 1 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git * [new tag] v0.2 -> v0.2 * [new tag] v0.9 -> v0.9
如果标签已经推送到远程,要删除远程标签就麻烦一点,操作完成后要想看看是否真的从远程库删除了标签,可以登陆GitHub查看1 2 3 4 5 6 7 8 $ git tag -d v0.9 Deleted tag 'v0.9' (was 6224937) $ git push origin :refs/tags/v0.9 To git@github.com:michaelliao/learngit.git - [deleted] v0.9
总结:
命令git push origin 可以推送一个本地标签;
命令git push origin –tags 可以推送全部未推送过的本地标签;
命令git tag -d 可以删除一个本地标签;
命令git push origin :refs/tags/可以删除一个远程标签。
0x05 总结 .git 仓库元数据 每一个 git 的代码仓库目录下,都会有一个 .git 的文件夹,其中包含的重要文件包含以下: 文件/文件夹 含义
config* 配置文件
description 描述仅供 Git Web 程序使用
HEAD 当前被检出的分支
index 暂存区信息
hooks/ 客户端或服务端的钩子脚本(hook scripts)
info/ 全局性排除(global exclude)文件,不希望被记录在 .gitignore 文件中的忽略模式(ignored patterns)
objects/ 所有数据内容
refs/ 数据(分支)的提交对象的指针