本文记录了一些 Git 相关的基本知识,内容主要来自 《Pro Git 》这本书。
版本控制系统是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
Git 是目前世界上最先进的分布式版本控制系统(没有之一)。
Git 仓库有三个工作区域:工作目录(工作区)、仓库目录、暂存区。
工作目录(工作区)是对项目的某个版本独立提取出来的内容,这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改,工作目录可以简单理解为在本地开展工作的目录,所有在这个目录(子目录)里的文件都会被 Git 管理,文件在此目录下(或子目录下)才能添加或提交到仓库。
仓库目录是 Git 用来保存项目的元数据和对象数据库的地方,是在工作目录中一个叫 .git
的隐藏目录。这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。
暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录 .git
中,按照 Git 的术语叫做“索引”,不过一般说法还是叫“暂存区”。
Git 有三种状态:已提交(commited)、已修改(modified)、已暂存(staged)。
除了这三种状态之外,还有一种特殊的状态:未跟踪(untracked)状态。未跟踪是指文件虽然在工作目录中,但并没有纳入 Git 的管理(Git 虽然知道这个文件的存在,但并不会跟踪它版本的变化),比如新建的文件还没有提交过,就是未跟踪状态,或者在一个非空的目录里初始化仓库(见下文“获取 Git 仓库”部分),那么在第一次提交之前,原先的内容都处于未跟踪状态。
在Linux系统中,先试着输入 git
,查看系统有没有安装 Git,如果没有安装,使用以下命令进行安装:
sudo apt install git-all # 基于 Debian 的发行版使用此项,如 Ubuntu
或:
sudo dnf install git-all # 基于 RPM 的发行版使用此项,如 Fedora、CentOS 或 RHEL
此命令适用于较新版本的 Debian 或 Fedora Linux,其它 Linux 版本,可以直接通过源码安装。
在 Mac Os 系统中,有多种方法安装 Git,最简单的方法是安装 Xcode Command Line Tools。 Mavericks (10.9) 或更高版本的系统中,在 Terminal 里尝试首次运行 git 命令即可。
git --version
如果没有安装过命令行开发者工具,将会提示安装。
也可以通过 homebrew 安装(需先安装 Homebrew):
brew install git
在 Windows 系统中,可以从 Git 官网直接下载安装程序,然后按默认选项安装即可。
安装好 Git 后,还需要对 Git 环境进行初步的配置。
Git 自带一个 git config
的工具来帮助设置控制 Git 外观和行为的配置变量。这些变量存储在三个不同的位置:
/etc/gitconfig
文件: 包含系统上每一个用户及他们仓库的通用配置。要读写此文件的配置变量,需使用--system
参数。~/.gitconfig
或 ~/.config/git/config
文件:此文件中的配置只针对当前用户。要读写此文件的配置变量,需使用 --global
参数。config
文件(也就是 .git/config
):此文件中的配置只针对该仓库。每一个级别覆盖上一级别的配置,比如 .git/config
的配置变量会覆盖 /etc/gitconfig
中的配置变量。
安装完成之后,第一件事是配置用户名和邮件地址:
git config --global user.name "your name"
git config --global user.email "[email protected]"
因为 Git 是分布式版本控制系统,支持多人协作开发,所以,每个用户提交时都必须自报家门:用户名和 Email 地址。
注意这里 git config
命令的 --global
参数,用了这个参数,表示当前用户下的所有仓库都使用这个用户名和 Email 地址,当然也可以对某个仓库指定不同的用户名和 Email 地址,只需要在目标仓库下去掉 --global
参数运行上面的命令。
Git 需要用户输入信息时会调用文本编辑器,默认是和操作系统一致,通常是 Vim,但如果不想使用默认的文本编辑器,也可以进行设置:
git config --global core.editor emacs
这一命令把默认的文本编辑器更改为了 emacs,并且应用到当前用户所有仓库。
git config --list
这个命令会列出所有 Git 当时能找到的配置。
git congfig
这个命令会检查 Git 的某一项配置
,如检查用户名的配置:
git config user.name
Git 会从多个配置文件中读取同一配置变量的不同值并把它们都显示出来,有时可能会显得混乱,使用 --show-origin
查找最后设置某个配置变量的配置文件。
git config --show-origin
这个命令会检查 Git 的某一项配置
的原始值,会显示哪一个配置文件最后设置了该值。
git help
git --help
man git-
以上三个命令都可以获得 Git 命令的使用手册,比如获取 config
命令的使用手册:
git help config
Git 仓库,英文名 repository,可以简单理解成一个目录,这个目录里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
首先,选择一个合适的地方,创建一个空目录,并进入这个目录,如:
mkdir learngit
cd learngit
pwd
pwd
命令用于显示当前目录。
第二步,通过 git init
命令把这个目录变成 Git 可以管理的仓库(初始化仓库):
git init
初始化仓库后,可以看到仓库目录中多了一个 .git
的隐藏目录,这个目录就是 Git 用来跟踪管理仓库的,里面含有 Git 所需的必要文件。
在多人协作的情况下,可能并不需要从零开始初始化仓库,而是直接克隆远程仓库到本地,直接开始工作,使用以下命令将远程仓库克隆到本地:
git clone
这个命令会在本地创建一个跟远程仓库重名的本地仓库,并将远程仓库的所有数据抓取到 .git
子目录下,将最新版本的文件拷贝到工作区。
remote-url
是指远程仓库的路径,一般有两种情况,分别是从本地主机克隆和从网络主机克隆,如果是从本地主机克隆仓库,命令就是:
git clone file:///path/to/myproject.git
如果是从网络主机克隆,命令就是:
git clone [user@]server:/path/to/myproject.git # 使用 SSH 协议
或:
git clone https://example.com/path/to/myproject.git # 使用 HTTPS 协议
也可以在克隆时,自定义本地仓库的名字:
git clone
Git 并不会主动将文件纳入 Git 的管理,需要手动将文件添加到仓库,先要把文件放到工作目录下(子目录也可以),此时文件就处于未跟踪状态,然后分两步进行:
第一步,用命令 git add
告诉 Git,把文件添加到暂存区:
git add /
这个命令使用文件或目录作为参数,如果参数是目录,该命令将递归地跟踪该目录下的所有文件。
第二步,用命令 git commit
告诉 Git,把文件提交到仓库:
git commit -m "message"
-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样就能从历史记录里方便地找到改动记录,如果在运行 git commit
命令时不加 -m
参数,Git
就会打开一个文本编辑界面,需要在第一行输入提交的说明。
在前文中,如果是在非空的本地目录中初始化仓库,那么目录中原来的内容都处于未跟踪状态,需要使用以上两个步骤将原来的内容添加到仓库进行管理。
实际上对文件进行修改后都要用 git add
命令将修改后的文件添加到暂存区,然后用 git commit
命令提交。
可以多次使用 git add
命令添加多个文件,然后用 git commit
命令一次提交多个文件。
也可以给 git add
命令指定 --all
选项,将工作区中修改过的文件一次性添加到暂存区,也包括新文件。
git add --all
通过给 git commit
命令指定 -a
选项,可以让 Git 把所有跟踪过的文件暂存起来一并提交,跳过 git add
步骤:
git commit -a -m "message"
所有的版本控制系统,其实只能跟踪文本文件的改动,而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从 100KB 改成了 120KB,但到底改了啥,版本控制系统不知道,也没法知道。如果要真正使用版本控制系统,就要以纯文本方式编写文件,建议使用标准的 UTF-8 编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
工作区(Working Directory)简单理解就是最初创建仓库的目录,文件在此目录下才能暂存或提交。
工作区中有一个隐藏目录 .git
,这个目录里保存着对 Git 很重要的内容,其中主要有称为 stage
(或者叫 index
)的暂存区,还有 Git 自动创建的第一个分支 master
,以及指向 master
的一个指针叫 HEAD
。
git add
命令实际上就是把要提交的所有修改放到暂存区,然后,执行 git commit
就可以一次性把暂存区的所有修改提交到当前分支,默认分支是 master
。(分支相关知识见下文“分支管理”部分)
在工作目录运行:
git status
这个命令返回当前的工作状态,包括当前分支,已经暂存的文件,已经修改的文件,未跟踪的文件等信息。
也可以使用 -s
或 --short
选项简化输出:
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
未跟踪文件前面有 ??
标记,新添加到暂存区中的文件前面有 A
标记,修改过的文件前面有 M
标记。输出中有两栏,左栏指明了暂存区的状态,右栏指明了工作区的状态。例如,上面的状态报告显示: README 文件在工作区已修改但尚未暂存,而 lib/simplegit.rb 文件已修改且已暂存。Rakefile 文件已修改,暂存后又作了修改,因此该文件的修改中既有已暂存的部分,又有未暂存的部分。
一般我们总会有些文件无需纳入 Git 的管理,也不希望 Git 总是提示有未跟踪文件。比如日志文件,或者编译过程中创建的临时文件等,这时可以在 Git 工作区目录中创建一个特殊的 .gitignore
文件,列出要忽略的文件的模式,Git 就会自动忽略这些文件,但要注意 .gitignore
文件本身要提交到仓库里才能生效。
比如文件内容可以这样写:
# windows
...
# python
......
# My configurations:
db.ini
deploy_key_rsa
文件 .gitignore
的格式规范如下:
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字);使用两个星号( ** )表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z、a/b/z 或 a/b/c/z 等。
在 Git 中,修改只要没有提交,都可以撤销:
如果文件修改后,错误的添加到了暂存区,使用以下命令取消暂存(运行 git status
会出现相应提示):
git restore --staged
或:
git reset HEAD
这个命令会把暂存区的修改撤销掉,而工作区中的内容不变。(实际上这个命令是用当前所在提交的文件替换暂存区的文件,但不替换工作区的文件)
如果只是想取消工作区对文件的修改,使用以下命令(运行 git status
会出现提示):
git restore
或:
git checkout --
这个命令会把文件在工作区的修改撤销,这里有两种情况:
(实际上这个命令是用暂存区的文件替换工作区的文件)
如果对文件的修改已经进行了提交,那么就不可能轻易撤销,可以通过 git reset
命令回到之前的某一个版本,达到撤销修改的效果,具体见下文的“版本回退”部分。
要从 Git 仓库中删除文件,不只要删除文件本身,还需要把文件的变动添加到暂存区,然后提交。
使用以下命令删除文件:
git rm
这个命令会把文件从工作目录中删除,同时将文件的变动添加到暂存区,准备下次提交。
git rm
命令后面可以列出文件或者目录的名字,也可以使用 glob 模式匹配多个文件。
如果只是简单地删除工作目录中的文件,运行 git status
后,会出现以下信息:
$rm readme.md
$git status
On branch master
Changes not staged for commit:
(use "git add/rm ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
deleted: readme.md
Git 提示有文件的修改没有暂存,使用 git rm
或 git add
命令将文件的变动添加到下次提交。
可以看到提示里的 changes
不止意味着文件内容的修改,也包括文件本身的变动,包括删除、移动、重命名之类。
如果误删了某个文件,那么可以通过 git restore
命令恢复误删的文件。
如果要删除之前修改过或已经放到暂存区的文件,则必须使用强制删除选项 -f
:
git rm -f
如果要把文件从 Git 仓库中删除,但仍然希望保留在当前工作目录中,即只是不让 Git 继续管理这个文件,可以使用 --cached
选项:
git rm --cached
要移动文件或重命名文件,使用以下命令:
git mv
这个命令把文件进行移动,同时把文件的变动添加到暂存区,相当于执行以下三个命令:
mv # 移动文件
git rm # 删除旧文件
git add # 添加新文件
但 git mv
命令无法将文件移动到工作目录外,如果是把工作目录中的文件移动到工作目录外,那就相当于是从 Git 仓库中删除。
使用以下命令查看提交历史:
git log [options] [ ...]
如果不加其它选项,默认情况下,这个命令按提交的先后顺序由近到远显示提交历史,包括每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明等信息。
如果加文件名作为参数,那么就会只查看跟某个文件有关的提交历史;
如果指定某次提交的校验和或者标签名作为参数,那么就会查看指定提交或标签之前的提交历史。
这个命令有很多选项,常用的如下:
选项 | 作用说明 |
---|---|
-n(n 为一个整数) | 指定显示最近的 n 次提交 |
-p 或 --patch | 显示每次提交所引入的差异 |
–name-only | 仅在提交信息后显示已修改的文件清单 |
–name-status | 显示新增、修改、删除的文件清单 |
–stat | 显示每次提交的简略统计信息 |
–graph | 使用 ASCII 字符形象的展示分支、合并历史等 |
–abbrev-commit | 仅显示 SHA-1 校验和所有 40 个字符中的前几个字符 |
–relative-date | 使用较短的相对时间而不是完整格式显示日期(比如“2 weeks ago”) |
–pretty= | 使用指定格式显示历史提交信息,常用的选项包括 oneline、short、full、fuller 或 format(用来定义自己的格式) |
–oneline | –pretty=oneline --abbrev-commit 合用的简写 |
–no-merges | 不显示合并提交 |
–since, --after | 仅显示指定时间之后的提交 |
–until, --before | 仅显示指定时间之前的提交 |
查看某两次提交之间的提交历史或两个分支之间的提交的区别 | |
查看在分支1上或在分支2上的提交历史 | |
–author | 仅显示作者匹配指定字符串的提交 |
–committer | 仅显示提交者匹配指定字符串的提交 |
–grep | 仅显示提交说明中包含指定字符串的提交 |
-S | 仅显示添加或删除内容匹配指定字符串的提交 |
例如:
(1)只查看最近的 3 次提交:
git log -3
或:
git log -n 3
(2)如果想要查看每次提交的文件增删数量,使用 --stat
选项:
git log --stat
(3)如果想要查看每次提交中文件的详细修改内容,使用 -p
选项:
git log -p
(4)如果嫌输出信息太多,可以加一个 --oneline
参数:
git log --oneline
相当于:
git log --pretty=oneline --abbrev-commit
这样每次提交的信息就会在一行当中显示,大大简化输出。
(5)合并分支的时候经常产生合并提交,这些合并提交可能对我们查看过去工作并无用处,但会让提交历史变得很长,可以使用 --no-merges
选项在查看提交历史使去掉合并提交:
git log --no-merges
当然也可以特别关注这些合并提交,使用 --merges
选项查看合并到当前分支的合并提交历史:
git log --merges
(6)只显示 2 周以来的提交:
git log --since='2 weeks'
(7)只查看标签 v1.0 之前的提交历史:
git log v1.0
(8)查看标签 v1.0 之后的提交历史:
git log v1.0..
(9)查看某两次提交之间的提交历史:
git log v1.0..v2.0
这个命令查看 v1.0 和 v2.0 之间的提交,但不包括 v1.0,注意 v1.0 和 v2.0 之间用两个点(…)连接。
(10)查看在 feature 分支,但不在 master 分支的提交:
git log master..feature
(11)只查看跟某个文件有关的提交历史:
git log -- test.txt
--
告诉 Git 后面的参数是文件名,而不是分支名,当要指定文件名作为参数时,一般要放在命令的最后。
可以使用 git log --pretty=format:
命令自定义提交历史的显示格式,
一般是一些格式占位符,表明要显示的内容,常用格式占位符写法如下:
选项 | 作用说明 |
---|---|
%H | 提交的完整哈希值 |
%h | 提交的简写哈希值 |
%T | 树的完整哈希值 |
%t | 树的简写哈希值 |
%P | 父提交的完整哈希值 |
%p | 父提交的简写哈希值 |
%an | 作者名字 |
%ae | 作者的电子邮件地址 |
%ad | 作者修订日期(可以用 --date=选项 来定制格式) |
%ar | 作者修订日期,按多久以前的方式显示 |
%cn | 提交者的名字 |
%ce | 提交者的电子邮件地址 |
%cd | 提交日期 |
%cr | 提交日期(距今多长时间) |
%s | 提交说明 |
例如,想要在查看提交时,显示提交的简写哈希值、作者名字、提交时间和提交说明,可以这样写:
git log --pretty=format:"%h %an %cd %s"
也可以在
中加入直接显示的普通字符,例如:
git log --pretty=format:"%h-%an %cd : %s"
这个命令会在哈希值和作者名字之间加一个短横线,在提交说明前面加一个冒号。
在 Git 中可以给命令配置别名,简化输入,比如在终端输入:
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.unstage 'reset HEAD'
git config --global alias.last "log -1"
这样,以后就可以用 git st
代替 git status
,用 git co
代替 git checkout
,当然也可以自己设置其它别名。
--global
参数表示全局设置,也就是这些设置对当前用户的所有 Git 仓库都适用,如果不加,就表示只是为当前仓库设置命令的别名,只对当前仓库起作用。
可以运行以下命令查看当前的设置:
git config --list
每个仓库的 Git 配置文件都放在.git/config
文件中,而当前电脑用户的 Git 配置文件放在用户主目录下的一个隐藏文件 .gitconfig
中,别名的设置都跟在 alias
的后面,如果要删除某个别名的设置,只需要将相关条目删掉即可。
别名可以后期根据自己的使用习惯进行添加。
git diff []
这个命令比较工作区和暂存区中文件的差异,如果指定文件名,就只比较指定的文件。
git diff --cached [] []
或:
git diff --staged [] []
这个命令比较暂存区和指定提交(可以是提交的校验和,也可以是标签、分支名等)中文件的差异,如果不指定提交,默认为 HEAD
也就是和当前提交进行比较。
git diff []
这个命令比较工作区和指定提交中文件的差异。
git diff HEAD []
这个命令比较工作区和当前提交之间的差异。
git diff
这个命令比较 2 次提交之间的差异。
Git 远程仓库是指托管在因特网或其他网络中的版本库。
Git 是分布式版本控制系统,在多人协作的情况下,尽管在技术上可以从(某个)个人仓库进行推送和拉取数据,但这样做会非常不方便,一方面很容易弄混每个人的进度,另一方面如果那人不在线,其他人便无法推送和拉取,为了便于多人协作,一般会搭建一个服务器,在服务器上建立公用仓库,每个人都从服务器上克隆仓库到本地进行工作,并且各自将成熟的提交推送到公用仓库。这个公用仓库对每个人来说就是远程仓库,为了更好的进行协作,必须知道如何使用远程仓库。
使用以下命令添加远程仓库,同时为远程仓库指定一个本地的简写 shortname
:
git remote add
这样,以后就可以使用这个简写 shortname
代替远程仓库的路径 url
来进行推送(push)或拉取(pull)等操作。
shortname
在本地也时常称作 “远程仓库名”,但它实际上只是在本地给远程仓库的路径 url
指定的一个简写,与服务器上真正的远程仓库的名字是不同的,下文中的远程仓库名基本都是这种情况,下文中也使用 remote-name
指代这种远程仓库的简写。如果本地仓库是通过运行 git clone
命令从远程仓库克隆来的,那么在克隆的同时,就已经自行添加了远程仓库并设置默认的远程仓库简写 shortname
是 origin
。
要查看远程仓库的信息,使用以下命令:
git remote
会显示已经添加的远程仓库的简写。
如果要查看更详细的信息,则要加一个 -v
选项:
git remote -v
这个命令会显示各个远程仓库的地址,并显示抓取和推送的权限。
如果要查看某一个远程仓库的信息,使用以下命令:
git remote show
remote-name
是指远程仓库在本地的简写,这个命令必须能够连接远程仓库才能生效,不然会出错。
使用以下命令从远程仓库获得数据:
git fetch [] []
这个命令会访问远程仓库,从中抓取所有本地没有的数据,默认情况下,命令取回所有分支的更新,也可以通过指定分支名 branch-name
只取回特定分支的更新内容。
必须注意这个命令只会将数据下载到本地仓库(存储为远程跟踪分支),它并不会自动合并或修改当前的工作,必须手动将其合并或修改。例如:
git fetch origin
这个命令会把远程仓库 origin
的更新取回本地,然后在本地可以通过 origin/master
查看抓取下来的内容,例如将抓取下来的远程分支合并到本地分支:
git merge origin/master
origin/master
叫做远程跟踪分支,是对远程分支状态的引用,详见“分支管理”部分。
如果当前分支设置了跟踪远程分支(见下文的“分支管理”部分),那么以下命令可以自动抓取该远程分支更新的数据并合并到当前分支:
git pull []
git clone
命令会自动设置本地 master
分支跟踪远程仓库的 master
分支(或其它名字的默认分支),后期可以用 git pull
命令自动更新数据。在默认情况下,git pull
命令在合并分支的时候采用的是 git merge
的方式,可以通过指定 --rebase
选项,指定采用 git rebase
的方式合并分支:
git pull --rebase []
也可以直接设置运行 git pull
命令时自动采用 --rebase
选项:
git config --global pull.rebase true
使用以下命令将本地的工作推送到远程仓库:
git push []
branch-name
是要推送到远程的本地的分支名。
比如,在本地工作了一段时间后,完成了一个稳定版本,已经将它合并到了本地的 master
分支,现在需要将它推送到远程仓库 origin
,就可以使用以下命令:
git push origin master
这个命令实际上是推送本地的 master
分支到远程仓库 origin
,作为远程仓库的 master
分支。当然,也可以推送本地分支到一个名字不同的远程分支,见下文的“分支管理”部分。
使用以下命令修改远程仓库的简写:
git remote rename
使用以下命令从本地删除远程仓库:
git remote remove
一旦使用这种方式删除了一个远程仓库,那么所有和这个远程仓库相关的远程跟踪分支以及配置信息也会一起被删除。
使用分支可以把工作从主线上分离出来,避免对主线的影响,也可以多个功能同时开发,互不影响,极大地提高开发的便捷。Git 处理分支的方式非常轻量,创建、切换分支都非常快速简便,是 Git 的一大优势。
Git 的分支,本质上是指向提交对象的可变指针,Git 的默认分支名是 master
,使用 git init
命令初始化仓库时会自动创建 master
分支,master
分支会在每次提交时自动向前移动以指向最新的提交。
使用以下命令查看分支有关信息:
git branch # 查看本地分支
git branch -l # 查看本地分支
git branch -r # 查看远程分支
git branch -a # 查看所有分支
在当前分支前会有一个*
标记。
如果要查看每个分支的最后一次提交,需要加一个 -v
选项:
git branch -v
创建分支:
git branch []
这个命令默认从当前提交创建分支,可以指定 start-point
,也就是从哪个提交创建分支,start-point
可以是提交的校验和或者标签名等。
切换当前分支:
git checkout
或:
git switch # 新版本的 Git 提供此命令
创建并切换分支:
git checkout -b []
或:
git switch -c [] # 新版本的 Git 提供此命令
git merge [options]
这个命令可以把分支 branch-name
合并到当前分支上。
当试图合并两个分支时,如果顺着一个分支走下去能到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧,这时合并的方式就是 fast-forward
(快进);如果两个分支从较早的地方开始分叉,也就是一个分支所在的提交并不是另一个分支的直接祖先,那么这时会采用合并提交的方式,Git 会结合两个分支末端和它们的共同祖先,做一个三方合并,如果合并时两个分支有冲突,需要先解决冲突,再合并,Git 会为合并的结果自动创建一个新的提交(同样也需要输入提交信息),此时这个提交就不止有一个父提交。
通常,合并分支时,Git 会尽量使用 Fast forward
模式,但这种模式下,删除分支后会丢掉分支信息。如果要强制禁用 Fast forward
模式,Git 就会在 merge
时生成一个新的 commit
,这样,从提交历史上就可以看出分支信息。
禁用 Fast forward
模式,要使用 --no-ff
选项:
git merge --no-ff -m "message"
因为禁用 Fast forward
模式,合并时会生成一个新的提交,所以要加一个 -m
选项。
可以使用 --merged
或 --no-merged
选项查看已经合并或尚未合并到当前分支的分支:
git branch --merged # 查看已经合并到当前分支的分支
git branch --no-merged # 查看尚未合并到当前分支的分支
git branch --merged dev # 查看已经合并到 dev 分支的分支
git branch -m
将当前分支重命名为 new-branch-name
。
删除分支:
git branch -d
如果要删除一个没有被合并过的分支,要把 -d
选项改为 -D
,表示强制删除:
git branch -D
远程跟踪分支 是远程分支状态的引用,它们是无法手动移动的本地引用,类似于标签。
远程跟踪分支以
的形式命名,每次运行 git fetch
命令从远程抓取数据时,Git 都会将远程跟踪分支更新到和远程仓库一样的状态。
通过远程跟踪分支,可以看出本地分支与远程分支的差距,例如,运行 git log --oneline
命令,就可以看到当前分支指向哪一个提交,而对应的远程分支指向哪一个提交,可以看到当前分支比远程分支领先多少。
git checkout -b /
命令在它的基础上创建分支,然后在新创建的分支上工作。跟踪分支 是与远程分支有直接关系的本地分支,如果在一个跟踪分支上运行不加参数的 git push
或 git pull
命令,Git 可以自动识别推送到相关联的远程分支或从远程分支抓取数据。
从一个远程跟踪分支上创建一个本地分支会自动设置跟踪分支,例如:
git checkout -b dev origin/dev
这个命令在远程跟踪分支 origin/dev
上创建并切换到本地分支 dev
,创建分支时,Git 自动设置 dev
分支跟踪远程仓库 origin
的 dev
分支。
git clone
命令克隆仓库时,Git 会自动创建一个 master
分支跟踪远程仓库的 master
分支。也可以设置已有的本地分支跟踪某个远程分支,使用 -u
或 --set-upstream-to
选项,命令如下:
git branch --set-upstream-to= <本地分支名>
或:
git branch -u <本地分支名>
如果要查看设置的所有跟踪分支,使用带 -vv
选项的 git branch
命令:
git branch -vv
如果想要与别人分享某个分支,必须显式的推送分支到远程仓库,Git 并不会自动将分支与远程仓库同步。
使用以下命令推送分支:
git push [] []
这个命令推送分支时,远程分支和本地分支名字是一样的,也可以推送本地分支到一个名字不同的远程分支:
git push 本地分支名:远程分支名
可以使用 -u
选项在推送分支的同时设置当前分支跟踪远程分支:
git push -u
如果想要推送所有本地分支到远程,使用 --all
选项:
git push --all
Git 可以给重要的提交打上标签,比如版本号更新的时候创建不同版本的标签,便于以后回顾。
Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。
轻量标签很像一个不会改变的分支,它只是某个特定提交的引用。
附注标签是存储在 Git 数据库中的一个完整对象,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。
git tag -a -m "message" []
这个命令中,-a
选项表明要创建附注标签,tag-name
是要创建的标签的名字,-m
选项指定标签的说明,如果不加 -m
选项,则会打开文本编辑器输入标签说明;
默认情况下,这个命令是在当前提交创建标签,但也可以指定 commit
指定要给哪一个提交创建标签,commit
是指提交的校验和或者部分校验和。
git tag []
创建轻量标签不需要加 -a
或 -m
选项,这个命令可以针对某次提交 commit
创建一个名为 tag-name
的标签,如果不指定 commit
,会在当前提交上创建标签。
commit
挂钩。如果这个 commit
既出现在某一个分支,又出现在另一个分支,那么在这两个分支上都可以看到这个标签。git tag
这个命令按字母顺序显示所有标签,如果要显示特定的标签,就需要加 -l
或 --list
选项,后面加标签名的匹配模式,如:
git tag -l "v1.8*"
这个命令会匹配所有标签名里以 v1.8
开头的标签。
如果要查看标签的详细信息,使用以下命令:
git show
这个命令会显示标签名、打标签的人、打标签的时间、以及标签指向的提交等信息。
git show
命令可以显示 Git 中各种对象的信息。对于提交,它显示提交信息和文本差异;对于标签,它显示标签信息和引用对象;默认情况下,不加参数的 git show
命令相当于 git show HEAD
。默认情况下,git push
命令并不会主动推送标签到远程仓库服务器上,如有必要,需要主动推送标签到远程仓库。
使用以下命令推送指定标签到远程仓库:
git push
这个命令用于推送某个标签到远程仓库,remote-name
是指远程仓库在本地的名字。
也可以同时推送多个标签:
git push --tags
这个命令会推送全部未推送过的标签到远程仓库。
删除本地的标签:
git tag -d
删除远程标签:
git push --delete
有时在某一个分支上工作一段时间后,因某些原因必须切换到另一个分支上工作,但又不想将当前分支做了一半的工作提交,那么就可以使用到贮藏功能。
Git 贮藏功能类似于一个临时的储存区域,可以把当前工作区和暂存区中还没有提交的修改保存起来,让工作区变得干净,等以后可以恢复贮藏的内容继续工作。
git checkout
命令切换分支时会失败。命令如下:
git stash
或:
git stash push
要查看当前贮藏起来的工作,使用以下命令:
git stash list
这个命令会显示当前所有贮藏起来的工作,贮藏的工作可以同时有多个,每个贮藏有一个 stash@{n}
形式的名字,n
为从 0 开始的编号,越大意味着越新,并且每次删除一个贮藏的工作,名字都会更新一次,比如删除了 stash@{0}
,那么 stash@{1}
会变成 stash@{0}
。
使用以下命令恢复(apply
)贮藏起来的工作:
git stash apply [stash@{n}]
如果不指定贮藏的名字 stash@{n}
,那么就会恢复最近贮藏的工作。
在恢复贮藏的工作时,并不会把之前暂存的文件重新暂存,而是放回到工作区,变成已修改但未暂存的状态,如果要在恢复工作的同时恢复暂存的文件,需要使用 --index
选项:
git stash apply --index [stash@{n}]
删除指定的贮藏:
git stash drop stash@{n}
应用并删除指定的贮藏:
git stash pop stash@{n}
恢复贮藏的工作后,一般都要将贮藏的工作删除。
在 Git 中,用 HEAD
表示当前版本,也就是当前所在的提交,上一个版本就是 HEAD^
,上上一个版本就是 HEAD^^
,更往上用 HEAD~n
,比如往上100个版本,就用 HEAD~100
。
HEAD
是一个指针,它指向哪里,就表示当前版本是哪个,比如在切换分支时,Git 会自动将 HEAD
指向目标分支的最新提交。那么,回退到上一个版本,使用以下命令:
git reset --hard HEAD^
这个命令同时把工作区和暂存区的内容也进行相应更新。
回退到某一个版本(可以指定提交的校验和或标签名),使用以下命令:
git reset --hard []
这个命令如果不指定提交,默认是 HEAD
,相当于:
git reset --hard HEAD
此时会将工作区和暂存区的文件变得跟当前提交一致,那么也就是放弃自从上次提交以来,当前工作区和暂存区的所有修改。
Git 使用 reflog
(引用日志) 来记录本地分支顶端的更新历史,reflog
完全存储在本地。
使用以下命令查看 reflog
:
git reflog
这个命令相当于:
git reflog show HEAD
这两个命令会按照时间的先后顺序显示 HEAD
的更新历史。
也可以显示具体分支的顶端的更新历史:
git reflog show
git reflog
经常用于恢复错误的版本回退操作,比如使用 git reset --hard
回退到了一个早期的版本,但发现回退的太多了或者根本不需要回退,我们想要恢复到较新的版本,这时如果使用 git log
命令已经不能看到较新版本的 commit
了,那么就可以使用 git reflog
命令查看 HEAD
的更新历史,依然可以看到较新版本的校验和,然后就可以使用 git reset --hard
命令恢复到较新版本。
git log
与 git reflog
的区别:
git log
显示提交的历史记录,它截止到当前 HEAD
指向的位置为止,也就是显示当前 HEAD
指向的提交和这个提交的祖先,也就是它的父提交,父提交的父提交等等。git reflog
按照时间的先后顺序显示 HEAD
的更新历史,也就是显示 HEAD
曾经指向的提交的一个历史记录列表,它不是去寻找 HEAD
的祖先,它是一个类似于 undo
的历史。比如,当前仓库有三个提交:
e0c426c
211cc78
2862b34
当前 HEAD
指向最新的提交 e0c426c
,现在先运行 git reset --hard 2862b34
回退到提交 2862b34
,再运行 git reset --hard 211cc78
恢复到 211cc78
提交,此时运行 git log
会显示两个提交的信息:
211cc78
2862b34
运行 git reflog
会显示五个提交的历史记录:
211cc78
2862b34
e0c426c
211cc78
2862b34
注意这个例子中的显示内容进行了简化,真正运行时还会显示其它关于提交的信息。