您的位置:首页 > 其它

Git简易教程

2015-03-17 17:00 267 查看
之前在网上看到git,学习了一下。参照廖雪峰git教程

Git简介

Git是什么?
Git是目前世界上最先进的分布式版本控制系统(没有之一)。
Git有什么特点?简单来说就是:高端大气上档次!
那什么是版本控制系统?
如果你用MicrosoftWord写过长篇大论,那你一定有这样的经历:
想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的Word文档变成了这样:
过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。
看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。
更要命的是,有些部分需要你的财务同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。
于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便?
这个软件用起来就应该像这个样子,能记录每次文件的改动:
版本
用户
说明
日期
1
张三
删除了软件服务条款5
7/1210:38
2
张三
增加了License人数限制
7/1218:09
3
李四
财务部门调整了合同金额
7/139:51
4
张三
延长了免费升级周期
7/1415:17
这样,你就结束了手动管理多个“版本”的史前时代,进入到版本控制的20世纪。

Git的诞生(05年)

很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。
Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?
事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。
不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。

集中式vs分布式

Linus一直痛恨的CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢?
先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,这还不得把人给憋死啊。
那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
当然,Git的优势不单是不必联网这么简单,后面我们还会看到Git极其强大的分支管理,把SVN等远远抛在了后面。
CVS作为最早的开源而且免费的集中式版本控制系统,直到现在还有不少人在用。由于CVS自身设计的问题,会造成提交文件不完整,版本库莫名其妙损坏的情况。同样是开源而且免费的SVN修正了CVS的一些稳定性问题,是目前用得最多的集中式版本库控制系统。
除了免费的外,还有收费的集中式版本控制系统,比如IBM的ClearCase(以前是Rational公司的,被IBM收购了),特点是安装比Windows还大,运行比蜗牛还慢,能用ClearCase的一般是世界500强,他们有个共同的特点是财大气粗,或者人傻钱多。
微软自己也有一个集中式版本控制系统叫VSS,集成在VisualStudio中。由于其反人类的设计,连微软自己都不好意思用了。
分布式版本控制系统除了Git以及促使Git诞生的BitKeeper外,还有类似Git的Mercurial和Bazaar等。这些分布式版本控制系统各有特点,但最快、最简单也最流行的依然是Git!

安装Git

最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,慢慢地有人把它移植到了Windows上。现在,Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。

要使用Git,第一步当然是安装Git了。根据你当前使用的平台来阅读下面的文字:

在Linux上安装Git

首先,你可以试着输入
git
,看看系统有没有安装Git:

$git

Theprogram'git'iscurrentlynotinstalled.Youcaninstallitbytyping:

sudoapt-getinstallgit

像上面的命令,有很多Linux会友好地告诉你Git没有安装,还会告诉你如何安装Git。

如果你碰巧用Debian或UbuntuLinux,通过一条
sudo
apt-getinstallgit
就可以直接完成Git的安装,非常简单。

老一点的Debian或UbuntuLinux,要把命令改为
sudoapt-get
installgit-core
,因为以前有个软件也叫GIT(GNUInteractiveTools),结果Git就只能叫
git-core
了。由于Git名气实在太大,后来就把GNU
InteractiveTools改成
gnuit
git-core
正式改为
git


如果是其他Linux版本,可以直接通过源码安装。先从Git官网下载源码,然后解压,依次输入:
./config
make
sudo
makeinstall
这几个命令安装就好了。

在MacOSX上安装Git

如果你正在使用Mac做开发,有两种安装Git的方法。

一是安装homebrew,然后通过homebrew安装Git,具体方法请参考homebrew的文档:http://brew.sh/。

第二种方法更简单,也是推荐的方法,就是直接从AppStore安装Xcode,Xcode集成了Git,不过默认没有安装,你需要运行Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command
LineTools”,点“Install”就可以完成安装了。

Xcode是Apple官方IDE,功能非常强大,是开发Mac和iOS
App的必选装备,而且是免费的!

在Windows上安装Git

实话实说,Windows是最烂的开发平台,如果不是开发Windows游戏或者在IE里调试页面,一般不推荐用Windows。不过,既然已经上了微软的贼船,也是有办法安装Git的。

Windows下要使用很多Linux/Unix的工具时,需要Cygwin这样的模拟环境,Git也一样。Cygwin的安装和配置都比较复杂,就不建议你折腾了。不过,有高人已经把模拟环境和Git都打包好了,名叫msysgit,只需要下载一个单独的exe安装程序,其他什么也不用装,绝对好用。

msysgit是Windows版的Git,从http://msysgit.github.io/下载,然后按默认选项安装即可。

安装完成后,在开始菜单里找到“Git”->“GitBash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!

安装完成后,还需要最后一步设置,在命令行输入:

$gitconfig--globaluser.name"YourName"

$gitconfig--globaluser.email"email@example.com"

因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字("HUYONGPC-DELL")和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。

注意
gitconfig
命令的
--global
参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

--global
参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。

创建版本库

先附上代码:

修改之后:执行以下两步

$gitaddreadme.txt

$gitcommit-m"
解释
"


什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。

所以,创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录:

$mkdirlearngit(
目录
,
默认
C
盘,
c:/users/administrator/learngit)

$cdlearngit

$pwd(
显示目录
)

/Users/michael/learngit


或者创建在
E


pwd
命令用于显示当前目录。在我的Mac上,这个仓库位于
/Users/michael/learngit


如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。

第二步,通过
gitinit
命令把这个目录变成Git可以管理的仓库:

$gitinit

InitializedemptyGitrepositoryin/Users/michael/learngit/.git/

瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(emptyGitrepository),细心的读者可以发现当前目录下多了一个
.git
的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

也不一定必须在空目录下创建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
withoutBOM即可:

言归正传,现在我们编写一个
readme.txt
文件,内容如下:

Gitisaversioncontrolsystem.

Gitisfreesoftware.

一定要放到
learngit
目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。

和把大象放到冰箱需要3步相比,把一个文件放到Git仓库只需要两步。

第一步,用命令
gitadd
告诉Git,把文件添加到仓库:

$gitaddreadme.txt

执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。

第二步,用命令
gitcommit
告诉Git,把文件提交到仓库:

$gitcommit-m"wroteareadmefile"

[master(root-commit)cb926e7]wroteareadmefile

1filechanged,2insertions(+)

createmode100644readme.txt

简单解释一下
gitcommit
命令,
-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。

嫌麻烦不想输入
-m"xxx"
行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对别人阅读都很重要。实在不想输入说明的童鞋请自行Google,我不告诉你这个参数。

gitcommit
命令执行成功后会告诉你,1个文件被改动(我们新添加的readme.txt文件),插入了两行内容(readme.txt有两行内容)。

为什么Git添加文件需要
add
commit
一共两步呢?因为
commit
可以一次提交很多文件,所以你可以多次
add
不同的文件,比如:

$gitaddfile1.txt

$gitaddfile2.txt

$gitaddfile3.txt

$gitcommit-m"add3files."

命令小结1

现在总结一下今天学的三点内容:

1、在本地创建新的Git仓库(连接别人的代码和创建自己的代码库)

mkdire:/learnGit//建立子目录(仓库)

cde:/learnGit//你想要进的目录

gitinit//把这个目录变成Git可以管理的仓库

pwd//用于显示当前目录

2、初始化一个Git仓库,使用
gitinit
命令。

3、添加文件到Git仓库,分两步:

·第一步,使用命令
gitadd<file>
,注意,可反复多次使用,添加多个文件;

·第二步,使用命令
gitcommit
,完成。

时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件,现在,是时候继续工作了,于是,我们继续修改readme.txt文件,改成如下内容:

Gitisadistributedversioncontrolsystem.

Gitisfreesoftware.

现在,运行
gitstatus
命令看看结果:

$gitstatus

#Onbranchmaster//master
是分支

#Changesnotstagedforcommit://
修改
还未提交

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

gitstatus
命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.txt被修改过了,但还没有准备提交的修改。

虽然Git告诉我们readme.txt被修改了,但如果能看看具体修改了什么内容,自然是很好的。比如你休假两周从国外回来,第一天上班时,已经记不清上次怎么修改的readme.txt,所以,需要用
git
diff
这个命令看看:

$gitdiffreadme.txt

diff--gita/readme.txtb/readme.txt

index46d49bf..9247db6100644

---a/readme.txt

+++b/readme.txt

@@-1,2+1,2@@

-Gitisaversioncontrolsystem.

+Gitisadistributedversioncontrolsystem.

Gitisfreesoftware.

gitdiff
顾名思义就是查看difference,显示的格式正是Unix通用的diff格式,可以从上面的命令输出看到,我们在第一行添加了一个“distributed”单词。

知道了对readme.txt作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,

第一步是
gitadd


$gitaddreadme.txt

同样没有任何输出。在执行第二步
gitcommit
之前,我们再运行
git
status
看看当前仓库的状态:

$gitstatus

#Onbranchmaster

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#modified:readme.txt

#

gitstatus
告诉我们,将要被提交的修改包括readme.txt,

下一步,就可以放心地提交了:

$gitcommit-m"adddistributed"

[masterea34578]adddistributed

1filechanged,1insertion(+),1deletion(-)

提交后,我们再用
gitstatus
命令看看仓库的当前状态:

$gitstatus

#Onbranchmaster

nothingtocommit(workingdirectoryclean)

Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(workingdirectoryclean)的。(工作区(Working
Directory):就是你在电脑里能看到的目录,比如我的
learngit
文件夹就是一个工作区)

命令小结2

·要随时掌握工作区的状态,使用
gitstatus
命令。

·如果
gitstatus
告诉你有文件被修改过,用
git
diff
可以查看修改内容。

版本回退

3410次阅读

现在,你已经学会了修改文件,然后把修改提交到Git版本库,现在,再练习一次,修改readme.txt文件如下:

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

然后尝试提交:

$gitaddreadme.txt

$gitcommit-m"appendGPL"

[master3628164]appendGPL

1filechanged,1insertion(+),1deletion(-)

像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为
commit
。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个
commit
恢复,然后继续工作,而不是把几个月的工作成果全部丢失。

现在,我们回顾一下readme.txt文件一共有几个版本被提交到Git仓库里了:

版本1:wroteareadmefile

Gitisaversioncontrolsystem.

Gitisfreesoftware.

版本2:adddistributed

Gitisadistributedversioncontrolsystem.

Gitisfreesoftware.

版本3:appendGPL

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

当然了,在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统干什么。版本控制系统肯定有某个命令可以告诉我们历史记录,在Git中,我们用
git
log
命令查看:

$gitlog

commit3628164fb26d48395383f8f31179f24e0882e1e0

Author:MichaelLiao<askxuefeng@gmail.com>

Date:TueAug2015:11:492013+0800


appendGPL


commitea34578d5496d7dd233c827ed32a8cd576c5ee85

Author:MichaelLiao<askxuefeng@gmail.com>

Date:TueAug2014:53:122013+0800


adddistributed


commitcb926e7ea50ad11b8f9e909c05226233bf755030

Author:MichaelLiao<askxuefeng@gmail.com>

Date:MonAug1917:51:552013+0800


wroteareadmefile

gitlog
命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是“append
GPL”,上一次是“adddistributed”,最早的一次是“wroteareadmefile”。

如果嫌输出信息太多,看得眼花缭乱的,可以试试加上
--pretty=oneline
参数:

$gitlog--pretty=oneline

3628164fb26d48395383f8f31179f24e0882e1e0appendGPL

ea34578d5496d7dd233c827ed32a8cd576c5ee85adddistributed

cb926e7ea50ad11b8f9e909c05226233bf755030wroteareadmefile

需要友情提示的是,你看到的一大串类似3628164...882e1e0的是commit id(版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commitid需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:
启动时光穿梭机
好了,现在我们启动时光穿梭机,准备把readme.txt回退到上一个版本,也就是“adddistributed”的那个版本,怎么做呢?
1、首先,Git必须知道当前版本是哪个版本,

在Git中,用
HEAD
表示当前版本,也就是最新的提交
3628164...882e1e0
(注意我的提交ID和你的肯定不一样),上一个版本就是
HEAD^
,上上一个版本就是
HEAD^^
,当然往上100个版本写100个
^
比较容易数不过来,所以写成
HEAD~100


现在,我们要把当前版本“appendGPL”回退到上一个版本“adddistributed”,就可以使用
git
reset
命令:

$gitreset--hardHEAD^

HEADisnowatea34578adddistributed

--hard
参数有啥意义?这个后面再讲,现在你先放心使用。

看看readme.txt的内容是不是版本“add
distributed”:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

Gitisfreesoftware.

果然。

还可以继续回退到上一个版本“wroteareadmefile”,不过且慢,然我们用
gitlog
再看看现在版本库的状态:

$gitlog

commitea34578d5496d7dd233c827ed32a8cd576c5ee85

Author:MichaelLiao<askxuefeng@gmail.com>

Date:TueAug2014:53:122013+0800


adddistributed


commitcb926e7ea50ad11b8f9e909c05226233bf755030

Author:MichaelLiao<askxuefeng@gmail.com>

Date:MonAug1917:51:552013+0800


wroteareadmefile


最新的那个版本“appendGPL”已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?

办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个“appendGPL”的
commit
id
3628164...
,于是就可以指定回到未来的某个版本:

$gitreset--hard3628164

HEADisnowat3628164appendGPL

版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。

再小心翼翼地看看readme.txt的内容:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

果然,我胡汉山又回来了。

Git的版本回退速度非常快,因为Git在内部有个指向当前版本的
HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD从指向“append
GPL”:

改为指向“adddistributed”:

然后顺便把工作区的文件更新了。所以你让
HEAD
指向哪个版本号,你就把当前版本定位在哪。

现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的
commitid
怎么办?

在Git中,总是有后悔药可以吃的。当你用
$gitreset--hardHEAD^
回退到“add
distributed”版本时,再想恢复到“appendGPL”,就必须找到“appendGPL”的commit id。Git提供了一个命令
gitreflog
用来记录你的每一次命令:

$gitreflog

ea34578HEAD@{0}:reset:movingtoHEAD^

3628164HEAD@{1}:commit:appendGPL

ea34578HEAD@{2}:commit:adddistributed

cb926e7HEAD@{3}:commit(initial):wroteareadmefile

终于舒了口气,第二行显示“appendGPL”的commitid是
3628164
,现在,你又可以乘坐时光机回到未来了。

命令小结3

现在总结一下:

HEAD
指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令[/code]
gitreset--hardcommit_id
。或
$gitreset--hardHEAD^(
上一个版本
)

·穿梭前,用
gitlog
可以查看提交历史,以便确定要回退到哪个版本。

·要重返未来,用
gitreflog
查看命令历史,以便确定要回到未来的哪个版本。

·看看readme.txt的内容

$catreadme.txt

Gitisadistributedversioncontrolsystem.

Gitisfreesoftware.


工作区和暂存区

2379次阅读

Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。

先来看名词解释。

工作区(WorkingDirectory):就是你在电脑里能看到的目录,比如我的
learngit
文件夹就是一个工作区:

版本库(Repository):工作区有一个隐藏目录
.git
,这个不算工作区,而是Git的版本库。

Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支
master
,以及指向
master
的一个指针叫
HEAD


分支和
HEAD
的概念我们以后再讲。

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

第一步是用
gitadd
把文件添加进去,实际上就是把文件修改添加到暂存区(stage或index);

第二步是用
gitcommit
提交更改,实际上就是把暂存区的所有内容提交到当前分支(master)。

因为我们创建Git版本库时,Git自动为我们创建了唯一一个
master
分支,所以,现在,
git
commit
就是往
master
分支上提交更改。

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

俗话说,实践出真知。现在,我们再练习一遍,先对
readme.txt
做个修改,比如加上一行内容:

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

然后,在工作区新增一个
LICENSE
文本文件(内容随便写)。

先用
gitstatus
查看一下状态:

$gitstatus

#Onbranchmaster

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

#Untrackedfiles:

#(use"gitadd<file>..."toincludeinwhatwillbecommitted)

#

#LICENSE

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

Git非常清楚地告诉我们,
readme.txt
被修改了,而
LICENSE
还从来没有被添加过,所以它的状态是
Untracked


现在,使用两次命令
gitadd
,把
readme.txt
LICENSE
都添加后,

gitaddreadme.txt


gitaddLICENSE.txt


gitstatus
再查看一下:

$gitstatus

#Onbranchmaster

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#newfile:LICENSE

#modified:readme.txt

#

现在,暂存区的状态就变成这样了:

所以,
gitadd
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行
git
commit
就可以一次性把暂存区的所有修改提交到分支。

$gitcommit-m"understandhowstageworks"

[master27c9860]understandhowstageworks

2fileschanged,675insertions(+)

createmode100644LICENSE

一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:

$gitstatus

#Onbranchmaster

nothingtocommit(workingdirectoryclean)


现在版本库变成了这样,commit提交后暂存区就没有任何内容了:

暂存区小结

暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。

没弄明白暂存区是怎么回事的童鞋,请向上滚动页面,再看一次。

管理修改

2248次阅读

现在,假定你已经完全掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。

你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。

为什么说Git管理的是修改,而不是文件呢?我们还是做实验。第一步,对readme.txt做一个修改,比如加一行内容:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschanges.

然后,添加:

$gitaddreadme.txt

$gitstatus

#Onbranchmaster

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#modified:readme.txt

#

然后,再修改readme.txt:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschangesoffiles.

提交:

$gitcommit-m"gittrackschanges"

[masterd4f25b6]gittrackschanges

1filechanged,1insertion(+)

提交后,再看看状态:

$gitstatus

#Onbranchmaster

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

咦,怎么第二次的修改没有被提交?

别激动,我们回顾一下操作过程:

第一次修改->
gitadd
->第二次修改
->
gitcommit


你看,我们前面讲了,Git管理的是修改,当你用
gitadd
命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,
git
commit
只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

提交后,用
gitdiffHEAD--readme.txt
命令可以查看工作区和版本库里面最新版本的区别:

$gitdiffHEAD--readme.txt

diff--gita/readme.txtb/readme.txt

index76d770f..a9c5755100644

---a/readme.txt

+++b/readme.txt

@@-1,4+1,4@@

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

-Gittrackschanges.

+Gittrackschangesoffiles.

可见,第二次修改确实没有被提交。

那怎么提交第二次修改呢?你可以继续
gitadd
gitcommit
,也可以别着急提交第一次修改,先
git
add
第二次修改,再
gitcommit
,就相当于把两次修改合并后一块提交了:

第一次修改->
gitadd
->第二次修改
->
gitadd
->
gitcommit


好,现在,把第二次修改提交了,然后开始小结。

小结

现在,你又理解了Git是如何跟踪修改的,每次修改,如果不
add
到暂存区,那就不会加入到
commit
中。

撤销修改

2060次阅读

自然,你是不会犯错的。不过现在是凌晨两点,你正在赶一份工作报告,你在
readme.txt
中添加了一行:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschangesoffiles.

MystupidbossstillprefersSVN.

在你准备提交前,一杯咖啡起了作用,你猛然发现了“stupidboss”可能会让你丢掉这个月的奖金!

既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用
gitstatus
查看一下:

$gitstatus

#Onbranchmaster

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

你可以发现,Git会告诉你,
gitcheckout--file
可以丢弃工作区的修改:

$gitcheckout--readme.txt

命令
gitcheckout--readme.txt
意思就是,把
readme.txt
文件在工作区的修改全部撤销,这里有两种情况:

一种是
readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;

一种是
readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

总之,就是让这个文件回到最近一次
gitcommit
gitadd
时的状态。

现在,看看
readme.txt
的文件内容:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschangesoffiles.

文件内容果然复原了。

gitcheckout--file
命令中的
--
很重要,没有
--
,就变成了“创建一个新分支”的命令,我们在后面的分支管理中会再次遇到
git
checkout
命令。

现在假定是凌晨3点,你不但写了一些胡话,还
gitadd
到暂存区了:

$catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschangesoffiles.

MystupidbossstillprefersSVN.


$gitaddreadme.txt

庆幸的是,在
commit
之前,你发现了这个问题。用
gitstatus
查看一下,修改只是添加到了暂存区,还没有提交:

$gitstatus

#Onbranchmaster

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#modified:readme.txt

#

Git同样告诉我们,用命令
gitresetHEADfile
可以把暂存区的修改撤销掉(unstage),重新放回工作区:

$gitresetHEADreadme.txt

Unstagedchangesafterreset:

Mreadme.txt

gitreset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用
HEAD
时,表示最新的版本。

再用
gitstatus
查看一下,现在暂存区是干净的,工作区有修改:

$gitstatus

#Onbranchmaster

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

还记得如何丢弃工作区的修改吗?

$gitcheckout--readme.txt


$gitstatus

#Onbranchmaster

nothingtocommit(workingdirectoryclean)

整个世界终于清静了!

现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把“stupidboss”提交推送到远程版本库,你就真的惨了……

命令小结4

又到了小结时间。

场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git
checkout--file


场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git
resetHEADfile
,就回到了场景1,第二步按场景1操作。

场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

删除文件

1735次阅读

在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交:

$gitaddtest.txt

$gitcommit-m"addtest.txt"

[master94cdc44]addtest.txt

1filechanged,1insertion(+)

createmode100644test.txt

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用
rm
命令删了:

$rmtest.txt

这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,
gitstatus
命令会立刻告诉你哪些文件被删除了:

$gitstatus

#Onbranchmaster

#Changesnotstagedforcommit:

#(use"gitadd/rm<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#deleted:test.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

现在你有两个选择,

一是确实要从版本库中删除该文件,那就用命令
git
rm
删掉,并且
gitcommit


$gitrmtest.txt

rm'test.txt'

$gitcommit-m"removetest.txt"

[masterd17efd8]removetest.txt

1filechanged,1deletion(-)

deletemode100644test.txt

现在,文件就从版本库中被删除了。

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

$gitcheckout--test.txt

gitcheckout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

命令小结5

命令
gitrm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容

远程仓库

2901次阅读

到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了。

可是有用过集中式版本控制系统SVN的童鞋会站出来说,这些功能在SVN里早就有了,没看出Git有什么特别的地方。

没错,如果只是在一个仓库里管理文件历史,Git和SVN真没啥区别。为了保证你现在所学的Git物超所值,将来绝对不会后悔,同时为了打击已经不幸学了SVN的童鞋,本章开始介绍Git的杀手级功能之一(注意是之一,也就是后面还有之二,之三…):远程仓库。

Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。

你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,怎么玩?

其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在一台电脑上搞几个远程库玩,因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了会导致所有库都挂掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。

实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。

完全可以自己搭建一台运行Git的服务器,不过现阶段,为了学Git先搭个服务器绝对是小题大作。好在这个世界上有个叫GitHub的神奇的网站,从名字就可以看出,这个网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。

在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,

所以,需要一点设置:

第1步:创建SSHKey。在用户主目录下(C:\Users\Administrator),看看有没有.ssh目录,如果有,再看看这个目录下有没有
id_rsa
id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开GitBash),创建SSH
Key:

$ssh-keygen-trsa-C"youremail@example.com"

你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。

(注意空格的有无)

如果一切顺利的话,可以在用户主目录里找到
.ssh
目录,里面有
id_rsa
id_rsa.pub
两个文件,这两个就是SSH
Key的秘钥对,
id_rsa
是私钥,不能泄露出去,
id_rsa.pub
是公钥,可以放心地告诉任何人。

第2步:登陆GitHub,打开“Account
settings”,“SSHKeys”页面:

然后,点“AddSSHKey”,填上任意Title,在Key文本框里粘贴
id_rsa.pub
文件的内容:

点“AddKey”,你就应该看到已经添加的Key:

为什么GitHub需要SSHKey呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。

如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。

确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。

添加远程库

3662次阅读

现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。

首先,登陆GitHub,然后,在右上角找到“Createanewrepo”按钮,创建一个新的仓库:

在Repositoryname填入
learngit
,其他保持默认设置,点击“Create
repository”按钮,就成功地创建了一个新的Git仓库:

目前,在GitHub上的这个
learngit
仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。

现在,我们根据GitHub的提示,在本地的
learngit
仓库下运行命令:(本地仓库关联远程仓库)用git协议或https协议(https://github.com/peterYong/learnGit.git稍慢)

$gitremoteaddorigin
git@github.com:michaelliao/learngit.git[/code]
gitremoteaddorigingit@github.com:peterYong/learnGit.git

请千万注意,把上面的
michaelliao
替换成你自己的GitHub账户名,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH
Key公钥不在我的账户列表中。

添加后,远程库的名字就是
origin
,这是Git默认的叫法,也可以改成别的,但是
origin
这个名字一看就知道是远程库。

下一步,就可以把本地库的所有内容推送到远程库上:

$gitpush-uoriginmaster
(出现异样,请看下面
SSH
警告)

Countingobjects:19,done.

Deltacompressionusingupto4threads.

Compressingobjects:100%(19/19),done.

Writingobjects:100%(19/19),13.73KiB,done.

Total23(delta6),reused0(delta0)

Togit@github.com:michaelliao/learngit.git

*[newbranch]master->master

Branchmastersetuptotrackremotebranchmasterfromorigin.

把本地库的内容推送到远程,用
gitpush
命令,实际上是把当前分支
master
推送到远程。

由于远程库是空的,我们第一次推送
master
分支时,加上了
-u
参数,Git不但会把本地的
master
分支内容推送的远程新的
master
分支,还会把本地的
master
分支和远程的
master
分支关联起来,在以后的推送或者拉取时就可以简化命令。

推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:

从现在起,只要本地作了提交,就可以通过命令:

$gitpushoriginmaster

把本地
master
分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!

SSH警告

当你第一次使用Git的
clone
或者
push
命令连接GitHub时,会得到一个警告:

Theauthenticityofhost'github.com(xx.xx.xx.xx)'can'tbeestablished.

RSAkeyfingerprintisxx.xx.xx.xx.xx.

Areyousureyouwanttocontinueconnecting(yes/no)?

这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入
yes
回车即可。

Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:

Warning:Permanentlyadded'github.com'(RSA)tothelistofknownhosts.

这个警告只会出现一次,后面的操作就不会有任何警告了。

如果你实在担心有人冒充GitHub服务器,输入
yes
前可以对照GitHub的RSAKey的指纹信息是否与SSH连接给出的一致。

命令小结6

要关联一个远程库,使用命令
gitremoteaddorigingit@server-name:path/repo-name.git


关联后,使用命令
gitpush-uoriginmaster
第一次推送master分支的所有内容;

此后,每次本地提交后,只要有必要,就可以使用命令
gitpushoriginmaster
推送最新修改;

分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!

从远程库克隆

2492次阅读

上次我们讲了先有本地库,后有远程库的时候,如何关联远程库。

现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。

首先,登陆GitHub,创建一个新的仓库,名字叫
gitskills


我们勾选
InitializethisrepositorywithaREADME
,这样GitHub会自动为我们创建一个
README.md
文件。创建完毕后,可以看到
README.md
文件:

现在,远程库已经准备好了,下一步是用命令
gitclone
克隆一个本地库:

$gitclonegit@github.com:michaelliao/gitskills.git

Cloninginto'gitskills'...

remote:Countingobjects:3,done.

remote:Total3(delta0),reused0(delta0)

Receivingobjects:100%(3/3),done.


$cdgitskills

$ls(
字母
L
s)

README.md



注意把Git库的地址换成你自己的,然后进入
gitskills
目录看看,已经有
README.md
文件了。

如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。

你也许还注意到,GitHub给出的地址不止一个,还可以用
https://github.com/michaelliao/gitskills.git
这样的地址。实际上,Git支持多种协议,默认的
git://
使用ssh,但也可以使用
https
等其他协议。

使用
https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用
ssh
协议而只能用
https


命令小结7

要克隆一个仓库,首先必须知道仓库的地址,然后使用
gitclone
命令克隆。

Git支持多种协议,包括
https
,但通过
ssh
支持的原生
git
协议速度最快。

分支管理

1791次阅读

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。

如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。

但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

创建与合并分支

3070次阅读

版本回退里,你已经知道,每次提交,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
分支:

真是太神奇了,你看得出来有些提交是通过分支完成的吗?

下面开始实战。

首先,我们创建
dev
分支,然后切换到
dev
分支:

$gitcheckout-bdev//
要注意当前打开的目录是什么,,
cd----

Switchedtoanewbranch'dev'

gitcheckout
命令加上
-b
参数表示创建并切换,相当于以下两条命令:

$gitbranchdev

$gitcheckoutdev

Switchedtobranch'dev'

然后,用
gitbranch
命令查看当前分支:

$gitbranch

*dev

master

gitbranch
命令会列出所有分支,当前分支前面会标一个
*
号。

然后,我们就可以在
dev
分支上正常提交,比如对readme.txt做个修改,加上一行:

Creatinganewbranchisquick.

然后提交:

$gitaddreadme.txt

$gitcommit-m"branchtest"

[devfec145a]branchtest

1filechanged,1insertion(+)

现在,
dev
分支的工作完成,

我们就可以切换回
master
分支:

$gitcheckoutmaster

Switchedtobranch'master'

切换回
master
分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在
dev
分支上,而
master
分支此刻的提交点并没有变:

现在,我们把
dev
分支的工作成果合并到
master
分支上:

$gitmergedev

Updatingd17efd8..fec145a

Fast-forward

readme.txt|1+

1filechanged,1insertion(+)

gitmerge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和
dev
分支的最新提交是完全一样的。

注意到上面的
Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把
master
指向
dev
的当前提交,所以合并速度非常快。

当然,也不是每次合并都能
Fast-forward
,我们后面会将其他方式的合并。

合并完成后,就可以放心地删除
dev
分支了:

$gitbranch-ddev

Deletedbranchdev(wasfec145a).

删除后,查看
branch
,就只剩下
master
分支了:

$gitbranch

*master

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在
master
分支上工作效果是一样的,但过程更安全。

命令小结8

Git鼓励大量使用分支:

查看分支:
gitbranch


创建分支:
gitbranch<name>


切换分支:
gitcheckout<name>


创建+切换分支:
gitcheckout-b<name>


合并某分支到当前分支:
gitmerge<name>


删除分支:
gitbranch-d<name>


解决冲突

1937次阅读

人生不如意之事十之八九,合并分支往往也不是一帆风顺的。

准备新的
feature1
分支,继续我们的新分支开发:

$gitcheckout-bfeature1

Switchedtoanewbranch'feature1'

修改readme.txt最后一行,改为:

CreatinganewbranchisquickANDsimple.

feature1
分支上提交:

$gitaddreadme.txt

$gitcommit-m"ANDsimple"

[feature175a857c]ANDsimple

1filechanged,1insertion(+),1deletion(-)

切换到
master
分支:

$gitcheckoutmaster

Switchedtobranch'master'

Yourbranchisaheadof'origin/master'by1commit.

Git还会自动提示我们当前
master
分支比远程的
master
分支要超前1个提交。

master
分支上把readme.txt文件的最后一行改为:

Creatinganewbranchisquick&simple.

提交:

$gitaddreadme.txt

$gitcommit-m"&simple"

[master400b400]&simple

1filechanged,1insertion(+),1deletion(-)

现在,
master
分支和
feature1
分支各自都分别有新的提交,变成了这样:

这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:

$gitmergefeature1

Auto-mergingreadme.txt

CONFLICT(content):
Mergeconflict
inreadme.txt

Automaticmergefailed;fixconflictsandthencommittheresult.

果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。
git
status
也可以告诉我们冲突的文件:

$gitstatus

#Onbranchmaster

#Yourbranchisaheadof'origin/master'by2commits.

#

#Unmergedpaths:

#(use"gitadd/rm<file>..."asappropriatetomarkresolution)

#

#bothmodified:readme.txt

#

nochangesaddedtocommit(use"gitadd"and/or"gitcommit-a")

我们可以直接查看readme.txt的内容:catreadme.txt

Gitisadistributedversioncontrolsystem.

GitisfreesoftwaredistributedundertheGPL.

Githasamutableindexcalledstage.

Gittrackschangesoffiles.

<<<<<<<HEAD

Creatinganewbranchisquick&simple.

=======

CreatinganewbranchisquickANDsimple.

>>>>>>>feature1

Git用
<<<<<<<
=======
>>>>>>>
标记出不同分支的内容,我们修改如下后保存:

Creatinganewbranchisquickandsimple.

再提交:

$gitaddreadme.txt

$gitcommit-m"conflictfixed"

[master59bc1cb]conflictfixed

现在,
master
分支和
feature1
分支变成了下图所示:

用带参数的
gitlog
也可以看到分支的合并情况:

$gitlog--graph--pretty=oneline--abbrev-commit

*59bc1cbconflictfixed

|\

|*75a857cANDsimple

*|400b400&simple

|/

*fec145abranchtest

...

现在,删除
feature1
分支:

$gitbranch-dfeature1

Deletedbranchfeature1(was75a857c).

工作完成。

命令小结9

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。

gitlog--graph
命令可以看到分支合并图。

$gitlog--graph--pretty=oneline--abbrev-commit




分支管理策略

1665次阅读

通常,合并分支时,如果可能,Git会用
Fastforward
模式(快进模式),但这种模式下,删除分支后,会丢掉分支信息。

如果要强制禁用
Fastforward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

下面我们实战一下
--no-ff
方式的
gitmerge


首先,仍然创建并切换
dev
分支:

$gitcheckout-bdev

Switchedtoanewbranch'dev'

修改readme.txt文件,并提交一个新的commit:

$gitaddreadme.txt

$gitcommit-m"addmerge"

[dev6224937]addmerge

1filechanged,1insertion(+)

现在,我们切换回
master


$gitcheckoutmaster

Switchedtobranch'master'

准备合并
dev
分支,请注意
--no-ff
参数,表示禁用
Fast
forward


$gitmerge--no-ff-m"mergewithno-ff"dev

Mergemadebythe'recursive'strategy.

readme.txt|1+

1filechanged,1insertion(+)

因为本次合并要创建一个新的commit,所以加上
-m
参数,把commit描述写进去。

合并后,我们用
gitlog
看看分支历史:

$gitlog--graph--pretty=oneline--abbrev-commit

*7825a50mergewithno-ff

|\

|*6224937addmerge

|/

*59bc1cbconflictfixed

...

可以看到,不使用
Fastforward
模式,merge后就像这样:

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,
master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在
dev
分支上,也就是说,
dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把
dev
分支合并到
master
上,在
master
分支发布1.0版本;

你和你的小伙伴们每个人都在
dev
分支上干活,每个人都有自己的分支,时不时地往
dev
分支上合并就可以了。

所以,团队合作的分支看起来就像这样:

小结10

Git分支十分强大,在团队开发中应该充分应用。

合并分支时,加上
--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而
fast
forward
合并就看不出来曾经做过合并。

Bug分支

1227次阅读

软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支
issue-101
来修复它,但是,等等,当前正在
dev
上进行的工作还没有提交:

$gitstatus

#Onbranchdev

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#newfile:hello.py

#

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

幸好,Git还提供了一个
stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

$gitstash

SavedworkingdirectoryandindexstateWIPondev:6224937addmerge

HEADisnowat6224937addmerge

现在,用
gitstatus
查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

1、首先确定要在哪个分支上修复bug,假定需要在
master
分支上修复,就从
master
创建临时分支:

$gitcheckoutmaster//
切换分支

Switchedtobranch'master'

Yourbranchisaheadof'origin/master'by6commits.

$gitcheckout-bissue-101//
创建并切换到
bug
分支

Switchedtoanewbranch'issue-101'

现在修复bug,需要把“Gitisfreesoftware...”改为“Git
isafreesoftware...”,然后提交:

$gitaddreadme.txt

$gitcommit-m"fixbug101"

[issue-101cc17032]fixbug101

1filechanged,1insertion(+),1deletion(-)

修复完成后,切换到
master
分支,并完成合并,最后删除
issue-101
分支:

$gitcheckoutmaster

Switchedtobranch'master'

Yourbranchisaheadof'origin/master'by2commits.

$gitmerge--no-ff-m"mergedbugfix101"issue-101

Mergemadebythe'recursive'strategy.

readme.txt|2+-

1filechanged,1insertion(+),1deletion(-)

$gitbranch-dissue-101

Deletedbranchissue-101(wascc17032).

太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到
dev
分支干活了!

$gitcheckoutdev

Switchedtobranch'dev'

$gitstatus

#Onbranchdev

nothingtocommit(workingdirectoryclean)

工作区是干净的,刚才的工作现场存到哪去了?用
gitstashlist
命令看看:

$gitstashlist

stash@{0}:WIPondev:6224937addmerge

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用
gitstashapply
恢复,但是恢复后,stash内容并不删除,你需要用
git
stashdrop
来删除;

另一种方式是用
gitstashpop
,恢复的同时把stash内容也删了:

$gitstashpop

#Onbranchdev

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#newfile:hello.py

#

#Changesnotstagedforcommit:

#(use"gitadd<file>..."toupdatewhatwillbecommitted)

#(use"gitcheckout--<file>..."todiscardchangesinworkingdirectory)

#

#modified:readme.txt

#

Droppedrefs/stash@{0}(f624f8e5f082f2df2bed8a4e09c12fd2943bdd40)

再用
gitstashlist
查看,就看不到任何stash内容了:

$gitstashlist

你可以多次stash,恢复的时候,先用
gitstashlist
查看,然后恢复指定的stash,用命令:

$gitstashapplystash@{0}

小结

修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;

当手头工作没有完成时,先把工作现场
gitstash
一下,然后去修复bug,修复后,再
git
stashpop
,回到工作现场。

(类似中断)

Feature分支

1330次阅读

软件开发中,总有无穷无尽的新的功能要不断添加进来。

添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。

现在,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。

于是准备开发:

$gitcheckout-bfeature-vulcan

Switchedtoanewbranch'feature-vulcan'

5分钟后,开发完毕:

$gitaddvulcan.py

$gitstatus

#Onbranchfeature-vulcan

#Changestobecommitted:

#(use"gitresetHEAD<file>..."tounstage)

#

#newfile:vulcan.py

#

$gitcommit-m"addfeaturevulcan"

[feature-vulcan756d4af]addfeaturevulcan

1filechanged,2insertions(+)

createmode100644vulcan.py

切回
dev
,准备合并:

$gitcheckoutdev

一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。

但是,

就在此时,接到上级命令,因经费不足,新功能必须取消!

虽然白干了,但是这个分支还是必须就地销毁:

$gitbranch-dfeature-vulcan

error:Thebranch'feature-vulcan'isnotfullymerged.

Ifyouaresureyouwanttodeleteit,run'gitbranch-Dfeature-vulcan'.

销毁失败。Git友情提醒,
feature-vulcan
分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令
git
branch-Dfeature-vulcan


现在我们强行删除:

$gitbranch-Dfeature-vulcan

Deletedbranchfeature-vulcan(was756d4af).

终于删除成功!

小结11

开发一个新feature,最好新建一个分支;

如果要丢弃一个没有被合并过的分支,可以通过
gitbranch-D<name>
强行删除。

多人协作

2152次阅读

当你从远程仓库克隆时,实际上Git自动把本地的
master
分支和远程的
master
分支对应起来了,并且,远程仓库的默认名称是
origin


要查看远程库的信息,用
gitremote


$gitremote

origin

或者,用
gitremote-v
显示更详细的信息:

$gitremote-v

origingit@github.com:michaelliao/learngit.git(fetch)

origingit@github.com:michaelliao/learngit.git(push)

上面显示了可以抓取和推送的
origin
的地址。如果没有推送权限,就看不到push的地址。

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

$gitpushoriginmaster

如果要推送其他分支,比如
dev
,就改成:

$gitpushorigindev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

·
master
分支是主分支,因此要时刻与远程同步;

·
dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;

·bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;

·feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!

抓取分支

多人协作时,大家都会往
master
dev
分支上推送各自的修改。

现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSHKey添加到GitHub)或者同一台电脑的另一个目录下克隆:

$gitclonegit@github.com:michaelliao/learngit.git

Cloninginto'learngit'...

remote:Countingobjects:46,done.

remote:Compressingobjects:100%(26/26),done.

remote:Total46(delta16),reused45(delta15)

Receivingobjects:100%(46/46),15.69KiB|6KiB/s,done.

Resolvingdeltas:100%(16/16),done.

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的
master
分支。不信可以用
git
branch
命令看看:

$gitbranch

*master

现在,你的小伙伴要在
dev
分支上开发,就必须创建远程
origin
dev
分支到本地,于是他用这个命令创建本地
dev
分支:

$gitcheckout-bdevorigin/dev

现在,他就可以在
dev
上继续修改,然后,时不时地把
dev
分支
push
到远程:

$gitcommit-m"add/usr/bin/env"

[dev291bea8]add/usr/bin/env

1filechanged,1insertion(+)

$gitpushorigindev

Countingobjects:5,done.

Deltacompressionusingupto4threads.

Compressingobjects:100%(2/2),done.

Writingobjects:100%(3/3),349bytes,done.

Total3(delta0),reused0(delta0)

Togit@github.com:michaelliao/learngit.git

fc38031..291bea8dev->dev

你的小伙伴已经向
origin/dev
分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

$gitaddhello.py

$gitcommit-m"addcoding:utf-8"

[devbd6ae48]addcoding:utf-8

1filechanged,1insertion(+)

$gitpushorigindev

Togit@github.com:michaelliao/learngit.git

![rejected]dev->dev(non-fast-forward)

error:failedtopushsomerefsto'git@github.com:michaelliao/learngit.git'

hint:Updateswererejectedbecausethetipofyourcurrentbranchisbehind

hint:itsremotecounterpart.Mergetheremotechanges(e.g.'gitpull')

hint:beforepushingagain.

hint:Seethe'Noteaboutfast-forwards'in'gitpush--help'fordetails.

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用
git
pull
把最新的提交从
origin/dev
抓下来,然后,在本地合并,解决冲突,再推送:

$gitpull

remote:Countingobjects:5,done.

remote:Compressingobjects:100%(2/2),done.

remote:Total3(delta0),reused3(delta0)

Unpackingobjects:100%(3/3),done.

Fromgithub.com:michaelliao/learngit

fc38031..291bea8dev->origin/dev

Thereisnotrackinginformationforthecurrentbranch.

Pleasespecifywhichbranchyouwanttomergewith.

Seegit-pull(1)fordetails


gitpull<remote><branch>


Ifyouwishtosettrackinginformationforthisbranchyoucandosowith:


gitbranch--set-upstreamdevorigin/<branch>

gitpull
也失败了,原因是没有指定本地
dev
分支与远程
origin/dev
分支的链接,根据提示,设置
dev
origin/dev
的链接:

$gitbranch--set-upstreamdevorigin/dev

Branchdevsetuptotrackremotebranchdevfromorigin.

再pull:

$gitpull

Auto-merginghello.py

CONFLICT(content):Mergeconflictinhello.py

Automaticmergefailed;fixconflictsandthencommittheresult.

这回
gitpull
成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

$gitcommit-m"merge&fixhello.py"

[devadca45d]merge&fixhello.py

$gitpushorigindev

Countingobjects:10,done.

Deltacompressionusingupto4threads.

Compressingobjects:100%(5/5),done.

Writingobjects:100%(6/6),747bytes,done.

Total6(delta0),reused0(delta0)

Togit@github.com:michaelliao/learngit.git

291bea8..adca45ddev->dev

因此,多人协作的工作模式通常是这样:

1.首先,可以试图用
gitpushoriginbranch-name
推送自己的修改;

2.如果推送失败,则因为远程分支比你的本地更新,需要先用
gitpull
试图合并;

3.如果合并有冲突,则解决冲突,并在本地提交;

4.没有冲突或者解决掉冲突后,再用
gitpushoriginbranch-name
推送就能成功!

如果
gitpull
提示“notrackinginformation”,则说明本地分支和远程分支的链接关系没有创建,用命令
git
branch--set-upstreambranch-nameorigin/branch-name


这就是多人协作的工作模式,一旦熟悉了,就非常简单。

小结12

·查看远程库信息,使用
gitremote-v


·本地新建的分支如果不推送到远程,对其他人就是不可见的;

·从本地推送分支,使用
gitpushoriginbranch-name
,如果推送失败,先用
git
pull
抓取远程的新提交;

·在本地创建和远程分支对应的分支,使用
gitcheckout-bbranch-nameorigin/branch-name
,本地和远程分支的名称最好一致;

·建立本地分支和远程分支的关联,使用
gitbranch--set-upstreambranch-nameorigin/branch-name


·从远程抓取分支,使用
gitpull
,如果有冲突,要先处理冲突。

标签管理

1025次阅读

发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。

Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

创建标签

1258次阅读

在Git中打标签非常简单,首先,切换到需要打标签的分支上:

$gitbranch

*dev

master

$gitcheckoutmaster

Switchedtobranch'master'

然后,敲命令
gittag<name>
就可以打一个新标签:

$gittagv1.0

可以用命令
gittag
查看所有标签:

$gittag

v1.0

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?

方法是找到历史提交的commitid,然后打上就可以了:

$gitlog--pretty=oneline--abbrev-commit

6a5819emergedbugfix101

cc17032fixbug101

7825a50mergewithno-ff

6224937addmerge

59bc1cbconflictfixed

400b400&simple

75a857cANDsimple

fec145abranchtest

d17efd8removetest.txt

...

比方说要对“addmerge”这次提交打标签,它对应的commitid是
6224937
,敲入命令:

$gittagv0.96224937

再用命令
gittag
查看标签:

$gittag

v0.9

v1.0

注意,标签不是按时间顺序列出,而是按字母排序的。可以用
gitshow<tagname>
查看标签信息:

$gitshowv0.9

commit622493706ab447b6bb37e4e2a2f276a20fed2ab4

Author:MichaelLiao<askxuefeng@gmail.com>

Date:ThuAug2211:22:082013+0800


addmerge

...

可以看到,
v0.9
确实打在“addmerge”这次提交上。

还可以创建带有说明的标签,用
-a
指定标签名,
-m
指定说明文字:

$gittag-av0.1-m"version0.1released"3628164

用命令
gitshow<tagname>
可以看到说明文字:

$gitshowv0.1

tagv0.1

Tagger:MichaelLiao<askxuefeng@gmail.com>

Date:MonAug2607:28:112013+0800


version0.1released


commit3628164fb26d48395383f8f31179f24e0882e1e0

Author:MichaelLiao<askxuefeng@gmail.com>

Date:TueAug2015:11:492013+0800


appendGPL

...

还可以通过
-s
用私钥签名一个标签:

$gittag-sv0.2-m"signedversion0.2released"fec145a

签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错:

gpg:signingfailed:secretkeynotavailable

error:gpgfailedtosignthedata

error:unabletosignthetag

如果报错,请参考GnuPG帮助文档配置Key。

用命令
gitshow<tagname>
可以看到PGP签名信息:

$gitshowv0.2

tagv0.2

Tagger:MichaelLiao<askxuefeng@gmail.com>

Date:MonAug2607:28:332013+0800


signedversion0.2released

-----BEGINPGPSIGNATURE-----

Version:GnuPGv1.4.12(Darwin)


iQEcBAABAgAGBQJSGpMhAAoJEPUxHyDAhBpT4QQIAKeHfR3bo...

-----ENDPGPSIGNATURE-----


commitfec145accd63cdc9ed95a2f557ea0658a2a6537f

Author:MichaelLiao<askxuefeng@gmail.com>

Date:ThuAug2210:37:302013+0800


branchtest

...

用PGP签名的标签是不可伪造的,因为可以验证PGP签名。验证签名的方法比较复杂,这里就不介绍了。

小结13

·命令
gittag<name>
用于新建一个标签,默认为
HEAD
,也可以指定一个commit id;

·
gittag-a<tagname>-m"blablabla..."
可以指定标签信息;

·
gittag-s<tagname>-m"blablabla..."
可以用PGP签名标签;

·命令
gittag
可以查看所有标签。

可以用
gitshow<tagname>
查看标签信息:
$gitshowv0.9




操作标签

856次阅读

如果标签打错了,也可以删除:

$gittag-dv0.1

Deletedtag'v0.1'(wase078af9)

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

如果要推送某个标签到远程,使用命令
gitpushorigin<tagname>


$gitpushoriginv1.0

Total0(delta0),reused0(delta0)

Togit@github.com:michaelliao/learngit.git

*[newtag]v1.0->v1.0

或者,一次性推送全部尚未推送到远程的本地标签:

$gitpushorigin--tags

Countingobjects:1,done.

Writingobjects:100%(1/1),554bytes,done.

Total1(delta0),reused0(delta0)

Togit@github.com:michaelliao/learngit.git

*[newtag]v0.2->v0.2

*[newtag]v0.9->v0.9

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

$gittag-dv0.9

Deletedtag'v0.9'(was6224937)

然后,从远程删除。删除命令也是push,但是格式如下:

$gitpushorigin:refs/tags/v0.9

Togit@github.com:michaelliao/learngit.git

-[deleted]v0.9

要看看是否真的从远程库删除了标签,可以登陆GitHub查看。

小结14

·命令
gitpushorigin<tagname>
可以推送一个本地标签;

·命令
gitpushorigin--tags
可以推送全部未推送过的本地标签;

·命令
gittag-d<tagname>
可以删除一个本地标签;

·命令
gitpushorigin:refs/tags/<tagname>
可以删除一个远程标签。

使用GitHub

2022次阅读

我们一直用GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。其实GitHub还是一个开源协作社区,通过GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。

在GitHub出现以前,开源项目开源容易,但让广大人民群众参与进来比较困难,因为要参与,就要提交代码,而给每个想提交代码的群众都开一个账号那是不现实的,因此,群众也仅限于报个bug,即使能改掉bug,也只能把diff文件用邮件发过去,很不方便。

但是在GitHub上,利用Git极其强大的克隆和分支功能,人们群众真正可以第一次自由参与各种开源项目了。

如何参与一个开源项目呢?比如人气极高的bootstrap项目(是Twitter推出的一个开源的用于前端开发的工具包,是一个CSS/HTML框架),这是一个非常强大的CSS框架,你可以访问它的项目主页https://github.com/twbs/bootstrap,点“Fork”就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone到本地:

gitclonegit@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。当然,对方是否接受你的pullrequest就不一定了。

如果你没能力修改bootstrap,但又想要试一把pullrequest,那就Fork一下我的仓库:https://github.com/michaelliao/learngit,创建一个
your-github-id.txt
的文本文件,写点自己学习Git的心得,然后推送一个pull
request给我,我会视心情而定是否接受。

小结

·在GitHub上,可以任意Fork开源仓库;

·自己拥有Fork后的仓库的读写权限;

·可以推送pullrequest给官方仓库来贡献代码。

自定义Git

1045次阅读

安装Git一节中,我们已经配置了
user.name
user.email
,实际上,Git还有很多可配置项。

比如,让Git显示颜色,会让命令输出看起来更醒目:

$gitconfig--globalcolor.uitrue

这样,Git会适当地显示不同的颜色,比如
gitstatus
命令:

文件名就会标上颜色。

我们在后面还会介绍如何更好地配置Git,以便让你的工作更高效。

忽略特殊文件

1085次阅读

有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次
git
status
都会显示“Untrackedfiles...”,有强迫症的童鞋心里肯定不爽。

好在Git考虑到了大家的感受,这个问题解决起来也很简单,在Git工作区的根目录下创建一个特殊的
.gitignore
文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

不需要从头写
.gitignore
文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore

忽略文件的原则是:

1.忽略操作系统自动生成的文件,比如缩略图等;
2.忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的
.class
文件;
3.忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
举个例子:

假设你在Windows下进行Python开发,Windows会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下就会有
Desktop.ini
文件,因此你需要忽略Windows自动生成的垃圾文件:

#Windows:

Thumbs.db

ehthumbs.db

Desktop.ini

然后,继续忽略Python编译产生的
.pyc
.pyo
dist
等文件或目录:

#Python:

*.py[cod]

*.so

*.egg

*.egg-info

dist

build

加上你自己定义的文件,最终得到一个完整的
.gitignore
文件,内容如下:

#Windows:

Thumbs.db

ehthumbs.db

Desktop.ini


#Python:

*.py[cod]

*.so

*.egg

*.egg-info

dist

build


#Myconfigurations:

db.ini

deploy_key_rsa

最后一步就是把
.gitignore
也提交到Git,就完成了!当然检验
.gitignore
的标准是
git
status
命令是不是说“workingdirectoryclean”。

使用Windows的童鞋注意了,如果你在资源管理器里新建一个
.gitignore
文件,它会非常弱智地提示你必须输入文件名,但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为
.gitignore
了。

小结

·忽略某些文件时,需要编写
.gitignore


·
.gitignore
文件本身要放到版本库里,并且可以对
.gitignore
做版本管理!

配置别名

938次阅读

有没有经常敲错命令?比如
gitstatus
status
这个单词真心不好记。

如果敲
gitst
就表示
gitstatus
那就简单多了,当然这种偷懒的办法我们是极力赞成的。

我们只需要敲一行命令,告诉Git,以后
st
就表示
status
:(alias别名)

$gitconfig--globalalias.ststatus

好了,现在敲
gitst
看看效果。

当然还有别的命令可以简写,很多人都用
co
表示
checkout
ci
表示
commit
br
表示
branch


$gitconfig--globalalias.cocheckout

$gitconfig--globalalias.cicommit

$gitconfig--globalalias.brbranch

以后提交就可以简写成:

$gitci-m"balabalabala..."

--global
参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。

撤销修改一节中,我们知道,命令
git
resetHEADfile
可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个
unstage
别名:

$gitconfig--globalalias.unstage'resetHEAD'

当你敲入命令:

$gitunstagetest.py

实际上Git执行的是:

$gitresetHEADtest.py

配置一个
gitlast
,让其显示最后一次提交信息:

$gitconfig--globalalias.last'log-1'

这样,用
gitlast
就能显示最近一次的提交:

$gitlast

commitadca45d317e6d8a4b23f9811c3d7b7f0f180bfe2

Merge:bd6ae48291bea8

Author:MichaelLiao<askxuefeng@gmail.com>

Date:ThuAug2222:49:222013+0800


merge&fixhello.py

甚至还有人丧心病狂地把
lg
配置成了:

gitconfig--globalalias.lg"log--color--graph--pretty=format:'%Cred%h%Creset-%C(yellow)%d%Creset%s%Cgreen(%cr)%C(boldblue)<%an>%Creset'--abbrev-commit"

来看看
gitlg
的效果:

为什么不早点告诉我?别激动,咱不是为了多记几个英文单词嘛!

配置文件

配置Git的时候,加上
--global
是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。

配置文件放哪了?每个仓库的Git配置文件都放在
.git/config
文件中:

$cat.git/config

[core]

repositoryformatversion=0

filemode=true

bare=false

logallrefupdates=true

ignorecase=true

precomposeunicode=true

[remote"origin"]

url=git@github.com:michaelliao/learngit.git

fetch=+refs/heads/*:refs/remotes/origin/*

[branch"master"]

remote=origin

merge=refs/heads/master

[alias]

last=log-1

别名就在
[alias]
后面,要删除别名,直接把对应的行删掉即可。

而当前用户的Git配置文件放在用户主目录下(C:\Users\Administrator)的一个隐藏文件
.gitconfig
中:

$cat.gitconfig

[alias]

co=checkout

ci=commit

br=branch

st=status

[user]

name=YourName

email=your@email.com

配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

小结

给Git配置好别名,就可以输入命令时偷个懒。我们鼓励偷懒。

搭建Git服务器

2572次阅读

远程仓库一节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。

GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。

搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的
apt
命令就可以完成安装。

假设你已经有
sudo
权限的用户账号,下面,正式开始安装。

第一步,安装
git


$sudoapt-getinstallgit

第二步,创建一个
git
用户,用来运行
git
服务:

$sudoaddusergit

第三步,创建证书登录:

收集所有需要登录的用户的公钥,就是他们自己的
id_rsa.pub
文件,把所有公钥导入到
/home/git/.ssh/authorized_keys
文件里,一行一个。

第四步,初始化Git仓库:

先选定一个目录作为Git仓库,假定是
/srv/sample.git
,在
/srv
目录下输入命令:

$sudogitinit--baresample.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以
.git
结尾。然后,把owner改为
git


$sudochown-Rgit:gitsample.git

第五步,禁用shell登录:

出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑
/etc/passwd
文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,
git
用户可以正常通过ssh使用git,但无法登录shell,因为我们为
git
用户指定的
git-shell
每次一登录就自动退出。

第六步,克隆远程仓库:

现在,可以通过
gitclone
命令克隆远程仓库了,在各自的电脑上运行:

$gitclonegit@server:/srv/sample.git

Cloninginto'sample'...

warning:Youappeartohaveclonedanemptyrepository.

剩下的推送就简单了。

管理公钥

如果团队很小,把每个人的公钥收集起来放到服务器的
/home/git/.ssh/authorized_keys
文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。

这里我们不介绍怎么玩Gitosis了,几百号人的团队基本都在500强了,相信找个高水平的Linux管理员问题不大。

管理权限

有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。

这里我们也不介绍Gitolite了,不要把有限的生命浪费到权限斗争中。

小结

·搭建Git服务器非常简单,通常10分钟即可完成;

·要方便管理公钥,用Gitosis;

·要像SVN那样变态地控制权限,用Gitolite。

期末总结

1034次阅读

终于到了期末总结的时刻了!

经过几天的学习,相信你对Git已经初步掌握。一开始,可能觉得Git上手比较困难,尤其是已经熟悉SVN的童鞋,没关系,多操练几次,就会越用越顺手。

Git虽然极其强大,命令繁多,但常用的就那么十来个,掌握好这十几个常用命令,你已经可以得心应手地使用Git了。

友情附赠国外网友制作的GitCheatSheet,建议打印出来备用:

GitCheatSheet

现在告诉你Git的官方网站:http://git-scm.com,英文自我感觉不错的童鞋,可以经常去官网看看。什么,打不开网站?相信我,我给出的绝对是官网地址,而且,Git官网决没有那么容易宕机,可能是你的人品问题,赶紧面壁思过,好好想想原因。

如果你学了Git后,工作效率大增,有更多的空闲时间健身看电影,那我的教学目标就达到了。

谢谢观看!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: