引子
如果要写一篇论文,会经历初稿、修改版、再次修改版……
如果要设计一张海报,海报初版0510、修改版0511、修改版0512……
史前文明
复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。
一、关于版本控制
版本控制系统(VCS, Version control System),版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态。
VCS初代—本地版本控制系统
采用某种简单的数据库来记录文件的历次更新差异。这代主要的特点提供本地代码版本控制,比如SCCS(1972)、RCS(1982)等。这代主要实现了基本的代码版本管理,但缺点是无法让多人同时对一个版本库进行修改。
VCS二代—集中式版本控制系统
集中式的版本控制系统( Centralized Version Control Systems,简称 CVCS ),客户端-服务器式,主要的特点是提供集中式服务器端代码版本控制,比如 CVS(1990), SVN(2000) 等。主要解决的问题是让在不同系统上的开发者协同工作,可以让多人同时对一个代码版本库进行同步和修改。
缺点:无法连接服务器的情况,无法查看日志以及提交和比较代码版本。不支持local branch,管理branch比较复杂。发生灾难性问题,日志有丢失的危险。代码量过大,速度比较缓慢。
VCS三代—分布式版本控制系统
分布式版本控制系统( Distributed Version Control System,简称 DVCS ),主要的特点是提供分布式代码版本控制,比如BitKeeper(2000), Git(2005), Mercurial(2005)等。结合了第一代和第二代的优点并实现了分布式的代码版本管理。在没有和服务器有连接的情况下仍然可以查看日志,提交代码,创建分支;支持local branch,可以快速方便的实现各种分支管理……
二、一枝独秀-Git
刀耕火种
1991-2002 — Linux内核维护所使用的工具:diff和patch。
欢乐时光
在2002年至2005年期间,Linux内核的代码管理工具是商业版本控制系统BitKeeper。
艰难时刻
2005年发生的一件事最终导致Git的诞生。在2005年4月,Andrew Tridgell(即大名鼎鼎的Samba的作者)试图对BitKeeper进行反向工程,以开发一个能与BitKeeper交互的开源工具。这激怒了BitKeeper软件的所有者BitKeeper公司,要求收回对Linux社区免费使用BitKeeper的授权。
Git诞生
迫不得已,Linus选择了自己开发一个分布式版本控制工具以替代BitKeeper。十天之后,Git诞生。“git”,该词源自英国俚语,意思大约是“混账”。林纳斯·托瓦兹自嘲地取了这个名字。
Git最早是根据Monotone(一个开放源码的分散式版本控制软件工具。C++写的)改写的。
GitHub助攻
2008年,GitHub平台(全球最大同性交友网站)正式上线,通过Git进行版本控制的软件源代码托管服务。截止到2015年,GitHub已经有超过两千八百万注册用户和7900万代码库。事实上已经成为了世界上最大的代码存放网站和开源社区。2018年6月4日晚上,美国科技公司微软宣布以75亿美元的股票收购GitHub。
一枝独秀
截至 2018 年,全球知名的 IT 技术问答网站 Stack Overflow 调查的 74,298 开发人员中,87.2% 的人表示更喜欢使用 Git 进行版本控制。(用SVN的为16.1%)
花絮:2016年5月11日,BitKeeper宣布以Apache 2.0许可证开源。托管在了GitHub上。
三、Git特点
1.直接记录快照,而非差异比较。
- Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交 更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。
2.近乎所有操作都是本地执行。
- 在本地磁盘上就有项目的完整历史。
3.Git 保证完整性。
- Git 中所有数据在存储前都计算校验和,然后以校验和(由 40 个十六进制字符组成字符串)来引用。
4.Git 一般只添加数据
- 一旦你提交快照到 Git 中,就不会丢失数据。
四、安装&配置
安装
1.Windows安装
官网下载:https://git-scm.com/download/win
2.在 Mac 上安装
1.最简单的方法是安装 Xcode Command Line Tools。在 Terminal 里尝试首次运行 git 命令即可。 如果没有安装过命令行开发者工具,将会提示你安装。
2.官网下载:https://git-scm.com/download/mac
3.使用homebrew:https://brew.sh/
$ brew install git
配置
初次运行 Git 前的配置
Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置:
-
/etc/gitconfig 文件
: 包含系统上每一个用户及他们仓库的通用配置。 如果使用带有—system
选项的 git config 时,它会从此文件读写配置变量。 -
~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。 可以传递—global
选项让 Git 读写此文件。 - 前使用仓库的 Git 目录中的 config 文件(就是
.git/config
):针对该仓库。
每一个级别覆盖上一级别的配置。
用户信息
设置你的用户名称与邮件地址,它会写入到你的每一次提交中。
$ git config --global user.name "hony"
$ git config --global user.email hony@163.com
检查配置信息
$ git config --list
user.name=hony
user.email=hony@163.com
...
列出系统中指定的配置信息
$ git config --system -l
$ git config --global -l
$ git config --local -l
检查某一项的配置
$ git config user.name
hony
五、Git目录结构
Git 有三种状态:已提交(committed)、已修改(modified)和已暂存(staged)。
已提交表示数据已经安全的保存在本地数据库中。
已修改表示修改了文件,但还没保存到数据库中。
已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
由此引入 Git 项目的三个工作区域的概念:工作目录、暂存区域(index)、Git 仓库(HEAD)。
工作目录是对项目的某个版本独立提取出来的内容,它持有实际文件。
暂存区域,它像个缓存区域,临时保存你的改动。
Git 仓库目录,是 Git 用来保存项目的元数据和对象数据库的地方。指向最近一次提交后的结果。
基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
六、基础命令
6.1获取 Git 仓库
有两种取得 Git 项目仓库的方法。
- 第一种是在现有项目或目录下导入所有文件到 Git 中。
$ git init
该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件。
- 第二种是从一个服务器克隆一个现有的 Git 仓库。
# clone已经存在的项目
$ git clone https://gitee.com/coderhony/gittest.git
这会在当前目录下创建一个名为 “gittest” 的目录,并在这个目录下初始化一个 .git
文件夹,从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。
6.2添加&提交
添加到暂存区git add <filename>
$ git add qin.md
提交到仓库
$ git commit -m "创建了'秦国'的文件"
跳过使用暂存区域
$ git commit -a -m '创建了 wu.md'
检查当前文件状态
$ git status
跟踪新文件
$ git add <filename>
6.3.撤销操作
取消暂存的文件
$ git reset HEAD wei.md
修改未暂存的状态了。修改的内容还在。
撤消对文件的修改
$ git checkout -- wei.md
在工作目录已修改的文件(指的是未提交到暂存区域的修改)还原成上次提交时的样子。
Tips: git
checkout -- [file] 是一个危险的命令,对文件做的任何修改都会消失
- 你只是拷贝了另一个文件来覆盖它。
撤销(重置)操作主要有3个命令:checkout、reset、revert
操作的范围又可以分为 commits 和 files,根据后面的参数决定操作的范围。
命令 | 范围 | 使用场景 |
---|---|---|
git reset | commit级别 | 丢弃提交或扔掉未提交的修改 |
git reset | file级别 | Unstage 文件 |
git checkout | commit级别 | 切换分支或检查旧的快照 |
git checkout | file级别 | 丢弃working directory中的修改 |
git revert | commit级别 | 撤销在公共分支上的提交 |
git reset作用于commit
选项 | HEAD | Index | Working directory |
---|---|---|---|
--soft | 是 | 否 | 否 |
--mixed(默认) | 是 | 是 | 否 |
--hard | 是 | 是 | 是 |
$ git reset --soft 7b055
$ git reset --mixed 7b055
$ git reset --hard 7b055
git reset作用于file
$ git reset HEAD <file>
修改只是添加到了暂存区,还没有提交时,可以有如下两种方式理解:
1.拉取最近一次提交到版本库的文件到暂存区
2.可以把暂存区的修改撤销掉(unstage),重新放回工作区
git checkout作用于file
$ git checkout -- files
两种情况:
1.修改后还没有被放到暂存区,撤销修改后工作目录就回到和版本库最后一次commit一样的状态;
2.已经添加到暂存区后,又再次作了修改,撤销修改就回到和暂存区一样的状态。
git checkout 作用于commit
$ git checkout branchname
切换分支,前提是该名称的分支存在
Tips:区分作用于file还是commit方式是有无 --
6.4.查看不同
查看工作目录中当前文件和暂存区域快照之间的差异
git diff
查看暂存区域和本地仓库之间的差异
git diff —cached
查看工作区与指定提交之间的区别git diff <gitreversion>
$ git diff HEAD
查看两个commit之间的差别git diff commitId1 commitId2
$ git diff 78c515d8 1ff36534
HEAD 是最近一次 commit
6.5 忽略文件
在git所管理的目录下,创建一个名为.gitignore
的文件,文件中列出要忽略的内容。
.DS_Store
# 忽略'shanggu.md'文件
shanggu.md
# 忽略所有以 .o 或 .a 结尾的文件
*.[oa]
# 以 / 开头防止递归
/sources
# 以 / 结尾指定目录
lib/
# 不忽略 qin.md 文件
!qin.md
6.6.删除已提交的文件
wu.md文件已经被提交到了仓库中,想要取消git对 wu.md
文件的跟踪:
$ git rm --cached wu.md
注意:wu.md 一定要添加到 .gitignore中。
6.7.修改操作
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 —amend
选 项的提交命令尝试重新提交。
提交信息需要修改:
$ git commit --amend
忘记提交了文件:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
最终你只会有一个提交,第二次提交将代替第一次提交的结果。
6.8查看历史
使用 git log
命令查看历史提交记录。如果不带任何参数,它会列出所有历史记录,最近的排在最上方,显示提交对象的哈希值,作者、提交日期、和提交说明。
显示参数
使用 -p
选项,显示每次提交的内容差异(信息较详细)。 也可以加上 -2
来仅显示最近两次提交:
$ git log -p -2
使用 —stat
选项,显示每次提交的简略的统计信息
$ git log --stat
使用 --name-only
选项,仅在已修改的提交信息后显示文件列表
$ git log --name-only
使用 --name-status
选项,显示新增、修改和删除的文件清单
$ git log --name-status
使用 --abbrev-commit
仅显示SHA-1的前几个字符
$ git log --abbrev-commit
使用--graph
显示ASCII图形表示的分支合并历史
$ git log --graph
选项 --pretty=
可以指定使用不同于默认格式的方式展示提交历史。
$ git log --pretty=oneline
# format 子选项,可以定制要显示的记录格式
$ git log --pretty=format:"%h - %an, %ar : %s"
-
%h
: 提交对象的简短哈希字串 -
%H
:提交对象的完整哈希字串 -
%an
:作者(author)的名字 -
%ae
:作者的电子邮件地址 -
%ar
: 作者修订日期,按多久以前的方式显示 -
%s
: 提交说明 -
%cd
: 提交日期 -
%p
: 父对象的简短哈希字串 -
%cn
:提交者(committer)的名字 -
%ce
:提交者的电子邮件地址
选项可以组合使用:
$ git log --pretty=format:"%h %s" --graph
带颜色的输出
$ git log --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>"
断句:[%Cred%h][%Creset -][%C(yellow)%d ][%Cblue%s][%Cgreen(%cd)][%C(bold blue)<%an>]
- 一个颜色+一个内容
- 颜色以%C开头,后边接几种颜色
- 能设置的颜色值包括:reset(默认的灰色),normal, black, red, green, yellow, blue, magenta, cyan, white.
选项--date=
(relative|local|default|iso|rfc|short|raw):输出指定格式日期
$ git log --date=short
定制日期格式
$ git log --date=format:'%Y-%m-%d %H:%M:%S'
筛选参数
1.按数量:-n
参数显示前n条log
$ git log -n 1
2.按日期:--after=
和--before=
参数,日期
$ git log --after="yesterday"
$ git log --before=="2019-5-7"
$ git log --after="2019-5-5" --before="2019-5-8"
3.按作者:--author
参数
$ git log --author="hony"
4.按文件:--
$ git log -- wu.md
5.按分支: branchname --
$ git log qinguo --
6.按内容:-S"<string>"
、-G"<string>"
查找包含特定字符的某处代码被修改的记录,比如某个函数什么时候添加和修改的,等等。
查文本用-S
,查找的结果是匹配的 字符串出现的次数发生变化 的提交。比如,某次提交将abcdef
修改为abcd
, 使用git log -Sabc
将不会查找出此提交,使用git log -Sabcde
可以查找出。(-S
可以使用–pickaxe-regex
修改为接受正则表达式。)
$ git log -S"开始"
查正则用-G
,没有次数变化的限制,只要变化就会查找出对应的提交
$ git log -G"开始"
注意:-G默认接受正则表达式,而-S通常接受一个字符串,但可以使用–pickaxe-regex修改为接受正则表达式。
7.按范围:显示2个branch之间的不同
..
包含所有qinguo有但是master没有的commit
$ git log master..qinguo
...
查询master或qinguo分支中的提交记录
$ git log master...qinguo
8.按tag:
查询tag之前的commit
$ git log v1.0
查询tag之后的commit, 不包含tag所在的commit本身
$ git log v1.0..
9.按commit:
查询commit之前的记录,包含commit
$ git log commit
查询commit1与commit2之间的记录,包括commit1和commit2
$ git log commit1 commit2
查询commit1与commit2之间的记录,不包括commit1
$ git log commit1..commit2
10.组合使用:
$ git log --date=format:'%Y-%m-%d %H:%M:%S' --author='hony' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit
6.9搜索提交内容
搜索代码内容
使用 grep
命令,可以很方便地从提交历史或者工作目录中查找一个字符串或者正则表达式。默认情况下 Git 会查找你工作目录的文件。
$ git grep "王"
传入 -n
参数来输出 Git 所找到的匹配行行号。
$ git grep -n "秦"
使用 -c
选项查看每个文件里有多少行匹配内容。
$ git grep -c "秦"
使用 -p
选项查看匹配的行是属于哪一个方法或者函数:
$ git grep -p "王"
使用--name-only
只显示文件名
$ git grep --name-only "王"
使用正则结合 --and
或--or
选项来查看复杂的字符串组合
$ git grep -e "^秦"
$ git grep -e '秦' --and -e "王"
$ git grep -e '秦' --or -e "王"
Git日志搜索
行日志搜索
搜索包含某个字符的提交说明
$ git log --grep="秦"
七、远程仓库
查看远程仓库
$ git remote
origin
origin
是 Git 给你克隆的仓库服务器的默认名字。
指定选项 -v
,会显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。
$ git remote -v
origin https://gitee.com/coderhony/gittest.git (fetch)
origin https://gitee.com/coderhony/gittest.git (push)
查看更多信息
$ git remote show origin
取消远程仓库和本地关联
$ git remote rm origin
添加远程仓库
添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写:
$ git remote add origin https://gitee.com/coderhony/gittest.git
其中origin
是仓库的名字,https://gitee.com/coderhony/gittest.git
是远程仓库的地址。
从远程拉取数据
注意:远程分支更新的情况。
$ git fetch [remote-name]
这个命令会访问远程仓库,从中拉取所有你还没有的数据。
注意 git fetch 命令会将数据拉取到你的本地仓库 - 它并不会自动合并或修改你当前的工作。
如果你有一个分支设置为跟踪一个远程分支,可以使用 git pull 命 令来自动的抓取然后合并远程分支到当前分支。
$ git pull
推送数据到远程
将本地分支推送到远程 git push [remote-name] [branch-name]
$ git push origin master
注意:1.必须对远程有写入权限,才可以进行push操作。
2.必须先进行pull操作,再进行push操作。
远程仓库的重命名
$ git remote rename origin pb
注意,这同样也会修改你的远程分支名字。
现在可以用字符串 pb
指代对应的仓库地址了。
添加本地分支与远程仓库的关联
本地的master分支与远程的master分支相关联
$ git branch --set-upstream-to=origin/master master
$ git branch -u origin/master
前面的 origin/master 指的是远程分支;后面的 master 指的是本地分支。
此命令的含义是指当前分支的upstream为origin远程仓库的master分支。
注意:如果提示
error: the requested upstream branch 'origin/master' does not exist
错误,则先进行git fetch
操作,然后再进行分支关联操作。
取消本地分支与远程仓库的关联
$ git branch --unset-upstream
八、协议
Git 可以使用四种主要的协议来传输数据:本地传输,SSH 协议,Git 协议和 HTTP 协议。
1.HTTPS协议
$ https://gitee.com/coderhony/gittest.git
可以使用用户名/密码授权,和SSH相比,不用先在本地生成 SSH 密钥对再把公钥上传到服务器。缺点是速度慢,每次推送都必须输入口令。(有了keychain
的功能就不用每次都输入密码了。)
2.SSH协议
是一个同时便于读和写操作的网络协议。通过 SSH 访问是安全的,所有数据传输都是加密和授权的。SSH 很高效,会在传输之前尽可能的压缩数据。SSH 唯一的限制就是不能匿名访问。
生成 SSH Key:
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
$ cat ~/.ssh/id_rsa.pub
$ pbcopy < ~/.ssh/id_rsa.pub
clone代码:
$ git clone ssh://git@gitee.com/<repo_owner>/<reponame>.git
$ git clone git@gitee.com:coderhony/gittest.git
3.本地协议
clone如下
$ git clone /Users/hony/Desktop/gittest/gittest
要添加一个本地仓库作为现有 Git 项目的远程仓库,可以这样做:
$ git remote add local_proj /Users/hony/Desktop/gittest/gittest
难以控制不同位置的访问权限。
4.Git协议
这是一个包含在 Git 软件包中的特殊守护进程; 它会监听一个提供类似于 SSH 服务的特定端口(9418),而无需任何授权。要么所有人都能克隆,要么都不能。所以没有授权机制是一个缺点。
九、分支
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。
在进行提交操作时,Git 会保存一个提交对象。该提交对象会包含一个指向暂存内容快照的指针,还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
注意,首次提交产生的提交对象没有父对象。普通提交操作产生的提交对象有一个父对象。由多个分支合并产生的提交对象有多个父对象。
暂存操作会为每一个文件计算校验和(SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
$ git add wei.md zhao.md han.md
$ git commit -m '添加魏赵韩三个国家'
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录的校验和,然后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外, 还包含指向这个树对象的指针。
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。它会在每次的提交操作中自动向前移动。
1.创建分支
创建分支就是创建了一个可以移动的新的指针。比如,创建一个 qinguo 分 支, 你需要使用 git branch
命令:
$ git branch qinguo
在 Git 中,有一个名为 HEAD 的特殊指针,指向当前所在的本地分支。
可以简单地使用 git log
命令搭配--decorate
参数查看各个分支当前所指的对象。
$ git log --oneline --decorate
当前 “master” 和 “qinguo” 分支均指向校验和以 f30ab 开头的提交对象。
切换分支
要切换到一个已存在的分支,使用 git checkout 命令。
$ git checkout qinguo
再修改内容并提交一次:
$ echo "秦国在战国史上是最辉煌的" >> qin.md
$ git commit -a -m '添加了qin.md'
HEAD 分支随着提交操作自动向前移动:
qinguo
分支向前移动了,master 分支还停留在了checkout
时的位置。切回到master分支:
$ git checkout master
这条命令做了两件事。 一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。
注意,分支切换会改变你工作目录中的文件。
再稍微做些修改并提交:
$ vim zhao.md
$ git commit -a -m '赵国的赵括出生了'
现在,这个项目的提交历史产生了分叉。使用 git log 命令查看分叉历史。 运行 git log --oneline --decorate --graph —all
,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
$ git log --oneline --decorate --graph --all
2.分支管理
查看分支
$ git branch
分支前的 *
字符:表示当前所在的分支(即当前 HEAD 指针所指向的分支)。
如果要查看每一个分支的最后一次提交,可以运行以下命令:
$ git branch -v
添加--merged
选项可以查看哪些分支已经合并到当前分支:
$ git branch --merged
添加--no-merged
选项可以查看所有包含未合并当前分支的分支:
$ git branch --no-merged
3.分支开发工作流
长期分支
在 master
分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。在短期分支(如develop
、topic
)上进行开发测试,这些短期分支达到稳定状态后,就可以被合并入 master
分支了。
稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。
特性分支
特性分支是一种短期分支,它被用来实现单一特性或其相关工作。比如如develop
、topic
分支。
4.远程分支
远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你可以通过 git ls-remote (remote)
来显式地获得远程引用的完整列表,或者通过 git remote show (remote)
获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。
远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
它们以 (remote)/(branch)
形式命名。比如 origin/master
分支, origin/feature-v410
分支。
远程有一个https://gitee.com/coderhony/gittest
服务器,从这里克隆,Git 的 clone
命令会为你自动将其命名为 origin
,拉取它的所有数据,创建一个指向它的 master
分支的指针,并且在本地将其命名为 origin/master
。Git 也会给你一个与 origin 的 master
分支在指向同一个地方的本地 master
分支,这样在本地就可以基于master
分支进行工作了。
“origin” 和“master”并无特殊含义。
“master” 是当你运行
git init
时默认的起始分支名字,原因仅仅是它的广泛使用。“origin” 是当你运行
git clone
时默认的远程仓库名字。如果你运行git clone -o repository https://gitee.com/coderhony/gittest.git
,那么你默认的远程分支名字将会是repository/master
。
如果你在本地的 master
分支做了一些工作,然而在同一时间,其他人推送提交到 git.ourcompany.com
并更新了它的 master
分支,那么你的提交历史将向不同的方向前进。 也许,只要你不与 origin 服务器连接,你的 origin/master
指针就不会移动。
如果要同步你的工作,运行 git fetch origin
命令。这个命令查找 “origin” 是哪一个服务器(是 git.ourcompany.com
),从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master
指针指向新的、更新后的位置。
假定你有另一个内部 Git 服务器,仅用于你的 sprint 小组的开发工作。 这个服务器位于 git.team1.ourcompany.com
。 你可以运行 git remote add
命令添加一个新的远程仓库引用到当前的项目。 将这个远程仓库命名为 teamone
,将其作为整个 URL 的缩写。
现在,可以运行 git fetch teamone
来抓取远程仓库 teamone
有而本地没有的数据。 因为那台服务器上现有的数据是 origin
服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程跟踪分支 teamone/master
指向 teamone
的 master
分支。
推送
当你想要公开分享一个分支时,需要手动显式地将其推送到有写入权限的远程仓库上。 使用 git push (remote) (branch)
命令:
比如serverfix
是共享分支
$ git push origin serverfix
Git 自动将 serverfix
分支名字展开为 refs/heads/serverfix:refs/heads/serverfix
,那意味着,“推送本地的 serverfix 分支来更新远程仓库上的 serverfix 分支。” 我们将会详细学习 Git 内部原理 的 refs/heads/
部分。
也可以运行 git push origin serverfix:serverfix
,可以通过这种格式来推送本地分支到一个命名不相同的远程分支。 如果并不想让远程仓库上的分支叫做 serverfix
,可以运行 git push origin serverfix:awesomebranch
来将本地的 serverfix
分支推送到远程仓库上的 awesomebranch
分支。
下一次其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/serverfix
,指向服务器的 serverfix
分支的引用:
$ git fetch origin
当抓取到新的远程跟踪分支时,本地不会自动生成一个新的 serverfix
分支,只有一个不可以修改的 origin/serverfix
指针。如果想要在自己的 serverfix
分支上工作,可以将其建立在远程跟踪分支之上:
$ git checkout -b serverfix origin/serverfix
这会创建一个用于工作的本地分支,并且起点位于 origin/serverfix
。
跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建所谓的 “跟踪分支”(它跟踪的分支叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master
的 master
分支。
$ git checkout -b [branch] [remotename]/[branch]
$ git checkout -b serverfix origin/serverfix
$ git checkout -b sf origin/serverfix
创建跟踪分支的快捷方式:
$ git checkout --track origin/serverfix
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支:
$ git branch --set-upstream-to origin/serverfix
$ git branch -u origin/serverfix
查看所有跟踪分支,使用 git branch
的 -vv
选项
$ git branch -vv
需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。 这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。 如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做:
$ git fetch --all;
$ git branch -vv
拉取
当 git fetch
命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。
git pull
命令是 git fetch
紧接着一个 git merge
命令。git pull
会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。
删除远程分支
运行带有 --delete
选项的 git push
命令来删除一个远程分支。 如果想要从服务器上删除 serverfix
分支,运行下面的命令:
$ git push origin --delete serverfix
基本上这个命令做的只是从服务器上移除这个指针。
5.整合分支
整合来自不同分支的修改主要有两种方法:merge
以及 rebase
。
Merge 快速前进
此时在chuguo分支上,要想把chuguo合并到到master上
可以先检出到master分支,然后执行merge操作
$ git checkout master
$ git merge chuguo
Merge 三方合并
在这种情况下,开发历史从C2的地方开始分叉开来。
因为,master 分支所在提交并不是 qinguo 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4
和 C5 )以及这两个分支的工作祖先(C2),做一个简单的三方合并。
C2: master和qinguo的共同祖先
C4:将要接收别处merge进来的快照
C5:将要被merge到别处的快照
Git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
Git会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础。
Rebase的基本操作
开发任务分叉到两个不同分支,又各自提交了更新。
可以提取在 C5 中引入的补丁和修改,然后在 C4 的基础上应用一次。 在 Git 中,这种操作就叫做 变基。可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
$ git checkout qinguo
$ git rebase master
它的原理是首先找到这两个分支(即当前分支 qinguo、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C4,最后以此将之前另存为临时文件的修改依序应用。
现在回到 master 分支,进行一次快进合并。
$ git checkout master
$ git merge qinguo
此时,C5' 指向的快照就和上面使用 merge 命令的例子中 C6 指向的快照一模一样了。 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。提交历史是一条直线没有分叉。
注意,无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。
更复杂的Rebase举例
需求:
希望将 hanguo 中的修改合并到主分支master并发布,但暂时并不想合并 weiguo 中的修改,因为它们还需要经过更全面的测试。
这时,你就可以使用 git rebase 命令的 --onto 选项,选中在 hanguo 分支里但不在 weiguo 分支里的修改(即 C8 和 C9),将它们在 master 分支上重放:
$ git rebase --onto master weiguo hanguo
取出 hanguo 分支,找出处于 hanguo 分支和 weiguo 分支的共同祖先之后的修改,然后把它们在 master 分支上重放一遍。
现在可以快进合并 master 分支了。
$ git checkout master
$ git merge hanguo
接下来你决定将 weiguo 分支中的修改也整合进来。 使用 git rebase [basebranch] [topicbranch] 命令可以直接将特性分支(即本例中的 weiguo )变基到目标分支(即 master)上。这样做能省去你先切换到 weiguo 分支,再对其执行变基命令的多个步骤。
$ git rebase master weiguo
此时, weiguo 中的代码被“续”到了 master 后面。
然后就可以快进合并主分支 master 了:
$ git checkout master
$ git merge weiguo
至此,hanguo 和 weiguo 分支中的修改都已经整合到主分支里了,你可以删除这两个分支
$ git branch -d hanguo
$ git branch -d weiguo
Rebase的风险
使用变基要遵守一条准则:
不要对在你的仓库外有副本的分支执行变基。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
merge 和 rebase的选择
merge : 仓库的提交历史即是 记录实际发生过什么。
rebase : 提交历史是 项目过程中发生的事。
总的原则是,只对尚未推送的本地修改执行变基操作清理历史,不要对已推送至别处的提交执行变基操作。
冲突解决
无论是使用merge 还是 rebase , 有时候合并操作不会如此顺利。
如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git
就没法干净的合并它们。
$ git merge qin
Auto-merging qin.md
CONFLICT (content): Merge conflict in qin.md
Automatic merge failed; fix conflicts and then commit the result.
此时Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。
<<<<<<< HEAD
秦孝公任用商鞅进行变法
=======
商鞅来到了秦国
>>>>>>> qinguo
HEAD和===
之间的部分表示当前分支ours(master上)的内容
===
和>>>qinguo
之间的部分表示其他分支theirs(qinguo)上的的内容
解决方案可以仅保留其中一个分支的修改,并且 <<<<<<<
, =======
, 和 >>>>>>>
这些行要完全删除。在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
接下来就可以继续工作了。
如果是merge产生的冲突
$ git add .
$ git commit -m “合并代码,解决冲突”
如果是rebase产生的冲突
$ git add .
$ git rebase --continue
…
如果要停止合并,可以使用
# merge的情况
$ git merge --abort
# rebase的情况
$ git rebase --abort
十、标签
查看标签
$ git tag
这个命令以字母顺序列出标签。
也可以使用特定的模式查找标签。如果只对 1.8.5 系列感兴趣,可以运行:
$ git tag -l 'v1.8.5*’
创建标签
两种主要类型的标签:
轻量标签(lightweight)与附注标签(annotated)。
•轻量标签只是一个特定提交的引用。
•附注标签是存储在 Git 数据库中的一个完整对象。它们是可以被校验的;其中包含打标签者的名字、电子 邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard (GPG)签名与验证。 通常建议创建附注标签。
附注标签
在运行 tag 命令时指定 -a 选项:
$ git tag -a v1.4 -m 'my version 1.4'
-m 选项指定了一条将会存储在标签中的信息。
通过使用 git show 命令可以看到标签信息与对应的提交信息:
$ git show v1.4
轻量标签
轻量标签本质上是将提交校验和存储到一个文件中, 没有保存任何其他信息。创建轻量标签,只需要提供标签名字:
$ git tag v1.4-lw
如果在标签上运行 git show,只显示提交信息。
后期打标签
在历史提交上打标签,在某个提交上打标签,需要在命令的末尾指定提交的校验和:
$ git log --pretty=oneline
$ git tag -a v1.2 9fceb02
推送标签
在创建完标签后你必须显式地推送标签到 共享服务器上,使用git push origin [tagname]命令。
$ git push origin v1.4
如果要一次性推送很多标签,也可以使用带有 —tags 选项。
$ git push origin --tags
检出标签
如果你想要工作目录与仓库中特定 的标签版本完全一样,可以使用 git checkout -b [branchname] [tagname] 在特定的标签上创建一个 新分支:
$ git checkout -b version2 v2.0.0
十一、储藏
有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。此时可以使用 git stash (或 git stash save)命令。修改的跟踪文件与暂存改动 - 然后将未完成的修改保存到一个栈上,而你 可以在任何时候重新应用这些改动。
$ git stash
工作目录是干净的了。
要查看储藏的东西,可以使用 git stash list:
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size”
恢复储藏的内容到工作区,使用 git stash apply 命令。如果想要更旧的储藏,可以指定git stash apply stash@{2} 。如果未指定某一个储藏,Git 认为指定的是最近的储藏:
$ git stash apply
git stash apply命令之后,在堆栈上还有记录。使用 git stash drop 命令,恢复之后,堆栈的信息也就没有了。
$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)
十二、配置别名
如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.unstage 'reset HEAD --’
$ git config --global alias.last 'log -1 HEAD’
$ git config --global alias.lm "log --color --date=format:'%Y-%m-%d %H:%M:%S' --author='hony' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit"
当要输入 git commit 时,只需要输入 git ci 即可。
使下面的两个命令等价:
$ git unstage fileA
$ git reset HEAD -- fileA
Git 只是简单地将别名替换为对应的命令。
十三、核武器级选项:filter-branch
filter-branch 是一个历史改写的选项,可以改写大量提交。
例如,全局修改你的邮箱地址或从每一个提交中移除一个文件。
全局修改邮箱地址
可以通过 filter-branch 来一 次性修改多个提交中的邮箱地址。 需要小心的是只修改你自己的邮箱地址,所以要使用 --commit-filter:
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "hony@163.com" ];
then
GIT_AUTHOR_NAME="hony";
GIT_AUTHOR_EMAIL="hony1220@163.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
这会遍历并重写每一个提交来包含你的新邮箱地址。
从所有提交中删除一个文件
意外地提交了一个巨大的二进制文件,想将它从所有地方删除。
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
--tree-filter
选项会在每次检出项目时先执行指定的命令然后重新提交结果。在这个例子中,你会在所有快照中删除一个名叫
password.txt 的文件,无论它是否存在。
Git重写目录树并且提交,使用时最好新建分支,测试没有问题了,再reset到master上。
十四、清理工作目录
可以使用git clean命令去除冗余文件或者清理工作目录。 使用git clean -f -d命令来移除工作目录中所有未追踪的文件以及空的子目录。 -f 意味着强制移除。
如果只是想要看看它会做什么,可以使用 -n 选项来运行命令,这意味着 “做一次演习然后告诉你 将要 移除什么”。
$ git clean -d -n
Would remove test.o
Would remove tmp/
如果不知道 git clean 命令将会做什么,在将 -n 改为 -f 来真正做之前总是先用 -n 来运行它做双重检查。
默认情况下,git clean 命令只会移除没有忽略的未跟踪文件。任何与.gitignore 或其他忽略文件中的模式匹配的文件都不会被移除。
十五、简短的SHA-1
Git 十分智能,你只需要提供 SHA-1 的前几个字符就可以获得对应的那次提交,当然你提供的 SHA-1 字符数量 不得少于 4 个。
例如查看一次指定的提交,假设你执行 git log 命令来查看之前新增一个功能的那次提交:
$ git log
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
假设这个提交是 1c002dd….,如果你想 git show 这个提交,下面的命令是等价的:
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b
Git 可以为 SHA-1 值生成出简短且唯一的缩写。 如果你在 git log 后加上 --abbrev-commit 参数,输出结 果里就会显示简短且唯一的值;默认使用七个字符:
$ git log --abbrev-commit --pretty=oneline
通常 8 到 10 个字符就已经足够在一个项目中避免 SHA-1 的歧义
如果地球上65 亿的人类都在编程,每人每秒都在产生等价于整个 Linux 内核历史(一百万个Git 对象)的代码,并将之提交到一个巨大的 Git 仓库里面,那将花费
5 年的时间才会产生足够的对象,使其拥有 50% 的概率产生一次SHA-1对象冲突。
十六、引用日志
当你在工作时,Git 会在后台保存一个引用日志(reflog),引用日志记录了最近几个月你的 HEAD 和分支引用所指向的历史。可以使用 git reflog 来查看引用日志:
$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD
每当你的 HEAD 所指向的位置发生了变化,Git 就会将这个信息存储到引用日志这个历史记录里。 通过这些数据,你可以很方便地获取之前的提交历史。 如果你想查看仓库中 HEAD 在五次前的所指向的提交,你可以使用 @{n} 来引用 reflog 中输出的提交记录。
$ git show HEAD@{5}
十七、祖先引用
祖先引用是另一种指明一个提交的方式。 如果你在引用的尾部加上一个 ^, Git 会将其解析为该引用的上一个提交。 假设你的提交历史是:
$ git log --pretty=format:'%h %s' --graph --all
* 734713b fixed refs handling, added gc auto, updated tests
* d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/ * 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list
你可以使用 HEAD^ 来查看上一个提交,也就是 “HEAD 的父提交”:
$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
另一种指明祖先提交的方法是~。 同样是指向第一父提交,因此 HEAD~
和 HEAD^
是等价的。而区别在于你在后面加数字的时候。 HEAD~2
代表 “第一父提交的第一父提交”,也就是 “祖父提交”。
在 ^
后面添加一个数字—例如d921970^2
代表 d921970 的第二父提交
这个语法只适用于合并 (merge)的提交,因为合并提交会有多个父提交。第一父提交是你合并时所在分支,而第二父提交是你所合并的分支。