Git是目前世界上最先进的分布式版本控制系统,此文章简述了Git的基本原理、代码版本控制、GitHub联动(远程仓库)、分支管理以及标签管理等内容。
Git安装后需要通过以下命令配置用户名和邮箱,被用于远程仓库记录commits的相关信息,即用于查询哪次提交是谁完成的,配置的用户名和邮箱不会用于push代码到远程仓库时的身份验证。 --global
参数表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
1 | $ git config --global user.name "Your Name" |
基本原理
Git跟踪代码仓库(Repository)中每个文件的改动,并基于改动提交的时间线形成日志(Log),后续便可根据相应的版本号进行版本回滚等操作。
仓库建立
本地的代码仓库来源有两种:
- 通过
git init
命令将本地某个文件夹变成Git可以管理的仓库 - 通过
git clone
命令将某个远程仓库克隆至本地,生成项目同名文件夹
基于Git的版本库文件夹下,具有.git
子文件夹,这个文件夹是用来跟踪管理代码版本的
基础命令
上图展示了Git管理下本地的三个工作区,包含代码库(Repository)的文件夹可以理解成工作目录(Working directory),同时此文件夹下还具有一个暂存区(Stage)。当文件夹内出现新文件时,此文件会被标记为Untracked,表明此文件未被Git跟踪,即此文件未被包含至代码库内。对于本身就包含于代码库的文件,在其未修改时是Unmodify状态,修改后为Modified的状态。如果我们希望Git记录修改以及新增的内容,就可以先通过git add [filename]
命令将其加入暂存区,此时改动文件被标记为Staged
的状态。随后通过git commit -m "message"
命令将改动内容提交到代码库,-m
参数是添加此提交的注释信息。每次commit对应一个十六进制的commit id
(版本号)。
常用的命令还包括以下几条:
git status
查看当前目录下文件状态git log
查看commit历史的时间线,版本回滚会导致时间线也回溯git reflog
查看所有的命令,可以用来查询发生于回滚版本后的commit id
代码版本控制
版本回滚或回调
所谓代码版本控制,即将代码库的版本根据需求变成对应commit id
的版本,即对代码库版本进行回滚或者回调。下面是版本控制的常用命令,HEAD
是当前分支当前版本的指针。
git reset --hard HEAD^
将当前版本回滚至上一个版本,^
的数量表示回滚的数量,如果回滚的版本数太多,可以写成HEAD~100
git reset --hard id
将代码库改动至指定版本,版本号可以通过上述的git log
和git reflog
查询
单文件回滚
上面叙述了如何在不同版本的代码库中进行切换,实际使用中还会涉及到单个文件的回滚,即撤销对某个文件的修改。下面分三种情况讨论如何撤销单个文件的修改:
- 修改的文件处于Modified的状态,即还未将修改加入暂存区,使用
git checkout -- [filename]
命令撤销修改,此时文件变成和版本库一样,此命令本质是将版本库中记录的文件替换工作区中的对应文件。 - 修改的文件处于Staged的状态,即修改已经放入了暂存区,使用
git reset HEAD [filename]
将暂存区的修改退回工作区,然后使用git checkout -- [filename]
丢弃工作区的修改。 - 如果文件的修改commit到了本地代码库,形成了新的代码版本,可以对版本进行回滚。
文件删除
在工作目录使用文件管理器删除后,如果希望将代码库中对应的文件也删除,可以先通过 git rm [filename]
删除,然后git commit
提交;如果文件是误删,可以通过git checkout -- [filename]
将文件恢复
GitHub联动
分布式版本控制系统Git中的分布式意味着项目中的所有开发人员都具有完整的版本库,但是通常需要某台中央服务器充当代码交换的作用,在其中建立远程代码库。本文以GitHub为例讲述本地仓库与远程仓库的联动,其他平台也是同理。既然本地仓库要与远程仓库联动,那么就需要在建立远程仓库与本地仓库的链接。通俗来说,当我们使用git push
命令时,本地仓库需要知道push到哪个远程仓库。
- 如果本地仓库是通过
git init
创建的,可以通过git remote add origin git@github.com:username/reponame.git
命令将本地仓库与远程新建空仓库建立链接。 远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的 。随后使用git push -u origin master
将本地的master分支推送到远程。-u
参数,会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令 。解绑链接通过git remote rm origin
命令。 - 如果通过
git clone
创建的本地仓库,则本地仓库与远程仓库之间的链接是自动建立的。
建立连接后,可以通过git remote -v
查看远程仓库的信息。
参与开源项目
通常上述的push操作只能推送修改到自己的账号下,也就是将本地生成的SSH公钥添加至GitHub账号中。而通常的开源项目,核心外的开发人员通常不具备推送权限,因此可以通过以下流程参与开源项目:
- Fork别人的开源项目,就在自己的账号下克隆了一个同样的仓库
- 从自己的账号下的项目仓库克隆到本地
- 本地修改,推送到自己的仓库
- 发起Pull Request(PR)
- 等待对方接收合并或者拒绝
分支管理
类似于平行宇宙,建立新分支意味着我们从某次commit后开启另一条提交时间线,此时间线与原始的提交时间线中的修改互不干扰。开发中新分支具体用途是用来测试不稳定的feature或者修改原始分支中的Bug。前文提到HEAD指向master分支,master指向当前版本的代码库。
创立新分支本质上是创立了一个新的分支指针指向当前版本的代码库,而切换分支是更改HEAD指向,因此每次改动的commit都会提交到HEAD所指的当前分支中。常用命令如下:
git checkout -b dev
创建dev
分支并且切换到dev
分支git branch dev
创建dev
分支git checkout dev
切换到dev
分支git branch
查看当前分支git merge dev
把dev
分支的工作成果合并到master
分支上 ,默认Fast forward
模式合并git branch -d dev
删除dev
分支
值得注意的是,分支合并时,如果不同分支中的同一文件的修改不同,那么合并会发生冲突(Merge conflict in filename)。冲突的修改方式为将不同分支中冲突的文件内容修改一致,然后再通过git add
和git commit
合并。
默认合并模式下, 删除分支后,会丢掉分支信息(失去合并历史) 。如果需要保留分支合并前的信息,可以禁用Fast forward
模式,即使用git merge --no-ff -m "merge with no-ff" dev
合并,会对分支合并前形成快照,即可以通过git log --graph --pretty=oneline --abbrev-commit
命令以图的形式查看到合并历史。
多人开发
在实际开发中,远程库通常具有dev
和master
两个分支。日常的新功能尝试都会在dev
分支中进行,而master
分支通常用来发布稳定的新版本,发布时将dev
合并至master
分支。每位开发员都可以拥有自己的独立开发分支, 时不时地往dev
分支上合并就可以了 。
通常开发中远程仓库的分支管理涉及到以下问题:
- 基于
master
发布的版本存在Bug,需要紧急修复,但自己目前在其他分支上的工作还没有提交。可以通过git stash
将当前的工作现场保存,后续恢复现场继续工作。git stash list
查看所有保存的工作现场git stash pop
恢复现场、同时把stash内容删除git stash apply
恢复 ,git stash drop
删除 stash内容- 如果修复的Bug在其他分支上也存在,可以通过
git cherry-pick id
复制一个特定的提交到当前分支
- 某个分支被开发出来后,还未被合并就要被删除,需要通过
git branch -D name
强行删除。 git push origin name
推送远程仓库时,需要指定本地分支名字, 这样,Git就会把该分支推送到远程库对应的远程分支上 。- 从远程仓库克隆时,只能看到
master
分支, 要在dev
分支上开发,就必须创建远程origin
的dev
分支到本地git checkout -b dev origin/dev
。 - 如果
git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,git branch --set-upstream-to=origin/dev dev
指定本地dev
分支与远程origin/dev
分支的链接 。 - 如果向远程仓库推送发生了冲突,即本地修改的文件与远程仓库中对应文件的修改存在冲突,可以 先用
git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送 。本质上是将本地的代码库进行更新,然后合并本地的修改。 git pull
本质是更新本地代码库,在提交历史上会形成分叉,看起来类似分支合并。使用git rebase
把分叉的提交历史“整理”成一条直线,看上去更直观 。
标签管理
标签管理就是对十六进制的commit id
添加一个别名,具体操作如下:
- 切换到需要打标签的分支
git tag [name]
就可以打一个新标签
值得注意的是标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。 另外一些常用命令如下:
git tag
查看所有标签,标签展示按照字母排序git tag <tagname>
id对指定的版本打标签,也可以使用
-m`参数添加信息git show <tagname>
可以查看标签的具体说明git tag -d <tagname>
删除某标签git push origin <tagname>
推送标签到远程git push origin --tags
一次性推送全部尚未推送到远程的本地标签git tag -d <tagname>
结合git push origin :refs/tags/<tagname>
删除远程标签
参考文献
[1] 廖雪峰,Git教程,https://www.liaoxuefeng.com/wiki/896043488029600
[2] Scott Chacon,Pro Git,http://iissnan.com/progit/