前言
最近想深入了解一下Git,阅读了一下Pro Git的前两章,前两章几乎涵盖了Git的大部分功能,在此做个记录,以便后续查阅
Git介绍
Git实际上是一个版本控制系统,本质上,版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
版本控制经历了本地版本控制系统(RCS,我没听过),集中化版本控制系统(CVS,Subversion等)和分布式版本控制系统(Git等)的演变。
本地版本控制系统无法实现协同工作,集中化的版本控制系统解决了协同工作的问题,但中央服务器的单点故障会影响所有人的工作,分布式版本控制系统采用快照的方式,每个开发者电脑上都有一份代码的完整拷贝,故障恢复起来就比较容易。
Git是Linux开源社区的杰作,他们开发Git之初就对其有以下目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
- 完全的分布式
- 有能力高效管理类似Linux内核一样的超大规模项目(速度和数据量)
Git和其他版本控制系统(如Subversion)的主要差别在于对待数据的方法。不像其他版本控制系统保存基本文件和基于基本文件的差异,Git直接保存每个文件的快照。


正因为如此,Git中的大多数操作只需要访问本地资源,而不需要与服务器进行通信,保证了操作的速度。
Git基础
Git的三种状态
在Git中,你的文件处于以下三种状态之一:
- 已提交(committed),表示数据已经安全的保存在本地数据库中。
- 已修改(modified),表示已经修改了文件,但还没保存在数据库中。
- 已暂存(staged),表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
由此引入Git项目的三个工作区的概念:Git仓库,工作目录以及暂存区域。

基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
Git常用命令
Git帮助
有三种方式获取Git的帮助:1
2
3git help <verb>
git <verb> --help
man git-<verb>
Git配置
Git的配置信息存放在三个不同的位置
- /etc/gitconfig文件,系统上每一个用户及他们仓库的配置,使用–system选项会读写此配置文件
- ~/.gitconfig 或 ~/.config/git/config 文件:只针对当前用户。使用–global选项会读写此配置文件
- 当前仓库中使用的配置(.git/config),需要在当前仓库进行修改,不需要加选项。
每一个级别的配置会覆盖上一个级别的配置。
以下是常用的配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 查看用户配置
git config --global -l
// 设置全局的用户名和邮箱
git config --global user.name "gwq5210"
git config --global user.email "gwq5210@qq.com"
// 设置使用的编辑器
git config --global core.editor "vim"
// 设置常用的别名,以下命令会在稍候介绍
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.st status
git config --global alias.unstage reset HEAD --
git config --global alias.last log -l HEAD
// 为Git设置代理
git config --global http.http http://127.0.0.1:8080
git config --global http.https http://127.0.0.1:8080
// 为不同的网址设置不同的代理
git config --global http.https://github.com.proxy http://127.0.0.1:8081
git config --global https.https://github.com.proxy http://127.0.0.1:8081
// 避免每次都输入账号密码,将账号密码保存在内存中几分钟
git config --global credential.helper cache
获取Git仓库
有两种方式获取Git仓库:从现有目录初始化仓库和从服务器克隆一个现有的Git仓库。1
2
3
4
5// 从现有目录初始化仓库
git init
// 从服务器克隆一个现有的仓库
git clone [url] [name]
Git仓库里存在着.git目录,保存着Git仓库的必要信息。
查看当前文件状态、跟踪新文件和暂存已修改的文件
工作目录的文件状态不外乎:已跟踪和未跟踪。已跟踪是已纳入版本控制的文件,在上一次快照中有他们的记录,工作过一段时候后,他们的状态可能是未修改,已修改,已暂存状态。除此之外其他文件都属于未跟踪文件
1 | // 查看当前文件状态 |
我们可以使用.gitignore文件来忽略我们不想跟踪的文件,这里有.gitignore文件的列表,可供查阅https://github.com/github/gitignore
查看已暂存和未暂存的修改
1 | // 查看未暂存的修改,即工作区域和已暂存区域的修改 |
提交更新
使用如下命令将暂存区域的内容提交1
2
3
4
5
6
7git commit
// 命令行指定提交消息
git commit -m "commit message"
// 跳过暂存,进行提交
git commit -a -m "commit message"
// commit的别名
git ci
移除或修改文件
要移除文件,需要从已跟踪清单中(确切的说是从暂存区域)移除,然后提交。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21git rm [path]
-------------
// git rm类似如下命令
rm [path]
git add [path]
-------------
// 从暂存区域移除,仍然保留在工作区域
git rm --cached [path]
// 如果已经修改了文件或者已经暂存了该文件,则需要使用-f进行删除,防止误操作删除了不可恢复的文件
git rm -f [path]
// 重命名文件
git mv file_from file_to
-------------
// git mv相当于下边的操作
mv file_from file_to
git rm file_from
git add file_to
-------------
查看提交历史
提交若干更新或者克隆一个仓库之后,我们可以查看提交历史1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 默认会列出所有的提交历史,最近的更新会排在最上边
git log
// 显示最新的n条记录
git log -n
// 显示每次提交的内容差异
git log -p
// 查看每次提交的简略统计信息
git log --stat
// 单行显示提交历史
git log --oneline
// 使用图形提交历史
git log --graph
撤销操作
1 | // 修改最后一次提交,使用这个选项会覆盖上一次的提交 |
使用远程仓库
克隆一个仓库,会自动给远程仓库添加origin的名称1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 显示远程仓库
git remote -v
git remote show <remote-name>
// 添加远程仓库
git remote add <shortname> <url>
// 拉取远程仓库的信息,不会自动合并或修改你当前的工作
// 分支设置了跟踪一个远程分支的话git pull会自动抓取然后合并
git fetch <remote-name>
// 推送到远程仓库
git push <remote-name> <branch-name>
// 远程仓库重命名和移除
git remote rename <old-name> <new-name>
git remote rm <remote-name>
使用标签
Git可以给历史中的某一个提交打上标签,表示重要
Git标签有两种标签,轻量标签和附注标签
轻量标签很像一个不会改变的分支,它只是一个特定提交的引用
附注标签是存储在Git数据库中的完整对象,可以包含打标签者的名字,邮件,日期等信息
通常建议创建附注标签1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// 列出标签
git tag
git tag -l "v1.8*"
// 创建附注标签,指定存储在标签中的信息
git tag -a v1.0 -m "create tag v1.0"
// 查看标签的信息和对应的提交信息,轻量标签只会显示提交信息
git show v1.0
// 创建轻量标签,只需要提供标签名字
git tag v1.0-lw
// 对过去的提交打标签
git tag -a v0.9 9fceb02
// 共享标签,将标签推送到远程仓库,类似共享远程分支
git push <remote-name> <tag-name>
// 将所有不在远程服务器的标签都推送过去
git push <remote-name> --tags
// 删除标签,此命令不会删除远程服务器的标签
git tag -d v1.0
// 删除远程服务器的标签
git push <remote-name> :refs<tag-name>
git push <remote-name> --delete <tag-name>
// 检出标签,此命令会使你的仓库处于分离头指针(detached HEAD)状态
// 在分离头指针状态你进行某些更改然后提交他们,标签不会发生变化,你的提交不属于任何分支,只能使用确切的哈希来访问
git checkout <tag-name>
git co <tag-name>
// 因此,需要修复旧版本的错误,通常需要创建一个新的分支,在新分支上进行修改,并提交,该分支就和此标签不同了
git co -b <branch-name> <tag-name>
Git分支
几乎所有的版本控制系统都支持分支,你可以把工作从开发主线上分离出来,以免影响开发主线
Git处理分支的方式十分轻量,创建和切换分支操作都十分便捷,因此Git鼓励使用分支
进行提交操作时,Git会保存一个提交对象,该提交对象包含一个指向暂存内容快照的指针,还包括作者的姓名,邮箱等信息,以及它的父对象信息(一个或多个)
Git的默认分支是master分支,master分支不是一个特殊的分支,与其他分支没有差别,git init命令会默认创建它
这些分支操作全部都保存在本地,没有与服务器发生交互1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 创建分支,会在当前所的提交对象上创建一个指针,Git有一个HEAD的特殊指针,指向当前分支,可以理解为当前分支的别名
git branch <branch-name>
git br <branch-name>
// 切换分支,分支切换会改变工作目录的内容,如果Git不能干净利落的完成这个任务,它将禁止切换分支
git checkout <branch-name>
git co <branch-name>
// 新建分支并切换
git checkout -b <branch-name>
git co -b <branch-name>
// 合并分支,将<branch-name>分支合并到当前分支
// 合并两个分支时,如果顺着一个分支能够到达另外一个分支,那么就进行快进(fast-forward)
// 有分叉的合并会产生一个合并提交,该提交使用三方(两个分支末端的快照和它们的公共祖先)合并的结果
// 如果合并时遇到对同一个文件同一处的修改,则会发生冲突,等你解决冲突后,使用git add命令将冲突的文件标记为已解决冲突
git merge <branch-name>
// 删除分支
git branch -d <branch-name>
git br -d <branch-name>
// 查看分支,*号代表当前所在分支
git branch
git br
// 查看分支最后一个提交
git branch -v
git br -v
// 显示列表中已经合并或尚未合并到当前分支的分支
git branch --merged
git br --no-merged

远程分支
远程引用是对远程仓库的引用,包括分支标签等。
我们经常利用远程跟踪分支,它是远程分支状态的引用,它们是你不能移动的本地引用,当你进行任何网络操作时,它们会自动移动
远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签,以
origin和master没有特殊含义,origin是git clone创建的远程仓库的默认名字,master是git init时默认的起始分支的名字1
2
3// 查看完整远程引用列表
git ls-remote
git remote show <remote-name>

推送到远程分支
你想分享一个分支,必须要将其推送到有写入权限的远程仓库,本地的分支不会自动与远程仓库同步,必须显式的推送1
2
3
4
5
6
7
8
9
10
11// 将分支推送到远程仓库
git push <remote-name> <branch-name>
// 本地分支与远程分支采用不同的名字
git push <remote-name> <branch-name>:<remote-branch-name>
// 其他人可以获取到远程分支
git fetch <remote-name>
// 合并远程分支到当前分支
git merge <remote-name>/<branch-name>
// 本地创建新的分支,起点为远程分支,该分支跟踪远程分支
git co -b <branch-name> <remote-name>/<branch-name>
跟踪分支
从一个远程仓库检出一个本地分支会自动创建一个做跟踪分支(上游分支),跟踪分支是与远程分支有直接关系的本地分支
如果在跟踪分支上输入git pull,则Git能自动识别去哪个服务器抓取、合并到哪个分支
克隆仓库时,会自动创建一个跟踪origin/master的master分支1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 本地创建新的分支,起点为远程分支,该分支跟踪远程分支
git checkout -b <branch-name> <remote-name>/<branch-name>
git checkout --track <remote-name>/<branch-name>
// 当前分支设置跟踪的远程分支
git branch -u <remote-name>/<branch-name>
git branch --set-upstream-to <remote-name>/<branch-name>
// 设置好跟踪分支后,可以通过@{u}或@{upstream}快捷方式来引用它
git merge origin/master
git merge @{u}
// 查看分支跟踪的远程分支
git branch -vv
// 获取远程分支,并不会修改本地工作目录中的内容,需要自己合并
git fetch origin master
git merge origin/master
// git pull会查找当前分支的跟踪分支,从服务器抓取数据然后尝试合并入那个远程分支
git pull
// 删除远程分支
git push <remote-name> --delete <branch-name>
变基
Git中整合来自不同分支的修改主要有两种方法:merge和rebase
合并进行一次三方合并,产生一个新的快照并提交
变基首先找到两个分支的共同祖先,对比当前分支的历次提交,提取相应的修改,然后在新的分支上进行重新播放,最后进行一次快速合并
一般进行变基的目的是确保向远程分支推送时,保持提交历史的整洁
这两种方式整合的最终快照是一样的
1 | // 将当前分支变基到master分支 |


变基存在风险,使用变基的准则是不要对在你的仓库外有副本的分支执行变基
变基的操作实际上是丢弃一些现有的提交,然后相应的新建一些内容一样的但是实际上不同的提交
如果别人在该分支上进行了工作,你对此分支进行了变基,那么就会出现问题
出现类似的情况可以使用变基来解决变基1
git pull --rebase
总结
以上的命令和知识可以应付大部分Git的使用场景了
如果遇到其他的困难问题,可以后续再补充
参考
1) Pro Git