Git笔记及实践经验

最近使用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 <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." 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 addgit 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 git@github.com: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查看,然后再恢复指定的stashgit stash apply stash@{0}

  • 多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且远程仓库默认名称是origin。要查看远程库的信息,用git remote,添加-v参数显示更详细信息。

$ git remote -v
origin  git@github.com:michaelliao/learngit.git (fetch)
origin  git@github.com:michaelliao/learngit.git (push)

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

- git push

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要制定本地分支,Git就会把该分支推送到远程库对应的远程分支上。git push origin master

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

  • master分支是主分支,因此要时刻与远程同步。
  • dev分支是开发分支,团队所有成员都需要在上面工作,所以要与远程同步。
  • bug分支只用于在本地修复bug,就没必要推到远程了。

总之Git中分支可以再本地自己藏着玩,是否推送视情况而定。

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

  1. 首先,可以试图用git push origin branch -name推送自己的修改
  2. 如果推送失败,则因为远程分支比你本地更新,需要先用git pull试图合并
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或解决冲突后,再用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<name>可以打一个新标签,使用命令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分出,最终必须合并回developmaster。发布分支以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分出,必须合并回developmaster。分支名以hotfix-*开头。如果生产系统里面有个紧急bug要马上修复的话,我们就从master里分出一个紧急修复分支。这样某人修复紧急bug的同时,团队其他成员可继续在develop上开发。修复完bug之后,需要合并回master,同时也需要合并回develop

参考文章 git分支最佳实践;


推荐阅读更多精彩内容

  • 学习资料pro gitgit - 简明指南Github官方帮助文档Git Community Book 中文版参考...
    合肥懒皮阅读 9,311评论 1 16
  • 1. 安装 Github 查看是否安装git: $ git config --global user.name "...
    Albert_Sun阅读 7,939评论 7 155
  • 本系列为《Git权威指南》的读书笔记,分为两个部分:Part 1 涵盖了书中第 1~3 篇共 20 章的内容,Pa...
    yestyle阅读 6,701评论 0 50
  • 今天第一天开始上班,没有任务,于是开始学习Git这一程序猿必须掌握之技能,希望今天的积累过后,对与Git或者...
    CoderTung阅读 5,361评论 2 91
  • 有一个周五的早晨,领导安排去打印一份报告,然后下午的时候快递出去。接到任务,在正准备去打印的时候,突然想到快递每天...
    云书四方阅读 1,524评论 0 0