最近使用Git时感到有些生疏,所以利用晚上空余时间将廖雪峰Git教程重新系统的学习一边并做了笔记,温故知新。
Git简介
Git是目前世界上最先进的分布式版本控制系统。
集中式vs分布式
CVS和SVN是集中式的版本控制系统,Git是分布式版本控制系统。
集中式:
版本库是集中存放在中央服务器的,在工作室都用自己的电脑,要先从中央服务器取得最新的版本,然后开始干活,干完活再把自己的或推送到中央服务器。集中式版本控制系统最大毛病是必须联网才能工作。
分布式:
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样你工作时就不须联网了,因为版本库就在你电脑中。既然每个人电脑上都有完整的版本库,那多人何如协作呢?比方你在电脑修改了文件A,你同事也在他电脑中修改了文件A,这时你们只需把各自修改推送给对方就可以互相看到对方修改了。
相比集中式,分布式安全性要更高些。Git优势不单是不必联网这么简单,Git极其强大的分支管理,把SVN远远抛在后面。
版本库版本管理
版本库(repository)又名仓库,可以简单理解为一个目录,这个目录里面所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,一遍任何时刻都能追踪历史,或者在将来某时刻可以还原。
- git init
通过git init
命令可以吧当前目标变成Git可以管理的仓库。瞬间Git就把仓库建好并告诉你这是个空的仓库,且目录下多一个.git目录,这个目录是Git来跟踪管理版本库的,没事不要手动修改这个目录的文件。
- git add 与git commit
在该仓库目录下先用命令git add readme.txt
告诉Git,把文件添加到仓库。执行上面命令,没有任何显示则表明成功。Unix的哲学就是“没有消息就是好消息”。第二步,用命令git commit
告诉Git,把文件提交到仓库。git commit -m "wrote a readme file"
,其中-m
后面输入的是本提交的说明,可以输入任意内容,方便在历史记录中查找改动。git commit
命令成功后会告诉你,1个文件被改动(新添加的readme文件),插入了两行内容。
为什么Git添加文件需要add
,commit
两步呢?
因为
commit
可以一次提交很多文件,所以你可以多次add
不同的文件。
- git status
当修改了文件后,运行git status
命令。
$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
可以让我们时刻掌握仓库当前的状态,命令告诉我们readme被修改过但还没准备提交修改。
- git diff
git diff可以查看文件具体修改了什么内容。输入git diff readme.txt
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
- git log
当commit
了数次版本后,可以使用git log
命令显示从最近到最远的提交日志。如果觉得输出信息太多,可以加上--pretty=oneline
参数。
- git reset
我们准备把readme文件会退到上一个版本,首先Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,上个版本就是HEAD^
,上上个版本就是HEAD^^
,往上100个版本可写成HEAD~100
。
输入git reset -- hard HEAD^
即可回到上个版本,此时用git log
再看版本库状态发现之前版本消失了。如果想再回去,只要顺着命令行串口向上找,找到那版本的commit id
然后再运行``git reset -- hard 95d933`就又可以回到未来的那版本了。
Git版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD
指针。HEAD
指向版本就是当前版本,因此Git允许我们在版本历史之间穿梭。穿梭前用git log
可以查看提交历史,以便确定要退回哪个版本。要重返未来,用git reflog
查看命令历史,以便确定要会带未来的哪个版本。
工作区和暂存区
工作区(Working Directory):就是你在电脑里能看到的目录。
版本库(Repository):工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。Git的版本库里存了很多东西,其中最重要的是stage(或叫index)的暂存区。还有Git为我们自动创建了第一个分支master
,以及指向master
的一个指针叫HEAD
。
前面讲吧文件往Git版本库里添加时分两步执行:git add
,实际就是把文件修改添加到暂存区。git commit
,实际上是把暂存区的所有内容提交到当前分支。因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以现在git commit
就是往这个分支上提交修改。
- git checkout
使用git checkout --file
可以把file文件在工作区的修改全部撤销,即让这个文件回到最近一次git add
或git commit
时的状态。这里必须在checkout
后写明文件,否则就变成“切换到另一个分支”的命令了。
如果想撤销掉已添加到暂存区的修改,用命令git reset HEAD file
可以把暂存区的修改撤销掉,重新放回工作区。git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。然后再按上面步骤撤销工作区。
- git rm
在Git中,删除也是一个修改操作。一般情况下会在终端调用命令rm file
删除文件,Git知道你删除了文件,因此工作区和版本库就不一致了。此时可以从版本库中删除这个文件,使用git rm
删掉并且git commit
,文件就从版本库中删除了。如果是删错了,因为版本库中还有,则使用git checkout --file
就可把文件恢复了。git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以还原。
远程仓库
Git是分布式版本控制系统,同一个Git仓库可以分布到不同的机器上。最早肯定是只有一台机器有原始版本库,伺候别的机器可以“克隆”这个原始版本库,而且每台机器的版本库都一样不分主次。实际情况常是找一台电脑充当服务器的角色,其他每个人都从这个“服务器”仓库克隆一份到自己电脑上并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
GitHub就是提供Git仓库托管服务的,只要注册一个GitHub账号就可以免费获得Git远程仓库。假设我们从零开发,最好的方式是先创建远程库,然后再从远程库克隆。
使用命令git clone
克隆一个本地库,如git clone [email protected]:orwater/cleargit.git
分支管理
在版本回退时我们已经知道,每次提交Git都办它们穿成一条时间线,这条时间线就是分支。截至目前只有一条时间线,在Git里这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始时,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点。每次提交,master
分支都会向前移动一部,这样随着不断提交,master
分支线也越来越长。
当我们创建新的分支,例如dev
时,Git新建一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上了。
从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新的提交一次后,dev
指针往前移动一步,而master
指针不变。
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上,最简单的方法是直接把master
指向dev
的当前提交完成合并。
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是删除dev
指针,最后就剩下一条主分支了。
-
创建分支
首先我们创建dev
分支,然后切换到上面,git checkout -b dev
,-b
参数表示创建并且切换。
然后可以用git branch
命令查看当前分支。
可以使用命令git checkout master
切换回主分支。
如果要把dev
分支的工作结果合并到master
上,使用git merge
命令用于合并指定分支到当前分支。git merge dev
合并完成后,就可以使用git branch -d dev
删除分支了。
-
解决冲突
创建个feature1
新分支,修改readme文件并提交。然后切换到主分支再对readme文件做不同的修改并提交。
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并可能会有冲突。
$ 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提示readme文件存在冲突,必须手动解决冲突后再提交。这时我们查看readme文件:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<
,=======
,>>>>>>>>
标记出不同分支的内容。这样进行修改后再提交就可以了。
-
分支合并策略
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下删除分支后会丢掉分支信息。如果要强制禁用Fast forward
模式,Git就会在merge
时生成一个新的commit
,这样从分支历史上就可以看出分支信息了,知道曾经做个合并。
使用--no-ff
参数的git merge
表示禁用Fast forward
模式。因为本次合并要创建一个新的commit
,所以加上-m
参数。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先。master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活。干活都在dev
分支上,其分支不稳定,到摸个时候再把分支合并到主分支上,在主分支发布版本。团队成员都在dev
上干活,每个人都有自己的分支,是不是往dev
分支上合并就可以了。
- git stash
软件开发中,修复bug时,在Git中由于分支是如此强大,所以每个bug都可以通过一个新的临时分支来修复,修复后合并分支再将临时分支删除。
如果正在工作时要修复一个bug,就要先创建一个临时分支来修复。但手头工作还没完成不能提交。这时应使用Git提供的stash
功能,可以把当前工作现场存储起来,等以后恢复现场后继续工作。
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
现在再用git status
查看工作区就是干净的了。
当修复好bug后再切换回dev
分支要恢复之前内容,有两个方法:
一种是用git stash apply
恢复,但恢复后,stash
内容并不删除,须再调用git stash drop
来删除。
另一种方法是用git stash pop
,恢复的同事把stash
内容也删除。再用git stash list
查看就看不到任何stash
内容了。你可以多次stash
,恢复时先用git stash list
查看,然后再恢复指定的stash
,git stash apply stash@{0}
。
-
多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master
分支和远程的master
分支对应起来了,并且远程仓库默认名称是origin
。要查看远程库的信息,用git remote
,添加-v
参数显示更详细信息。
$ git remote -v
origin [email protected]:michaelliao/learngit.git (fetch)
origin [email protected]:michaelliao/learngit.git (push)
上面显示了可以抓取和推送的origin
地址,如果没有推送权限就看不到push地址。
- git push
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要制定本地分支,Git就会把该分支推送到远程库对应的远程分支上。git push origin master
。
但是并不是一定要把本地分支往远程推送,那么哪些分支需要推送?
-
master
分支是主分支,因此要时刻与远程同步。 -
dev
分支是开发分支,团队所有成员都需要在上面工作,所以要与远程同步。 - bug分支只用于在本地修复bug,就没必要推到远程了。
总之Git中分支可以再本地自己藏着玩,是否推送视情况而定。
多人协作的工作模式通常是这样:
- 首先,可以试图用
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
。
标签管理
在发布一个版本时,通常先在版本库中打一个标签(tag),这样就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本就是把那个打标签时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签岁谈事版本库的快照,但其实他就是指向某个commit的指针,所以创建和删除标签都是瞬间完成的。相比于难记的commit id
,tag是一个让人容易记住的名字,它跟某个commit绑定在一起。
- git tag
使用命令git tag
可以打一个新标签,使用命令git tag
可查看所有标签。默认标签是打在最新提交的commit上的,如果想之前的commit打标签可以先找到是历史提交的commit id
,然后打命令git tag v0.9 6224937
即可。
最后附一张Git Cheat Sheet
Git分支最佳实践
- 主要分支:
中央仓库有两个长期的分支:
- master
- develop
master
用作生产分支,里面的代码是准备部署到生产环境的。develop
是可交付的开发代码,也可以看作用于集成分支,每晚构建从`develop获取代码。当
develop分支中的代码足够稳定时,就将改动合并到
master``分支,同时打上一个标签,标签名称为发布的版本号。
- 辅助分支
通过辅助分支来帮助并行开发,和主要分支不同,这些分支的生命周期是有限的:
- 特性分支
- 发布分支
- 紧急修复分支
特性分支:
特性分支可能从develop
分支分出,最终必须合并回develop
。特性分支用于开发新特性。每个新特性开一个新分支,最终会合并会develop
。特性分支只存在于开发者的仓库中。
创建新的特性分支:
git checkout -b myfeature develop
合并回develop
:
git checkout develop
git merge --no-ff myfeature
git branch -d myfeature
git push origin develop
使用--no-ff
确保总是新生成一个提交,避免丢失曾经存在一个特性分支的历史信息,也能方便地看出那些提交属于同一个特性。
发布分支:
发布分支可能从develop
分出,最终必须合并回develop
和master
。发布分支以release-*
的方式命名。
发布分支为新的发部分版本做准备,包括一些小bug修正和发布的元信息(版本号、发布日期等)的添加。这样develop
分支就可以接受针对以后的发布的新特征。
在代码基本可以发布的时候从develop
分支分出发布分支。这是要确保此次发布包括的特性都已经合并到develop
分支了。
创建发布分支:
git checkout -b release-1.2 develop
./bump-version.sh 1.2
git commit -a -m "Bumped version number to 1.2"
完成发布分支:
git checkout master
git merge --no-ff release-1.2
git tag -a 1.2
git checkout develop
git merge --no-ff release-1.2
紧急修复分支:
可能从master
分出,必须合并回develop
和master
。分支名以hotfix-*
开头。如果生产系统里面有个紧急bug要马上修复的话,我们就从master
里分出一个紧急修复分支。这样某人修复紧急bug的同时,团队其他成员可继续在develop
上开发。修复完bug之后,需要合并回master
,同时也需要合并回develop
。
参考文章 git分支最佳实践;