Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Git教程
注明:学习自廖雪峰老师
Git简介
安装Git
在Linux上安装Git
首先,你可以试着输入
git
,看看系统有没有安装Git:1
2
3$ git
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git像上面的命令,有很多Linux会友好地告诉你Git没有安装,还会告诉你如何安装Git。
如果你碰巧用Debian或Ubuntu Linux,通过一条
sudo apt-get install git
就可以直接完成Git的安装,非常简单。老一点的Debian或Ubuntu Linux,要把命令改为
sudo apt-get install git-core
,因为以前有个软件也叫GIT(GNU Interactive Tools),结果Git就只能叫git-core
了。由于Git名气实在太大,后来就把GNU Interactive Tools改成gnuit
,git-core
正式改为git
。如果是其他Linux版本,可以直接通过源码安装。先从Git官网下载源码,然后解压,依次输入:
./config
,make
,sudo make install
这几个命令安装就好了。在Mac OS X上安装Git
如果你正在使用Mac做开发,有两种安装Git的方法。
一是安装homebrew,然后通过homebrew安装Git,具体方法请参考homebrew的文档:http://brew.sh/。
第二种方法更简单,也是推荐的方法,就是直接从AppStore安装Xcode,Xcode集成了Git,不过默认没有安装,你需要运行Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command Line Tools”,点“Install”就可以完成安装了。
在Windows上安装Git
在Windows上使用Git,可以从Git官网直接下载安装程序,然后按默认选项安装即可。
安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!
安装完成后,还需要最后一步设置,在命令行输入:
1
2$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。
注意
git config
命令的--global
参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
创建版本库
创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录:
1
2
3
4$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngitpwd
命令用于显示当前目录。在我的Mac上,这个仓库位于/Users/michael/learngit
。第二步,通过
git init
命令把这个目录变成Git可以管理的仓库:1
2$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个
.git
的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。如果你没有看到
.git
目录,那是因为这个目录默认是隐藏的,用ls -ah
命令就可以看见。也不一定必须在空目录下创建Git仓库,选择一个已经有东西的目录也是可以的。不过,不建议你使用自己正在开发的公司项目来学习Git,否则造成的一切后果概不负责。
把文件添加到版本库
首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
使用Windows的童鞋要特别注意:
千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Notepad++代替记事本,不但功能强大,而且免费!记得把Notepad++的默认编码设置为UTF-8 without BOM即可
言归正传,现在我们编写一个
readme.txt
文件,内容如下:1
2Git is a version control system.
Git is free software.一定要放到
learngit
目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。和把大象放到冰箱需要3步相比,把一个文件放到Git仓库只需要两步。
第一步,用命令
git add
告诉Git,把文件添加到仓库:1
$ git add readme.txt
执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。
第二步,用命令
git commit
告诉Git,把文件提交到仓库:1
2
3
4$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt简单解释一下
git commit
命令,-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。嫌麻烦不想输入
-m "xxx"
行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对别人阅读都很重要。实在不想输入说明的童鞋请自行Google,我不告诉你这个参数。git commit
命令执行成功后会告诉你,1 file changed
:1个文件被改动(我们新添加的readme.txt文件);2 insertions
:插入了两行内容(readme.txt有两行内容)。为什么Git添加文件需要
add
,commit
一共两步呢?因为commit
可以一次提交很多文件,所以你可以多次add
不同的文件,比如:1
2
3$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."小结
现在总结一下今天学的两点内容:
初始化一个Git仓库,使用
git init
命令。添加文件到Git仓库,分两步:
- 使用命令
git add <file>
,注意,可反复多次使用,添加多个文件; - 使用命令
git commit -m <message>
,完成。
- 使用命令
时光机穿梭
版本回退
HEAD
指向的版本就是当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。因此,Git允许我们在版本的历史之间穿梭,还可以写成使用命令git reset --hard commit_id
。- 穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本。 - 要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本。
工作区和暂存区
工作区:电脑里能看到的目录,前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区;第二步是用
git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。因为我们创建Git版本库时,Git自动为我们创建了唯一一个
master
分支,所以,现在,git commit
就是往master
分支上提交更改。你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。(所以,
git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。)Git管理的文件分为:工作区,版本库,版本库又分为暂存区stage和暂存区分支master(仓库)
工作区>>>>暂存区>>>>仓库
git add
把文件从工作区>>>>暂存区,``git commit`把文件从暂存区>>>>仓库,git diff
查看工作区和暂存区差异,git diff
–cached查看暂存区和仓库差异,git diff HEAD
查看工作区和仓库的差异,git add
的反向命令git checkout
,撤销工作区修改,即把暂存区最新版本转移到工作区,git commit
的反向命令``git reset HEAD`,就是把仓库最新版本转移到暂存区。
管理修改
- 理解了Git是如何跟踪修改的,每次修改,如果不用
git add
到暂存区,那就不会加入到commit
中。
- 理解了Git是如何跟踪修改的,每次修改,如果不用
撤销修改
- 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git checkout -- file
。 - 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。 - 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
- 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
删除文件
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用
rm
命令删了:$ rm test.txt
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,
git status
命令会立刻告诉你哪些文件被删除了:现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令
git rm
删掉,并且git commit
:$ git rm test.txt
rm 'test.txt'
现在,文件就从版本库中被删除了小提示:先手动删除文件,然后使用
git rm <file>
和git add<file>
效果是一样的。另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
。git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。但是,从来没有被添加到版本库就被删除的文件,是无法恢复的!小结:命令
git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
远程仓库
添加远程库
要关联一个远程库,使用命令
git remote add origin git@server-name:path/repo-name.git
;以github
为例子:git remote add origin git@github.com:liudufu/First_Git_Text.git
关联后,使用命令
git push -u origin master
第一次推送master分支的所有内容;此后,每次本地提交后,只要有必要,就可以使用命令
git push origin master
推送最新修改;分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而
SVN
在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了
从远程库克隆
要克隆一个仓库,首先必须知道仓库的地址,然后使用
git clone
命令克隆。例如:git clone git@github.com:michaelliao/gitskills.git
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。你也许还注意到,
GitHub
给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。使用
https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http
端口的公司内部就无法使用ssh
协议而只能用https
。Git支持多种协议,包括
https
,但ssh
协议速度最快。
分支管理
简介:分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
创建与合并分支
master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:每次提交,
master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。当我们创建新的分支,例如
dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:首先,我们创建
dev
分支,然后切换到dev
分支checkout -b dev``` 1
2
`git checkout`命令加上`-b`参数表示创建并切换,相当于以下两条命令:$ git branch dev
$ git checkout dev
Switched to branch ‘dev’1
2
3
4
5
6
7
8
9
- 把`dev`分支的工作成果合并到`master`分支上:
```git
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。当然,也不是每次合并都能
Fast-forward
我们注意到切换分支使用
git checkout <branch>
,而前面讲过的撤销修改则是git checkout -- <file>
,同一个命令,有两种作用,确实有点令人迷惑。实际上,切换分支这个动作,用
switch
更科学。因此,最新版本的Git提供了新的git switch
命令来切换分支:创建并切换到新的
dev
分支,可以使用:1
$ git switch -c dev
直接切换到已有的
master
分支,可以使用:1
$ git switch master
使用新的
git switch
命令,比git checkout
要更容易理解。小结:查看分支:
git branch
创建分支:
git branch <name>
切换分支:
git checkout <name>
或者git switch <name>
创建+切换分支:
git checkout -b <name>
或者git switch -c <name>
合并某分支到当前分支:
git merge <name>
删除分支:
git branch -d <name>
解决冲突
- 当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
- 解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
- 用
git log --graph
命令可以看到分支合并图。
分支管理策略
Git会用
Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用
Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。下面我们实战一下
--no-ff
方式的git merge
:准备合并
dev
分支,请注意--no-ff
参数,表示禁用Fast forward
:1
$ git merge --no-ff -m "merge with no-ff" dev
因为本次合并要创建一个新的commit,所以加上
-m
参数,把commit描述写进去。合并后,我们用git log
看看分支历史:在Git中,干活都在
dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。
bug分支
哪个分支上修复bug,假定需要在
master
分支上修复,就从master
创建临时分支:1
2
3
4
5
6
7$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'isse-101'bug 修复后把文件add以及commit,切换回master分支,删除
issue-101
分支:$ git switch master
git merge --no-ff -m "merged bug fix 101" issue-101
查看刚才的工作现场
git stash list
1
2$ git stash list
stash@{0}: WIP on dev: f52c633 add merge工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用
git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除;另一种方式是用
git stash pop
,恢复的同时把stash内容也删了:再用git stash list
查看,就看不到任何stash内容了:你可以多次stash,恢复的时候,先用
git stash list
查看,然后恢复指定的stash,用命令:1
$ git stash apply stash@{0}
同样的bug,要在dev上修复,我们只需要把
4c805e2 fix bug 101
这个提交所做的修改“复制”到dev分支。注意:我们只想复制4c805e2 fix bug 101
这个提交所做的修改,并不是把整个master分支merge过来。为了方便操作,Git专门提供了一个
cherry-pick
命令,让我们能复制一个特定的提交到当前分支:1
2
3
4
5
6$ git branch
* dev
master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)Git自动给dev分支做了一次提交,注意这次提交的commit是
1d4b803
,它并不同于master的4c805e2
,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick
,我们就不需要在dev分支上手动再把修bug的过程重复一遍。有些聪明的童鞋会想了,既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复bug,然后在master分支上“重放”行不行?当然可以,不过你仍然需要
git stash
命令保存现场,才能从dev分支切换到master分支。小结:修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场
git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场;在master分支上修复的bug,想要合并到当前dev分支,可以用
git cherry-pick <commit>
命令,把bug提交的修改“复制”到当前分支,避免重复劳动
Feature分支
开发一个新feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过
git branch -D <name>
强行删除。
多人协作
当你从远程仓库克隆时,实际上Git自动把本地的
master
分支和远程的master
分支对应起来了,并且,远程仓库的默认名称是origin
。要查看远程库的信息,用git remote
用git remote -v
显示更详细的信息:master
分支是主分支,因此要时刻与远程同步;dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。抓取分支,现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
$ git clone git@github.com:michaelliao/learngit.git
你的小伙伴已经向
origin/dev
分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:$ git push origin dev
,这个时候会推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送:如果git pull
也失败了,原因是没有指定本地dev
分支与远程origin/dev
分支的链接,根据提示,设置dev
和origin/dev
的链接:1
2$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.在进行pull,这回
git pull
成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:因此,多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin <branch-name>
推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并; - 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功!
如果
git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <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
,如果有冲突,要先处理冲突。
Rebase
多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。
每次合并再push后,分支变成了这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/
* | 12a631b merged bug fix 101
|\ \
| * | 4c805e2 fix bug 101
|/ /
* | e1e9c68 merge with no-ff
|\ \
| |/
| * f52c633 add merge
|/
* cf810e4 conflict fixed这个时候,rebase就派上了用场。我们输入命令
git rebase
试试1
2
3
4
5
6
7
8
9
10
11
12$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py输出了一大堆操作,到底是啥效果?再用
git log
看看:1
2
3
4
5
6$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...原本分叉的提交现在变成一条直线了!这种神奇的操作是怎么实现的?其实原理非常简单。我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了
f005ed4 (origin/master) set exit=1
之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于d1be385 init hello
,而是基于f005ed4 (origin/master) set exit=1
,但最后的提交7e61ed4
内容是一致的。这就是rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。
最后,通过push操作把本地分支推送到远程:
再用
git log
看看效果1
2
3
4
5
6$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...远程分支的提交历史也是一条直线。
小结:
- rebase操作可以把本地未push的分叉提交历史整理成直线;
- rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。
标签管理
简介:发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。例如:
请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
创建标签
- 打标签很简单,切换到打标签的分支
1
2
3
4
git branch
* dev
master
$ git checkout master
敲`git tag <name>`打一个新的标签
1
$ git tag v1.0
使用`git tag` 查看所有标签
1
2
$ git tag
v1.0
比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id,然后打上就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git log --pretty=oneline --abbrev-commit
12a631b (HEAD -> master, tag: v1.0, origin/master) merged bug fix 101
4c805e2 fix bug 101
e1e9c68 merge with no-ff
f52c633 add merge
cf810e4 conflict fixed
5dc6824 & simple
14096d0 AND simple
b17d20e branch test
d46f35e remove test.txt
b84166e add test.txt
519219b git tracks changes
e43a48b understand how stage works
1094adb append GPL
e475afc add distributed
eaadf4e wrote a readme file
比方说要对`add merge`这次提交打标签,它对应的commit id是`f52c633`,敲入命令:`$ git tag v0.9 f52c633`
标签不是按时间顺序列出,而是按字母排序的。可以用
git show <tagname>
查看标签信息:还可以创建带有说明的标签,用
-a
指定标签名,-m
指定说明文字:1
$ git tag -a v0.1 -m "version 0.1 released" 1094adb
用命令
git show <tagname>
可以看到说明文字:1
2
3
4
5
6
7$ git show v0.1
tag v0.1
Tagger: Michael Liao <askxuefeng@gmail.com>
Date: Fri May 18 22:48:43 2018 +0800
version 0.1 released
......标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
小结:
- 命令
git tag <tagname>
用于新建一个标签,默认为HEAD
,也可以指定一个commit id; - 命令
git tag -a <tagname> -m "blablabla..."
可以指定标签信息; - 命令
git tag
可以查看所有标签。
- 命令
操作标签
删除一个标签:
git tag -d v0.1
因为创建的标签都保存在本地,不会自动推送到远程。所以打错的标签可以在本地安全的删除,如果要推送某个标签到远程,使用命令
git push origin <tagname>
:1
2
3
4$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v1.0 -> v1.0或者,一次性推送全部尚未推送到远程的本地标签:
1
2
3
4$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v0.9 -> v0.9如果需要删除已经推送到远程的标签,需要先删除本地标签
1
2$ git tag -d v0.9
Deleted tag 'v0.9' (was f52c633)然后,从远程删除,命令也是push,但是格式却不同:
1
2
3$ git push origin :refs/tags/v0.9
To github.com:michaelliao/learngit.git
- [deleted] v0.9登录GitHub查看是否删除了标签
小结:
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签。
- 命令
使用GitHub
如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的项目主页https://github.com/twbs/bootstrap,点“Fork”就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone:
1
git clone git@github.com:michaelliao/bootstrap.git
一定要从自己的账号下clone仓库,这样你才能推送修改。如果从bootstrap的作者的仓库地址
git@github.com:twbs/bootstrap.git
克隆,因为没有权限,你将不能推送修改。Bootstrap的官方仓库
twbs/bootstrap
、你在GitHub上克隆的仓库my/bootstrap
,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:如果你想修复bootstrap的一个bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。
如果你希望bootstrap的官方库能接受你的修改,你就可以在GitHub上发起一个pull request。当然,对方是否接受你的pull request就不一定了。
如果你没能力修改bootstrap,但又想要试一把pull request,那就Fork一下我的仓库:https://github.com/michaelliao/learngit,创建一个`your-github-id.txt`的文本文件,写点自己学习Git的心得,然后推送一个pull request给我,我会视心情而定是否接受。liudufu https://github.com
小结
- 在GitHub上,可以任意Fork开源仓库;
- 自己拥有Fork后的仓库的读写权限;
- 可以推送pull request给官方仓库来贡献代码。
使用Gitee
和GitHub相比,Gitee也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等功能。对于团队协作开发,Gitee还提供了项目管理、代码托管、文档管理的服务,5人以下小团队免费。
Gitee的免费版本也提供私有库功能,只是有5人的成员上限。
使用Gitee和使用GitHub类似,我们在Gitee上注册账号并登录后,需要先上传自己的SSH公钥。选择右上角用户头像 -> 菜单“修改资料”,然后选择“SSH公钥”,填写一个便于识别的标题,然后把用户主目录下的
.ssh/id_rsa.pub
文件的内容粘贴进去:如果我们已经有了一个本地的git仓库(例如,一个名为learngit的本地库),如何把它关联到Gitee的远程库上呢?
首先,我们在Gitee上创建一个新的项目项目名称最好与本地库保持一致:
然后,我们在本地库上使用命令
git remote add
把它和Gitee的远程库关联:1
2git remote add origin git@gitee.com:liaoxuefeng/learngit.git
(git remote add origin git@gitee.com:https://gitee.com/dufu-03-13/git_repo_Text)之后,就可以正常地用
git push
和git pull
推送了!如果在使用命令
git remote add
时报错:1
2git remote add origin git@gitee.com:liaoxuefeng/learngit.git
fatal: remote origin already exists.这说明本地库已经关联了一个名叫
origin
的远程库,此时,可以先用git remote -v
查看远程库信息:1
2
3git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)可以看到,本地库已经关联了
origin
的远程库,并且,该远程库指向GitHub。我们可以删除已有的GitHub远程库:
1
git remote rm origin
再关联Gitee的远程库(注意路径中需要填写正确的用户名):
1
git remote add origin git@gitee.com:liaoxuefeng/learngit.git
此时,我们再查看远程库信息:
1
2
3git remote -v
origin git@gitee.com:liaoxuefeng/learngit.git (fetch)
origin git@gitee.com:liaoxuefeng/learngit.git (push)现在可以看到,origin已经被关联到Gitee的远程库了。通过
git push
命令就可以把本地库推送到Gitee上。git本身是分布式版本控制系统,可以同步到另外一个远程库,当然也可以同步到另外两个远程库。
使用多个远程库时,我们要注意,git给远程库起的默认名称是
origin
,如果有多个远程库,我们需要用不同的名称来标识不同的远程库。仍然以
learngit
本地库为例,我们先删除已关联的名为origin
的远程库:1
git remote rm origin
然后,先关联GitHub的远程库:
1
git remote add github git@github.com:michaelliao/learngit.git
注意,远程库的名称叫
github
,不叫origin
了。接着,再关联Gitee的远程库:
1
git remote add gitee git@gitee.com:liaoxuefeng/learngit.git
同样注意,远程库的名称叫
gitee
,不叫origin
。现在,我们用
git remote -v
查看远程库信息,可以看到两个远程库:1
2
3
4
5git remote -v
gitee git@gitee.com:liaoxuefeng/learngit.git (fetch)
gitee git@gitee.com:liaoxuefeng/learngit.git (push)
github git@github.com:michaelliao/learngit.git (fetch)
github git@github.com:michaelliao/learngit.git (push)如果要推送到GitHub,使用命令:
1
git push github master
如果要推送到Gitee,使用命令:
1
git push -u gitee master
这样一来,我们的本地库就可以同时与多个远程库互相同步: