Git 项目管理使用指南
一. 基本原理
Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方法。 概念上来区分,其它大部分系统以文件变更列表的方式存储信息。 这类系统(CVS、Subversion、Perforce、Bazaar 等等)将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。
反之,Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个快照流。
Git 是一个分布式版本控制系统,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。在实际情况中,有一台服务器每天24小时开机,每个人都从这个服务器仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
二. git 配置
2.1 Git 配置文件
/etc/gitconfig
文件: 包含系统上每一个用户及他们仓库的通用配置。 如果使用带有--system
选项的git config
时,它会从此文件读写配置变量。~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。 可以传递--global
选项让 Git 读写此文件。- 当前使用仓库的
.git
目录中的config
文件(就是.git/config
):针对该仓库。
注意: 每一个级别的配置覆盖上一级别的配置,所以 .git/config
的配置变量会覆盖 /etc/gitconfig
、~/.gitconfig
或~/.config/git/config
中的配置变量。
2.2 Git 配置命令
git 的基本配置很简单,在配置时可以选择不同的配置级别,即加上不同的选项--global、--system
, 但通常我们都只为当前仓库配置,所以不加这两个选项。
$ git config user.name "Your Name"
$ git config user.email "email@yuchaoshui.com"
$ git config color.ui true
$ git config --global user.name "Your Name"
$ git config --global user.email "email@yuchaoshui.com"
$ git config --global color.ui true
三. 初始化git仓库
3.1 初始化提交版本库
操作系统通常都已经默认安装了git 命令,如果没有安装使用 yum、apt-get 安装即可。初始化一个git 仓库很简单,只需在一个目录下执行git init
即可,不用管目录是否有文件存在,但通常情况下我们都是初始化一个空的 git 仓库。
$ mkdir learngit
$ cd learngit
$ git init
$ cp /root/readme.txt ./
$ git status
$ git add *
$ git commit -m "add a readme file"
$ git status
上面初始化了一个git仓库,查看当前仓库的状态,并提交到了这个版本控制系统。git add
可以多次执行,git commit
可以一次提交很多文件,比如下面。
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
$ git status
3.2 Git 的三种状态
git 有三种状态,你的文件可能处于其中之一:已修改(modified)、已暂存(staged)、已提交(committed)。已提交表示数据已经安全的保存在本地数据库中。已修改表示修改了文件,但还没保存到数据库中。已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。分别对应 git 项目的三个工作区域的概念:Git 仓库(已提交)、工作目录(已修改)、暂存区域(已暂存)。
修改代码并更新的步骤一般就三个:工作目录修改文件、添加到暂存区、提交到版本库、push到服务器。 另外可能还会伴随着回退版本、创建分支、合并分支、处理合并冲突等操作,后续一一讲解。
3.3 删除版本库文件
删除版本库某个文件,重要的是删除后需要提交到版本库。
# 先操作系统级别删除然后删除版本库。
$ rm filename.txt
$ git rm filename.txt
$ git commit -m "removed file filename.txt"
# 或者直接删除并添加到暂存区然后提交。
$ git rm filename.txt
$ git commit -m "removed file filename.txt"
如果在工作空间误删了某个文件,可以用撤销工作区的方法恢复。
$ rm -f filename.txt
$ git checkout -- filename.txt
四. 回退到旧版本
- 回退到指定版本(过去某个版本、将来某个版本),如果版本ID已经找不到了(
git log
无法查看),可以通过git reflog
查看历史操作的命令。 第一列是某一次操作(提交、修改HEAD指向)后获得的commit id,后面部分表示操作的动作(提交、修改HEAD指向)。
$ git reset --hard 7d4658a247
- 退回到上一个版本, HEAD 表示当前版本。git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,git仅仅是把HEAD指向特定版本号而已。
$ git reset --hard HEAD^
# 或者
$ git reset --hard HEAD~1
# 退回到倒数第二个版本
$ git reset --hard HEAD~2
# 退回到倒数第三个版本
$ git reset --hard HEAD~3
五. 工作空间的修改撤销
当在工作空间修改了某个文件后,还未添加到暂存区 ,这时想撤销对该文件的修改可用下面方法。
$ git checkout -- readme.txt
这里的 --
的名称叫做double dash,是bash的内置命令,用来标记可选命令选项的结束。即在它后面的带 --
的字符串,不被当做是一个命令选项。命令git checkout -- readme.txt
意思就是,把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况:
- 一种是 readme.txt 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
- 一种是 readme.txt 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或 git add
时的状态。
六. 暂存区的撤销
撤销后恢复到工作空间。
$ git reset HEAD readme.txt
# 或者清空暂存区的所有add
$ git reset HEAD *
# 新版本git的是下面这样 , -r 表示递归文件夹
$ git rm --cached -r taokl
- 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git checkout -- filename
。 - 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令
git reset HEAD filename
,就回到了场景1,第二步按场景1操作。 - 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,可通过版本回退实现,不过前提是没有推送到远程库。
场景3有两种情况:
情况1: 当想撤销到暂存区时使用 --soft, git reset --soft HEAD^
情况2: 当想删掉这个commit,也不保留暂存区时使用 --hard, git reset --soft HEAD^
七. 关联远程仓库
这里使用 github 作为远程仓库,也可以使用coding、gitlab等远程仓库。现在的情况是:本地有现成的仓库,远程有一个空仓库,现在需要将这两个仓库的 master 分支关联起来。
7.1 客户端生成公钥
生成私钥公钥对后将公钥添加到github上面(公钥为~/.ssh/id_rsa.pub
文件)
$ ssh-keygen -t rsa -C "youremail@example.com"
# -C 表示注释的意思。
7.2 关联远程仓库
$ git remote add origin git@github.com:yuchaoshui/programgit.git
$ git push -u origin master
- origin 表示远程仓库名,master 表示 master 分支。
- git 会把本地的 master 分支内容推送的远程新的 master 分支。
- 加上了-u参数,表示把本地的 master 分支和远程的 master 分支关联起来。
7.3 提交到远程仓库
当首次提交使用 -u 参数以后就不用使用 -u 参数了。以后在本地提交的代码后只需要如下操作即可将本地的 master 分支推到远程仓库的 master 了。
$ git push origin master
查看远程库信息
$ git remote
$ git remote -v
八. 克隆远程仓库
现在的情况是:本地没有仓库,远程有一个有代码的仓库,现在需要将远程仓库 clone 到本地,然后修改代码,最后提交到远程仓库。
首先必须要添加 ssh 公钥,使用git clone
命令克隆。Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。现在远程仓库的地址是 taobao_scrapy.git 先有一个仓库
$ git clone git@github.com:yuchaoshui/taobao_scrapy.git
$ echo "readme file is done!" > readme.txt
$ git add readme.txt
$ git commit -m "append one line of readme.txt file"
$ git push origin master
如果推送到远程仓库的 dev 分支。
$ git push origin dev
删除远程仓库的某个分支。
$ git push origin :dev
九. 分支创建与合并
9.1 创建分支
一开始的时候只有master分支,在master分支上会有很多次的提交。这些多次提交的时间点就组成了一时间条线。Git 用 HEAD 指向当前分支的最新提交,使用 git branch
查看所有的分支,当前分支前有一个 *
号表示。
- 每次提交,当前分支都会向前移动一步,这样,随着你不断提交,分支的线也越来越长。
- 当我们在master分支上创建新的分支dev时,Git新建了一个指针叫dev,它指向master相同的提交。当切换到dev分支时,Git把HEAD指向dev,就表示当前分支在dev上了。从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变。
- 假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并,叫做快速合并。
- 合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下一条master分支了。
创建 dev 分支,然后切换到dev分支
$ git checkout -b dev
$ git branch
# -b 参数表示创建并切换到dev,相当于下面两条命令。
$ git branch dev
$ git checkout dev
$ git branch
然后,在 dev 分支上正常修改提交, 然后切换回 master 分支发现在 dev 分支修改的东西在 master 上面看不到,这就是分支的作用(不同分支的内容分开), 当 dev 分支修改得差不多了,就需要将 dev 分支和master 分支合并了。
$ git branch
$ echo "add a new feature" >> readme.txt
$ git add readme.txt
$ git commit -m "add a new feature"
9.2 合并分支
首先创建好 dev 分支并切换到dev、修改文件、提交到dev分支、然后切换回master, 必须在 master 上合并分支。
$ git checkout master
$ git merge dev
git merge
命令用于合并指定分支到当前分支。Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。 --no-ff 参数,表示禁止fast forward 模式。合并会产生一个commit, 所以加上 -m 参数。
合理的合并命令就是:
$ git merge --no-ff -m "merge with no-ff" dev
合并完成后,就可以放心地删除dev分支了。删除后,查看branch,就只剩下master分支了。
$ git branch -d dev
$ git branch
9.3 解决冲突
- 解决冲突的思路
将冲突的地方删除、或者将两个分支冲突的地方修改为相同的内容,然后在提交。提交成功后不需要再次合并了。可使用带参数的log查看提交合并情况。
$ git log --graph --pretty=oneline
- 创建新的feature1分支,并切换到该分支上。
$ git checkout -b feature1
修改 readme.txt 最后一行如下,然后在 feature1上提交。
Creating a new branch is quick AND simple.
$ git add readme.txt
$ git commit -m "modify file readme's last line AND"
- 然后切换到master上同样修改最后一行(和feature1同一行),然后add、然后提交。
Creating a new branch is quick & simple.
$ git add readme.txt
$ git commit -m "modify file readme's last line &"
现在的情况是:master 分支、 feature1分支对同一行进行了修改,并进行了提交,现在合并会出现问题,不能使用快速合并方式。会提示 Merge conflict 。必须手动解决冲突后再提交。 git status
也可以告诉我们冲突的文件。查看 readme.txt, 里面会有提示哪个地方有冲突。
- 尝试使用快速合并
将冲突的地方修改一致再次提交即可。
$ 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 commit -m "conflict fixed"
十. 分支策略
- 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
- 那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
- 你和你的小伙伴们每个人都在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的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像,但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
- 创建标签, 在给当前分支的最新提交打标签v0.01,并查看标签。
$ git checkout master
$ git tag v0.01
$ git tag
$ git show v0.01
- 根据 commit 的 id 打标签。
$ git tag v0.01 4a274087
- 给打的标签添加说明。
$ git tag -a v0.01 -m "version 0.01 released" 4a274087
- 删除本地标签,标签默认是不会被推送到远程仓库的,除非手动推送。
$ git tag -d v0.01
- 删除远程仓库标签。先删除本地标签,然后删除远程标签。
$ git tag -d v0.01
$ git push origin :refs/tags/v0.01
- 标签推送到远程仓库。
$ git push origin v0.01
- 或者,一次性推送全部尚未推送到远程的本地标签。
$ git push origin --tags
十二. rebase
$ 从 upstream 拉取最新代码,合并到本地当前分支,同时变基
git pull --rebase upstream master