Git版本控制管理
前言¶
环境¶
- 操作系统:CentOS8 on Docker(homqyy/dev_env_centos8)
- Git版本: 2.31.1
约定¶
- 对于例图有如下约定:
- 圆形:提交(commit)对象
- 三角形:树(tree)对象
- 矩形:内容(blob)对象
- 平行四边形:标签(tag)对象
- 圆角矩形:分支
第1章 介绍¶
- 优点
- 有助于分布式开发
- 能够顾胜任上千开发人员的规模
- 性能优异
- 保持完整性和可靠性
- 强化责任
- 不可变性
- 原子事物
- 支持并鼓励基于分支的开发
- 完整的版本库
- 一个清晰的内部设计
- 免费自由
第2章 安装Git¶
- 无
第3章 起步¶
3.1 帮助¶
- 查看
git
支持的子命令:git
git --help
-
查看子命令的使用信息:
git help _subcommand_
git --help _subcommand_
git _subcommand_ --help
-
每个命令都有对应的连字符命令,比如
git commit
等效于git-commit
- 使用“裸双破折号”(
--
)来分离一系列参数:
# checkout the tag named "main.c"
git checkout main.c
# checkout the file named "main.c"
git checkout -- main.c
3.2 创建版本库¶
-
创建初始版本库
- 执行
git init
时会在版本库的根目录下生成一个.git/
的隐藏目录,由Git维护
- 执行
-
将改变提交到版本库中:
-
为了避免频繁的改动,Git将提交到版本库的动作分为两个步骤,分别为“暂存(
add
)”和“提交(commit
)”-
暂存改动
-
提交改动
git commit -m "Initial contents of git example" --author="homqyy <homqyy@example.com>" # [master (root-commit) 1896487] Initial contents of git example # Author: homqyy <homqyy@example.com> # 1 file changed, 0 insertions(+), 0 deletions(-) # create mode 100644 README.md
commit
除了可以通过-m
参数来编辑提交日志外,还可以通过编辑器的方式编辑,只需要设置环境变量GIT_EDITOR
即可:- 比如在Bash中:
export GIT_EDITOR=vim
- 比如在Bash中:
-
-
-
Git会尽力确定每一次提交的作者,设置方法:
- 命令:
- 姓名:
git config user.name "homqyy"
- 邮箱:
git config user.email "homqyy@example.com
- 姓名:
- 环境变量:
- 姓名:
GIT_AUTHOR_NAME
;邮箱:GIT_AUTHOR_EMAIL
- 姓名:
- 命令:
-
查看提交日志:
-
执行
git log
命令查看: -
查看详细信息,执行
git show
命令查看:git show # commit 33c79c6038978aad4bed37f3bb6b0277bd74ca06 (HEAD -> master) # Author: homqyy <homqyy@example.com> # Date: Wed Aug 3 15:40:07 2022 +0000 # # add head 2 # # diff --git a/README.md b/README.md # index e59c974..fda9cda 100644 # --- a/README.md # +++ b/README.md # @@ -2,3 +2,5 @@ # # ## head 1 # # +## head 2 # +
- 查看特定提交的信息:
git show 189648713da8d48ed9360cac1395341db80d6271
- 查看特定提交的信息:
-
查看当前分支的概要信息,执行
git show-branch
命令查看:git show-branch --more=10 # [root@3b05e5c9c2d3 git_example]# git show-branch --more=10 # [master] add head 2 # [master^] add head 1 # [master~2] first commit
--more
:展示近期最多10个版本
-
-
查看提交差异:
-
删除文件:
git rm example.txt
-
重命名文件:
-
用
mv
子命令: -
先删除,后添加:
-
-
创建版本副本:
3.3 配置文件¶
-
配置文件路径:
- 版本库配置:
.git/config
;可用--file
选项修改,是默认选项 - 用户配置:
~/.gitconfig
;可用--global
选项修改 - 系统配置:
/etc/gitconfig
;可用--system
选项修改
- 版本库配置:
-
设置配置:
git config ...
- 移除配置:
git config --unset ...
- 别名:
- 设置别名
show-graph
:git config --global alias.show-graph 'log --graph --abbrev-commit --pretty=oneline'
- 使用别名:
git show-graph
- 设置别名
第4章 基本的Git概念¶
4.1 基本概念¶
-
Git维护两个主要的数据结构:
对象库(object store)
:复制时,是完整的索引(index)
:暂时的信息,私有的
-
Git对象类型:
- 块(
blob
,binary large object):文件的每个版本视为一个blob
。仅保存文件中的数据,不包含文件的任何元数据。 - 目录树(
tree
):一个tree
对象代表一层目录信息。tree
记录了“blob
标识符”、“路径名”和“在一个目录里所有文件的一些元数据”,同时也能引用其他的tree对象。 - 提交(
commit
):一个commit
对象保存版本库中每一次变化的元数据,包括“作者”、“提交者”、“提交日期”和“日志消息”。每个commit
对象指向一个tree
对象。除了根root commit
外,其余commit
都有parent commit
。 - 标签(
tag
):一个tag
对象分配一个任意的且人类可读的名字给一个特定对象,通常是一个commit
对象。
- 块(
-
索引:索引是一个临时的,动态的二进制文件,它捕获项目在某个时刻的一个整体结构。项目状态包括
commit
和tree
,它可以来自项目历史中的任意时刻,或则它可以是你正在开发的未来状态。 -
可寻址内容名称:Git通过SHA1来散列存储对象的内容,并将其作为对象的ID。(SHA1是一个160位的数,通常用40位的16进制表示)
-
Git追踪内容:
- Git不仅是一个VCS,同时还是一个内容追踪系统(content tracking system)。
- Git追踪内容表现在:
- Git的对象基于其对象内容的散列计算的值,而不是基于用户原始文件布局的文件名或目录名设置。Git追踪的是内容而不是文件。因此如果两个文件内容完全一样,Git在对象库里只保存一份
blob
形式的内容副本。 - Git存储的是 每个文件的每个版本,而不是他们的“差异”。Git通过文件内容的散列值来作为文件名,因此Git是用不同散列值的blob之间的区别来计算这个历史。
- Git的对象基于其对象内容的散列计算的值,而不是基于用户原始文件布局的文件名或目录名设置。Git追踪的是内容而不是文件。因此如果两个文件内容完全一样,Git在对象库里只保存一份
-
Git将路径名也作为一个数据进行存储,因此文件路径与文件内容在Git中是分离的。
- 打包文件(pack file):
- Git通过pack file进行存储,即它会计算非常相似的全部文件,并为它们之一存储一个完整内容,之后计算相似文件之间的差异,并且只存储差异。
- 由于Git是内容驱动的,因此它可以不用在乎路径,对所有文件进行差异分析。
- pack file跟对象库中的其他对象存储在一起。
4.2 对象库¶
4.3 Git在工作时的概念¶
.git/objects/
目录用来存储所有对象-
SHA1会产生160位数,因此用40字节的16进制串表示。Git咋前面两个数字后面插入一个
/
以提高文件系统效率(如果你把太多的文件放在同一目录中,一些文件系统会变慢);使用SHA1的第一个字节称为一个目录是一个很简单的办法,可以为所有均匀分布的可能的对象创建一个固定的、256(\(16^2\))路分区的命名空间。 -
查看对象内容:
git cat-file -p <Object ID>
- 每次执行命令(比如:
git add
、git rm
或则git mv
)的时候,Git并不会立刻创建tree
,而是用新的路径名和blob
信息去更新index
。 - 任何时候都可以从当前
index
创建一个tree
对象。用命令git write-tree
来捕获当前index
的信息去创建tree
对象,并打印到屏幕上。 - 使用
git ls-files
命令可以查看当前index
和work
的文件信息git ls-files -s
查看暂存后的内容
- 使用
git commit-tree -m <message> <tree ID>
去创建一个commit
对象,并输出到屏幕上。 -
使用
git commit -m <message>
将改变提交到版本库中 -
标签(
tag
):- 两种类型:
- 轻量级的(lightweight):知识一个提交对象的引用,通常被版本库视为是私有的。这些标签并不在版本库中创建永久对象。
- 带附注的(annotated):会创建对象,并可以提供一条消息。可以根据“RFC 4880”来使用GnuPG密钥进行数字签名。
- Git在命名一个
commit
的时候对轻量级的tag
和带附注的tag
同等对待。 - 创建
tag
:git tag -m <message> <tagname> <commit>
- 删除
tag
:git tag -d <tagname>
- 查看tag ID:
git rev-parse <tagname>
- 两种类型:
第5章 文件管理和索引¶
-
Git在工作目录和版本库之间加设了一层
index
,用来暂存(stage)、收集或者修改。当你使用Git来管理代码时,你会在工作目录下编辑,在索引中积累修改,然后把索引中积累的修改作为一次性的变更来进行提交。 > Linux Torvalds在Git邮件列表里曾说,如果不先了解索引的目的,你就不能完全领会Git的强大之处。 -
任何时候都可以通过
git status
查看索引状态 git diff
可以用来查看两个commit
的差异,或work
与commit
,或index
与commit
5.1 Git中的文件分类¶
- 已追踪的(Tracked):已经在
index
或版本库中的文件 - 被忽略的(Ignored):被版本库主动声明为忽略的文件(
.gitignore
、.git\info\exclude
) - 未追踪的(Untracked):不在前两类中的文件(在工作目录中,但未被忽略,也未被加入到
index
或版本库中的文件)
5.2 git add¶
git add
用来暂存一个文件内容,如果作用的是文件夹则将该目录下的文件和子目录递归暂存。- 在发出
git add
命令时每个文件的全部内容都将被复制到对象库中,并且按文件的SHA1名来索引。暂存一个文件也称作缓存(caching)一个文件,或则叫“把文件放进索引” - 通过命令
git ls-files --stage
可以查看当前索引快照下的文件的SHA1值。 - 通过命令
git hash-object <file>...
可以计算文件的hash值
5.4 git commit 注意事项¶
-
自动暂存所有未暂存和未追踪的文件内容:
git commit --all ...
- 但是如果有整个目录未追踪的情况,则不会自动暂存该目录
-
通过编辑器编写
commit
日志的时候,如果不想提交了,可以直接退出。或则把已经保存的内容删除后保存退出即可。
5.3 git rm¶
git rm <file>
可以从索引或则同时从索引和工作目录中删除一个文件。git rm --cached <file>
会删除索引中的文件,但不会删除工作目录,等效于git add <file>
的反向操作。- Git在删除一个文件之前,会先确保工作目录下的该文件的版本与当前分支中的最新版本(HEAD)是匹配的。这个验证可以防止文件的修改丢失。
5.4 git mv¶
-
git mv <old> <new>
用来重命名一个文件,其等效于以下3个步骤:mv old new
git rm old
git add new
-
重命名一个文件并提交到版本库后,通过
git log <file>
查看日志,会发现无法查看重命名之前的记录,此时可以增加--follow
参数来查看:git log --follow <file>
> VCS的经典问题之一就是文件重命名会导致它们丢失对文件历史记录的追踪,而Git即使经历过重命名,也仍然能保留此信息。
5.5 git clean (自增)¶
git clean \<path>
:丢弃在path
下的改动。常用来丢弃工作目录中的改动
5.6 .gitignore¶
-
.gitignore
文件用来告诉Git需要忽略哪些文件- 可以放置于于任何目录中。
- 每个文件都只影响同级目录及其子目录。
- 级联的,可以覆盖高层目录中的规则(取反的话会覆盖掉高层目录中的正向结果)
-
书写格式支持模式,文件中的每一行都为一条规则:
- 空行会被忽略,而以
#
开头的行可以用于注释。 - 一个简单的字面文件名匹配任何目录中的同名文件。
- 目录由末尾的
/
标记。这样能匹配同名的目录和子目录,但不匹配文件或符号链接。 - 包含shell通配符。(如
*
,可以通配任何字符,但注意,它不能跨越目录) -
起始的
!
会对该行其余部分的模式进行取反。此外,被之前模式排除,但被取反规则匹配的文件是要被包含的。它会覆盖低优先级的正向结果。
- 空行会被忽略,而以
-
优先级从高至低:
- 在命令行上指定的模式
- 从相同目录的.gitignore文件中读取的模式
- 从上层目录中的模式,向上进行。因此当前目录的模式能推翻上层目录的模式,而最接近当前目录的上层目录的模式优先于更上层的目录的模式。
- 来自
.git/info/exclude
文件的模式 - 来自配置变量
core.exludefile
指定的文件中的模式。
-
.gitignore
会随着版本传播,但.git/info/exclude
不会。因此一般情况下,只有当模式普遍适用于所有派生的版本库时,才应该把条目放进版本控制下的.gitignore
文件中。否则,那些仅适用于与自身版本库的模式,应当写入.git/info/exclude
。
5.7 对象模型¶
- 无
第6章 提交¶
- 提交是将变更引入版本库的唯一方法,任何版本库中的变更都必须由一个提交引入。
- 虽然最常见的提交情况是由开发人员引入的,但是Git自身也会引入提交(比如合并)。
- 对于提交不要有任何负担,Git非常适合频繁的提交,并且它还提供了丰富的命令集来操作这些提交。
- 提交是一个原子性的操作,一张提交快照代表所有文件和目录的变更,它代表一棵树的状态,而两张提交快照之间的变更集就代表一颗完整树到树的转换。
- Git还会记录每个文件的可执行模式标识。标识的改变也算是变更的一部分。
- Git的提交有“显示”和“隐式”两种引用,显示表达为ID,隐式如
HEAD
6.1 识别提交¶
-
绝对提交名
-
每一个提交的散列ID都是全局唯一的,不仅仅是对某个版本库,而且是对任意和所有版本库都唯一。(如果连个版本库中的ID是一致的,说明他们有一个相同内容的相同提交)
git log -1 --pretty=oneline HEAD
git log -l --pretty=oneline 33c79c6038978aad4bed37f3bb6b0277bd74ca06
- Git允许用前缀来表达ID:
git log -1 33c79c
-
引用和符号引用
- 定义
- 引用(
ref
)是一个SHA1散列值,指向Git对象库中的对象。虽然ref
可以指向对象,但是通常指向commmit
对象。 - 符号引用(
symref
, symbolic reference)间接指向Git对象。
- 引用(
- 类别
- 本地特性分支名称:
refs/heads/ref/
- 远程跟踪分支名称:
refs/remotes/ref/
- 标签名:
refs/tags/ref/
- 本地特性分支名称:
-
使用引用的时候可以用全称也可以用简称,当使用简称出现冲突时,会按如下优先级搜索,从高到底:
- If
$GIT_DIR/<refname>
exists, that is what you mean (this is usually useful only forHEAD
,FETCH_HEAD
,ORIG_HEAD
,MERGE_HEAD
andCHERRY_PICK_HEAD
); - otherwise,
refs/<refname>
if it exists; - otherwise,
refs/tags/<refname>
if it exists; - otherwise,
refs/heads/<refname>
if it exists; - otherwise,
refs/remotes/<refname>
if it exists; - otherwise,
refs/remotes/<refname>/HEAD
if it exists.
-
From Manual
git rev-parse --help
:HEAD
names the commit on which you based the changes in the working tree.FETCH_HEAD
records the branch which you fetched from a remote repository with your last git fetch invocation.ORIG_HEAD
is created by commands that move yourHEAD
in a drastic way, to record the position of theHEAD
before their operation, so that you can easily change the tip of the branch back to the state before you ran them.MERGE_HEAD
records the commit(s) which you are merging into your branch when you run git merge.CHERRY_PICK_HEAD
records the commit which you are cherry-picking when you rungit cherry-pick
.- Note that any of the refs/* cases above may come either from the
$GIT_DIR/refs
directory or from the$GIT_DIR/packed-refs
file. While the ref name encoding is unspecified, UTF-8 is preferred as some output processing may assume ref names in UTF-8.
-
<refname>
可以是:master
、heads/master
,refs/heads/master
- 从技术角度来说,Git的目录名
.git
是可以改变的,因此内部文档都使用$GIT_DIR
表示。 - 特殊符号引用:这些引用可以在使用提交的任何地方使用 HEAD
:HEAD始终指向当前分支的最近提交ORIG_HEAD
:某些操作,例如合并(merge
)和复位(reset
),会把HEAD
改为最新值前,将其值赋值给ORIG_HEAD
FETCH_HEAD
:使用远程库时,git fetch
命令将所有抓取分支的头记录到.git/FETCH_HEAD
中,并且仅在刚刚抓取操作之后有效。MERGE_HEAD
:当一个合并操作正在进行时,~其他分支的头暂时记录在MERGE_HEAD中~将那些准备合入到你分支的提交记录到此头部中。
- If
- 定义
-
-
相对提交名称:
-
- 第一个父提交:
C^1
、C^
-
第一个父提交的第一个父提交:
C^1^1
、C^^
(等效于C~2
) -
前1提交:
C~1
、C~
- 前2提交:
C~2
、C~~
- 注意:这些名字都相对于引用的当前值。如果当前引用指向一个新提交,那么提交历史图将变为“新版本”,所有父提交“辈分”都会上升一层。
- 示例:在git源码中执行
git show-branch --more=35 | tail -10
[root@3b05e5c9c2d3 git]# git show-branch --more=35 | tail -10 [master~14] Merge branch 'ds/rebase-update-ref' [master~14^2] sequencer: notify user of --update-refs activity [master~15] Merge branch 'kk/p4-client-name-encoding-fix' [master~15^2] git-p4: refactoring of p4CmdList() [master~16] Sync with 'maint' [master~16^2] Downmerge a handful of fixes for 2.37.x maintenance track [master~16^2^] Merge branch 'tk/rev-parse-doc-clarify-at-u' into maint [master~16^2^^2] rev-parse: documentation adjustment - mention remote tracking with @{u} [master~16^2~2] Merge branch 'll/ls-files-tests-update' into maint [master~16^2~2^2] ls-files: update test style
- 第一个父提交:
-
6.2 查看提交记录¶
-
查看旧提交:
git log
等效于git log HEAD
,从HEAD开始回溯git log <commit>
从指定提交开始回溯git log <since>..<until>
,查看从since
至until
范围内的提交记录,数学表达为\((since, until]\):git log --pretty=short --abbrev-commit master~12..master~10
- 参数:
-p
:产生补丁--pretty=<oneline|short|full>
:输出格式-<n>
:输出n
条日志--stat
:每个提交的变更统计情况--graph
:查看图形表达
-
提交图:
-
提交范围:
-
可达性:
- 如果从A节点出发,根据规则沿着图中的边走,并且可以到达节点X,那么我们就称为X节点是A节点的可达节点。A节点的所有可达节点就组成A节点的可达节点集合(
S
)
git log Y
的意思就是给出Y
的所有可达节点。git log ^X Y
的意思就是给出Y
的排除X
及其之前的所有可达节点,用简单表达就是X..Y
,\((X, Y]\),也可以理解为: $$ S=Y-X $$- 分支间的范围:
-
A..B
:表示的是在B
中,但不在A
中的提交。可以理解为: $$ S = A - (A \cap B) $$- 对称差(Symmetric difference):
A...B
- 对称差顾名思义是对称的,因此可以还可以表达为:B...A
,实际表达方式为:git rev-list A B --not $(git merge-base --all A B)
$$ S = (B \cup A) - (B \cap A) $$
- 如果从A节点出发,根据规则沿着图中的边走,并且可以到达节点X,那么我们就称为X节点是A节点的可达节点。A节点的所有可达节点就组成A节点的可达节点集合(
-
任意序列的包含和排除:
git log ^branch1 ^branch2 ^branch3 master
-
-
查找提交
git bisect
:利用二分法来查找一个坏的版本(提交)- 参数
start
:启动bad
:声明此提交为一个坏的提交good
:声明次提交为一个好的提交log
:查看处理日志reset
:切回主分支
- 参数
git blame
可以识别文件中的内容是谁修改的,哪次提交的。git log -S<string>
:查看关键字string
在文件差异历史中搜索。这个搜索方式称为"pickaxe"
第7章 分支¶
7.1 使用分支的原因¶
-
常见理由:
- 一个分支通常代表一个单独的客户发布版。如果你想开始项目的1.1版本,但你知道一些客户想要保持1.0版,那就把旧版本留作一个单独的分支。
- 一个分支可以封装一个开发阶段,如原型、测试、稳定或临近发布。你也可以认为1.1版本发布是一个单独的阶段,也就是维护版本。
- 一个分支可以隔离一个特性的开发或则研究特别复杂的Bug。例如,可以引入一个分支来完成一个明确定义的、概念上孤立的任务,或在发布之前帮助几个分支合并。
- 只是为了解决一个Bug就创建一个新分支,这看起来可能是杀鸡用牛刀了,但Git的分支系统恰恰鼓励这种小规模的使用。
- 每一个分支可以代表单个贡献者的工作。另一个“集成”分支可以专门用于凝聚力量。
-
Git把列出的这些分支视为特性分支(topic branch)或开发分支(development branch)。“特性”仅指每个分支在版本库中有特定的目的。Git也有追踪分支(tracking branch)的概念。
-
分支或标签:应避免使用相同的名称
- 标签:静态的
- 分支:动态的
7.2 分支名¶
- 默认名:
master
- 层次分支:类似于UNIX的路径名,比如:在
bug
分支下创建bug/pr-1023
和bug/pr-17
分支。 - Git搜索分支时支持通配符:
git show-branch 'bug/*'
- 命名规则:
- 可以使用
/
创建一个分层的名称,但是该分支名不能以斜线结尾。 - 不能以
-
开头。 - 以
/
分割的组件不能以.
开头,如:feature/.new
是不被允许的。 - 不能包含两个连续的
.
。 - 不能包括一下符号:
- 任何空格或其他空白符。
- 在Git中具有特殊含义的字符:
~
、^
、:
、?
、*
、[
。 - ASCII码控制字符,即值小于
\040
和\0177
字符
- 可以使用
7.3 使用分支¶
- 活动分支:操作的默认目标
- 可以把任意分支设置成活动分支:
git checkout <master>
- 可以把任意分支设置成活动分支:
- 每个分支在一个特定的版本库中必须有唯一的名字,这个名字始终指向该分支上最近提交的版本。一个分支的最近提交称为该分支的头部(tip或head)
- 通过源分支名和分叉出的新分支名,可以查找到分叉点:
git merge-base <original-branch> <new_branch>
7.4 创建分支¶
-
命令:
git branch <branch_name> [commit]
- 如果没有提供
commit
,则默认为HEAD
- 如果没有提供
-
注意:
git branch <branch_name>
命令指示把分支名引进版本库,分支名就好比C语言的指针,指向某个提交,或者说是动态的tag
。
7.5 列出分支名¶
-
命令:
git branch [-r|-a]
:*
指示活动分支- 参数:没有提供
-r
或-a
的话,列出的是本地特性分支-a
:列出所有分支-r
:列出远程追踪分支
7.6 查看分支¶
-
git show-branch
,这个命令列出的信息比git branch
详细:mbp:git-example wang_hongqi$ git show-branch ! [b1] add dir1/file3 * [b2] add file5 ! [master] add file4 --- * [b2] add file5 + [master] add file4 +*+ [b1] add dir1/file3
--
符号上方是分支名,下方是每个分支的提交矩阵:- 分支名:
- 活动分支用
*
表示 - 分支名后面是日志消息的第一行
- 活动分支用
- 提交矩阵:
*
突出活动分支+
表示提交在一个分支中-
表示一个合并
- 分支名:
- 分支中的提交是有序的,但是分支之间是无序的。
-
当调用时,git show-branch遍历所有显示的分支上的提交,在它们最近的共同提交处停止。 > 在第一个共同提交处停止是默认启发策略,这个行为是合理的。 据推测,达到这样一个共同的点会产生足够的上下文来了解分支之间 的相互关系。如果由于某种原因,你想要更多提交历史记录,使用
--more=<n>
选项,指定你想在共同提交后看到多少个额外的提交。 git show-branch [branch_name]...
:指定要查看的分支- 支持通配符
*
- 支持通配符
7.7 检出分支¶
git checkout <branch_name>
用于检出指定分支,即切换到指定分支,此时目标分支为活动分支。- 参数
-f
:强制检出-m
:merge,可以将本地的修改和目标分支之间进行一次合并- 注意:此时一定要查看是否有冲突存在
- 参数
- 检出动作的 影响:
- 在要被检出的分支中但不在当前分支中的文件和目录,会从对象库中检出并放置到工作树中;
- 在当前分支中但不在要被检出的分支中的文件和目录,会从工作树中删除;
- 这两个分支都有的文件会被修改为要被检出的分支的内容。
- 检出动作的 安全机制:
- 不会变动哪些未被追踪的文件或目录
- 如果一个本地文件被修改且不同于目标分支上的变更,则拒绝检出,并提示导致拒绝的原因
- 当出现此问题时,可以通过查看本地分支和目标分支上关于差异文件的情况:
- 查看当前分支文件:
cat example_file
- 查看当前分支文件的差异:
git diff example_file
- 查看目标分支文件:
git show branch_name:example_file
- 查看当前分支文件:
- 当出现此问题时,可以通过查看本地分支和目标分支上关于差异文件的情况:
- 创建并检出到新分支:
git checkout -b <branch_name> [commit]
-
当检出一个提交,而非分支的时候,如
tag
,Git回自动创建一个分离的HEAD(detached HEAD, 临时的),如果我们改动并持久化,这时候可以通过-b
参数,来同步创建一个对应的分支。以下情况回产生分离HEAD:- 检出的提交不是分支的头部。
- 检出一个追踪分支
- 检出一个
tag
- 启动
git bisect
- 使用
git submodule update
mbp:git-example wang_hongqi$ git checkout test_tag Note: switching to 'test_tag'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 0213937 add file5
-
7.8 删除分支¶
-
命令:
git branch -d <branch_name>
-
安全机制:
- 不允许删除当前分支
- 不允许删除一个 不存在于当前分支中 的提交 的分支
-
用
-D
而不是-d
可以强制删除一个分支 - 如果一个提交不可达,会被
git gc
工具回收 - 意外删除的恢复工具:
git reflog
git fsck
第8章 diff¶
-
diff [-u] [-r]
:Linux/Unix系统中用来比较两个文件的差异-
-u
:可选项,用来产生合并格式的差异(unified diff)[admin@iZj6ciigioovfebodr8251Z git-example]$ diff -u file1 file2 --- file1 2022-09-04 19:29:55.971028382 +0800 +++ file2 2022-09-04 19:29:12.644588336 +0800 @@ -1,2 +1,2 @@ 1 -3 +2
---
:原始文件被其标记+++
:新文件被其标记-
:减号开始的行表示从原始文件删除改行以得到新文件+
:加号开始的行表示从原始文件中添加该行以产生新文件:以空格开始的行是两个版本都有的行
-
-r
:递归;遍历每个目录的文件,并总结每个文件的差异。
-
-
git diff [-r]
:用来比较两颗树对象之间的差异-r
:遍历两颗树对象,并比较其差异
8.1 git diff 命令的格式¶
-
基本命令形式
git diff
:比较work
跟index
git diff commit
:比较work
跟commit
git diff --cached commit
:比较index
跟commit
- 也可以用
--staged
代替--cached
,等效的
- 也可以用
git diff commit1 commit2
:比较commit1
跟commit2
-
参数:
-w
或--ignore-all-space
:比较时忽略空白符--stat
:显示针对两个树状态之间差异的统计数据--color
:给输出结果上色
8.2 git diff和提交范围¶
-
git log
关心的是每一次提交(变更),而git diff
关心的是两次提交的差异。因此git log
操作一系列提交git diff
操作两个不同的节点
-
git diff commit1 commit2
等效于git diff commit1.. commit2
8.3 路径限制的git diff¶
-
git diff commit1 commit2 directory|file
:比较directory
或file
在commit1
和commit2
中的差异 -
git diff -S<string> master~10
:查看在master
分支中最近10次提交中关于string
的改动(即string
的新增或删除)
8.4 比较SVN和git如何产生diff¶
- SVN为了节省空间和开销,只存储文件间的差异(diff)。
- 因此当要比对两个版本间的差异时,SVN需要获取两个版本之前的所有差异(diff),然后将其合并成大的diff并发送给客户端。
- 而git则可以直接通过两个版本的提交获取差异,并生成diff交给用户,因此速度快得多。
第9章 合并¶
9.1 合并例子¶
-
合并操作
git merge
操作是区分上下文的。当前分支始终是目标分支,其他一个或多个分支始终合并到当前分支
-
作为一般规则: > 如果每次合并都从干净的工作目录和索引开始,那么关于Git的操作将会容易很多
-
合并出现冲突,用
git diff
查看冲突:[admin@iZj6ciigioovfebodr8251Z git-example]$ git diff diff --cc file1 index 1191247,7ecb0bf..0000000 --- a/file1 +++ b/file1 @@@ -1,2 -1,3 +1,7 @@@ 1 ++<<<<<<< HEAD +2 ++======= + 333 + 4 ++>>>>>>> alt
- 改变的内容:
<<<<<<<
和=======
之间。 - 替代的内容:
=======
和>>>>>>>
之间。 - 用
+
和-
来表示各合并分支相较于当前HEAD的一个增删情况- 第一列符号显示相对于目标(当前)分支的更改,第二列显示相对于另一个分支的更改
- 三方合并标记线(
<<<<<<<
、=======
和>>>>>>>
)是自动生成的,但是他们只是提供给你看的,而不是给程序看的,一旦解决了冲突,就应该在文本编辑里删除它们。
- 改变的内容:
9.2 合并冲突¶
-
当存在冲突时,git会在索引中把他们标记为冲突的(cfonflicted)
-
定位冲突文件:
git status
git ls-files -u
:-u
是unmerge的意思
-
git给第二个父版本(
HEAD^2
)起了特殊名字:MERGE_HEAD
-
git diff --ours
是git diff HEAD
的同义词,翻译为“我们的”; git diff MERGE_HEAD
与git diff --theirs
同义,翻译为“他们的”;git diff --base
可以查看与合并基础的差异;等效于git diff $(git merge-base HEAD MERGE_HEAD)
- 对于有冲突的文件执行
git diff
只会显示真正有冲突的部分。 - 如果只有一边有变化,这部分就不显示。
-
对冲突使用
git log
:--merge
:只显示跟产生冲突的文件相关的提交--left-right
:如果提交来自合并的“左”边则显示<
(“我们的”版本,就是你开始的版本),如果提交来自合并的“右”边则显示>
(“他们的”的版本,就是你要合并到的版本)。-p
:显示提交信息和每个提交相关联的补丁。
-
Git追踪分支的方法由几部分组成:
.git/MERGE_HEAD
存储合并时“他们的”分支的HEAD
对应的SHA1值.git/MERGE_MSG
存储当冲突解决后执行git commit
命令时用到的默认合并消息- Git的索引包含每个冲突文件的三个副本:合并基础、“我们的”版本和“他们的”版本
- 冲突的版本(合并标记和所有内容)不存储在索引中。相反,它存储在工作目录中的文件里。当执行不带任何参数的
git diff
命令时,始终比较索引与工作目录中的内容。
-
使用
git diff :1:hello :3:hello
来查看合并基础与“他们的”版本的差别: -
冲突期间(1.6.1版本+),通过
git checkout --ours
检出我们的版本,git checkout --theirs
检出他们的版本。 -
当解决一个冲突后,执行
git add
或git rm
或git update-index
来清除冲突状态 -
当查看一个合并提交时,应注意三件有趣的事:
- 在开头第二行写着的“
Merge:
”。通常在git log
或者git show
中不显示父提交,但当一个提交是来源于合并操作时,会则通过Merge:
字段来显示每个祖先的SHA1。 - 自动生成的提交日志消息有助于标注冲突的文件列表。如果事实证明一个特定的问题是由合并引起的,这将十分有用。通常,问题都是由不得不手动进行合并的文件引起的。
- 合并提交的差异不是一般的差异。它始终处于组合差异或者“冲突合并”的格式。认为Git中一个成功合并是完全没有变化的;它只是简单地把其他已经在历史中的变更组合起来。因此,合并提交的内容里只显示与合并分支不同的地方,而不是全部的区别。
- 在开头第二行写着的“
-
中止或重启合并:
-
如果合并还未提交:想立即把工作目录和索引都还原到
git merge
命令之前,执行: -
如果合并已经提交,则执行:
-
如果冲突解决方案搞砸了,想返回尝试解决冲突前的冲突状态,则执行:
-
9.3 合并策略¶
-
退化合并:虽然执行了合并操作(
git merge
),但是实际上并不引入一个合并提交。- 已经是最新的:来自其他分支(
HEAD
)的所有提交都存在于目标分支上时,即使它已经在自己的分支上前进了,目标分支还是已经更新到最新的。因此,没有新的提交添加到你的分支上。 - 快进的:当分支
HEAD
已经在在其他分支中完全存在或表示时,就会发生快进合并。(前一种的反向场景)
- 已经是最新的:来自其他分支(
-
常规合并:
-
特殊提交:
-
“我们的”策略:
- 合并任意数量分支。丢弃其他分支的改动,仅适用当前分支的文件。
- 该策略用于合并历史记录
-
子树策略: > 将一个分支合到当前分支的某个子树下面。无需指定,Git自动决定。
-
-
应用合并策略
- 尝试“退化合并”,消除不重要的,简单的情况
- 常规合并:
- 如果指定了超过2个分支,则使用章鱼策略
- 否则选择默认策略(早期版本是“解决策略”,后期默认策略改为“递归合并策略”)
- 手动使用解决策略:
git merge -s resolve Bob
- 手动使用解决策略:
-
合并驱动程序:合并策略是由合并驱动程序处理的,一个合并驱动程序会产生三个临时文件名来代表“共同祖先”、“目标分支版本”和“文件的其他分支版本”。
- 文本(text)合并驱动程序:留下三方合并标志。
- 二进制(binary)合并驱动程序:简单保留文件的目标分支版本,在索引中把文件标记为冲突的。(迫使你手动处理二进制文件)。
- 联合(union)驱动程序:把两个版本的所有行留在合并后的文件里。
9.4 Git如何看待合并¶
- 合并后的
tree
对称地代表源分支两边,但仅在目标分支有commit
指向。 -
Git的理念,所有分支生而平等。
-
压制合并:将源分支的所有变动作为一个patch提交到目标分支中,这样源分支的历史记录将丢失。Git并不提倡压制分支
git merge
或git pull
中使用--squash
选项可以启用压制合并。
第10章 更改提交¶
-
修改或返工某个提交或提交序列的理由:
- 可以在某个问题变为遗留问题之前修复它
- 可以将大而全面的变更分解为一系列小而专题的提交。相反,也可以将一些小的变更合并成一个更大的提交
- 可以合并反馈评论和建议
- 可以在不破坏构建需求的情况下重新排列提交序列
- 可以将提交调整为一个更合乎逻辑的序列
- 可以删除意外提交的调试代码
-
是否应当更改历史有一个哲学探讨,我倾向于不变更历史:
- 历史结合“频繁小而全的提交”的理念,可以轻易的复盘提交者的想法。
- 完整的历史可以保留发展过程,调整历史意味着有可能抹去此过程。
-
注意事项:
- 分支若已发布,则相应的提交不应被修改。反之,则可以修改
10.1 git reset¶
git reset
是有“破坏性的”,因为它可以覆盖并销毁工作目录中的修改。事实上,数据可能会丢失。
git reset --soft
:将HEAD
引用指向给定提交,索引和工作目录的内容保持不变。这个版本的命令有“最小”影响,只改变一个符号引用的状态使其指向一个新提交。git reset --mixed
:将HEAD
引用指向给定提交,索引内容也跟着改变,但工作目录不变。默认模式git reset --hard
:将HEAD
引用指向给定提交,索引和工作目录也跟着变更。
选项 | HEAD | 索引 | 工作目录 |
---|---|---|---|
--soft | Y | N | N |
--mixed | Y | Y | N |
--hard | Y | Y | Y |
- 执行此命令后,会将原始
HEAD
值存在ORIG_HEAD
中。 - 重置HEAD的方法:
- 用
git log
查看reset
前的SHA1值 -
用
git reflog
查看reset
前的SHA1值:69fcd79 (HEAD -> master, origin/master, origin/HEAD) HEAD@{0}: commit: 如果没有提供build_type会出问题 b41da0b HEAD@{1}: commit: clean时会出问题 beeca34 HEAD@{2}: pull: Fast-forward ba29bdf HEAD@{3}: pull: Fast-forward 570ae93 HEAD@{4}: commit: 解决jenkins编译的时候报错没有输入设备 bc28786 HEAD@{5}: commit: 解决Jenkinsfile格式有误 e01cc27 HEAD@{6}: commit: 解决test时报错 d679eac HEAD@{7}: commit: 解决容器编译错误 8185dc8 HEAD@{8}: commit: 解决在容器中构建失败 128979d HEAD@{9}: pull: Fast-forward b59c76e HEAD@{10}: commit: 解决容器编译失败 4dd8234 HEAD@{11}: pull: Fast-forward 6b17e50 HEAD@{12}: pull: Fast-forward f56fe48 HEAD@{13}: clone: from https://gitee.com/homqyy/hcore
- 例如用
HEAD@{2}
来表示beeca34
- 通过
git reset HEAD@{2}
来重置HEAD
- 例如用
- 用
10.2 git cherry-pick¶
git cherry-pick
命令通常用于把版本库中一个分支的特定提交引入一个不同的分支中。常见用法是把维护分支的提交移植到开发分支。git cherry-pick commit
:取出指定提交并应用到当前分支上git cherry-pick commit1..commit2
:取出指定的提交范围并应用到当前分支上
10.3 git revert¶
git revert
提交命令跟git cherry-pick
大致相同,重要区别为: 它应用给定提交的逆过程。因此此命令用于引入一个新提交来抵消给定提交的影响。- 此命令可以翻译为“撤销某个提交”:
git revert commit
10.4 修改最新提交¶
git commit --amend
:修改文件并提交到索引后,再执行此命令。- 修改作者使用选项
--author
- 此命令不会引入新提交
- 修改作者使用选项
10.5 rebase¶
-
git rebase
常见用途是保持你正在开发的一系列提交相对于另一个分支是最新的,那通常是master分支或来自另一个版本库的追踪分支。- 变基操作会把原始分支历史(带合并)线性化,想明确保留使用
--preserve-merges
选项
- 变基操作会把原始分支历史(带合并)线性化,想明确保留使用
-
变基本概念:
- 变基把提交重写成新提交;
- 不可达的旧提交会消失;
- 任何旧的、变基前的提交的用户可能被困住;
- 如果你有个分支用变基前的提交,你可能需要反过来对它变基。
- 如果有个用户有不同版本库中变基前的提交,即使它已经移动到了你的版本库中,他仍然拥有该提交的副本;该用户现在必须也修复他的提交历史记录。
-
将
topic
变基为master
当前的HEAD
: -
使用
--onto
参数把一条分支上的开发线整个移植到完全不同的分支上: -
变基操作一次只迁移一个提交,出现冲突后会暂停,解决后执行
git rebase --continue
恢复。 - 在检查变基冲突的时候,执行
git rebase --skip
跳过当前提交。 - 执行
git rebase --abort
中止变基操作。 -
git rebase -i
可以修改分支的提交顺序或合并提交:pick 60816f8 alt file1 pick 32b859d add hcore.git to ./hore # Rebase 8a28e1d..32b859d onto 8a28e1d (2 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # . create a merge commit using the original merge commit's # . message (or the oneline, if no original merge commit was # . specified). Use -c <commit> to reword the commit message. # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. #
- 从最老到最新排序
第11章 储藏和引用日志¶
11.1 储藏¶
- 储藏可以捕获你的工作进度,允许你保存工作进度并且当你方便时再回到该进度。
-
储藏是一种快捷方式,它让你仅通过一条简单的命令就全面彻底地捕获工作目录和索引。它使你的版本库时干净整洁的,准备好向另一个方向开发。有另一条简单的命令可以完全还原工作目录和索引,让你回到你离开时的状态。
-
操作
-
储藏:
git stash [--include-untracked] [--all] [save [message]]
- 常用缩略语:"WIP(work in progress)"
git stash
默认操作是save
- 索引和工作目录的内容实际上另存为独立且正常的提交,它们可以通过
refs/stash
来查询: -
使用
--include-untracked
参数来储藏未追踪的文件 -
使用
--all
选项来储藏所有文件包括“未追踪的”、被“.gitignore”和“exclude”忽略的
-
恢复并删除储藏:
git stash pop
:仅在干净工作目录中可用- 恢复:
git stash apply
- 删除:
git stash drop
- 恢复:
-
将储藏的内容放到一个分支中:git stash branch \
- 该分支基于储藏时的基本提交,而不是执行此命令时的HEAD。
-
列出储藏栈:
git stash list
-
查看变更:
git stash show
- 显示的是特定储藏条目相对于它的福提交的索引和文件的变更记录。
git diff
的所有选项也适用于此
-
-
git stash save
和git stash pop
这两条命令实现了储藏状态栈,因此它允许我们在中断工作流的情况下再次中断。栈上每个储藏的上下文都可以通过正常提交流程来单独管理。 -
当需要解决冲突时,Git将不会自动丢弃状态,以防你想要尝试不同的方法或还原到不同的提交。一旦清理了合并冲突并希望继续,就应该使用
git stash drop
来删除储藏。 -
git stash
的另一个经典应用场景是所谓的“在脏树中进行拉取”:
11.2 引用日志¶
-
引用日志(reflog)记录非裸版本库中分支头的改变。每次对引用的更新,包括对
HEAD
的,引用日志都会更新以记录这些引用发生了哪些变化。把引用日志当做面包屑轨迹一样指示你和你的引用去过那里。以此类推,也可以通过引用日志来跟随你的足迹并回溯你的分支操作。 -
会更新引用日志的基本操作:
- 复制;
- 推送;
- 执行新提交;
- 修改或创建分支;
- 变基操作;
- 重置操作;
-
从根本上说,任何修改引用或更改分支头的Git操作都会记录。
-
默认情况下,引用日志在飞裸版本库中是启用的,在裸版本库中是禁用的。明确地说,引用日志是由配置选项
core.logAllRefUpdates
控制的。可以通过git config core.logAllRefUpdates <true|false>
命令启用或禁用引用日志。 -
查看引用日志:
git reflog show
[admin@iZj6ciigioovfebodr8251Z git-example]$ git reflog show 32b859d (HEAD -> master) HEAD@{0}: reset: moving to HEAD 32b859d (HEAD -> master) HEAD@{1}: rebase (abort): updating HEAD 32b859d (HEAD -> master) HEAD@{2}: rebase (abort): updating HEAD 0d8261a HEAD@{3}: commit: modify file1 166373a HEAD@{4}: commit (amend): modify file2 81d6da3 HEAD@{5}: commit: modify file2 32b859d (HEAD -> master) HEAD@{6}: reset: moving to HEAD 32b859d (HEAD -> master) HEAD@{7}: commit: add hcore.git to ./hore 537e9ad HEAD@{8}: checkout: moving from alt to master 60816f8 (alt) HEAD@{9}: checkout: moving from master to alt 537e9ad HEAD@{10}: commit (merge): Merge branch 'alt' 8a28e1d HEAD@{11}: commit: alt line 2 3116b8a HEAD@{12}: checkout: moving from alt to master 60816f8 (alt) HEAD@{13}: commit: alt file1 3116b8a HEAD@{14}: checkout: moving from master to alt 3116b8a HEAD@{15}: commit (initial): add file1 and file2
- 此命令一次只显示一个引用的事务
- 查看指定分支的引用:
git reflog <branch_name>
HEAD@{1}
始终指向分支的前一次提交- Git针对引用大量基于日期的限定符。其中包括如
yesterday
,noon
,midnight
,tea
,星期
,月份
,A.M
和P.M
标识,这些绝对时间或日期,还有像last monday
,1 hour ago
,10 minutes ago
和这些短语的组合(如1 day 2hours ago
等),相对时间或日期。 - 省略引用名的形式
@{...}
,默认引用就是当前分支 -
为了简化断字问题,Git允许以下几种形式:
-
通常情况下,一个提交,如果既不能从某个分支或引用指向,也不可达,将会默认在30天后过期,而哪些可达的提交将默认在90天后过期。
- 设置超时时间:
git config gc.reflogExpireUnreachable
和git config gc.reflogExpire
- 删除指定条目:
git reflog delete
- 立即清除过期项目:
git reflog expire
--expire=now --all
选项:等于立刻清除所有引用
- 设置超时时间:
-
引用日志都存储在
git/logs
目录下。.git/logs/HEAD
文件包含HEAD
值的历史记录,它的子目录.git/log/refs/
包含所有引用的历史记录,其中也包括储藏(stash
)。它的二级子目录.git/logs/refs/heads
包含分支头的历史记录:- 在引用日志中存储的所有信息,特别是
.git/logs
目录下的一切内容,归根结底还是临时的,不重要的。抛弃.git/logs目录或关闭引用日志不会损坏Git的内部数据结构;它只意味着诸如master@{4}
这样的引用不会被解析。
- 在引用日志中存储的所有信息,特别是
第12章 远程版本库¶
-
一个克隆是版本库是版本库的副本。一个克隆包含所有原始对象;因此,每个克隆都是独立、自治的版本库,与原始版本库是真正对称、地位相同的。一个克隆允许每个开发人员可以在本地独立地工作,不需要中心版本库,投票或者锁。归根结底,克隆使Git易于扩展,并允许地理上分离的很多贡献者一起协作。
-
分离版本库的优点:
- 开发人员独立自主工作。
- 开发人员被广域网分离。在相同地区的一群开发人员可以共享一个本地库来积累局部变化。
- 一个项目预计在不同的发展线上有显著差异。虽然前面几章展示的正常分支和合并机制可以处理任何数量的独立开发,但是产生的复杂性带来的麻烦可能会比它们的价值更多。相反,独立的开发线可以使用单独的版本库,到适当的时候再进行合并。
-
远程版本库(remote)是一个引用或句柄,通过文件系统或网络指向另一个版本库。
- 一旦远程版本库建立,Git就可以使用推模式或拉模式在版本库之间传输数据。
- 要跟踪其他版本库中的数据,Git使用远程追踪分支(remote-trackin branch)。
- 你可以将你的版本库提供给他人。Git一般指此为发布版本库(publishing a repository)。
12.1 版本库概念¶
- 一个Git版本库要么是一个裸(bare)版本库,要么是一个开发(非裸)(development, nobare)版本库。
-
一个裸版本库没有工作目录,并且不应该用于正常开发。罗版本库也没有检出分支的概念。裸版本库可以简单地看做
.git
目录的内容。换句话说,不应该在裸版本库中进行提交操作。 -
裸版本库用处:作为协作开发的权威焦点。
- 其他开发人员从裸版本库中
clone
和fetch
,并push
更新。
- 其他开发人员从裸版本库中
-
创建裸版本库:
git clone --bare URL
git init --bare
-
在正常使用
git clone
命令时,原始版本库中存储在refs/heads
下的本地开发分支,会成为新的克隆版本库中refs/remotes
下的远程追踪分支。原始版本库中refs/remotes
下的远程追踪分支不会被克隆。标签也会被克隆。- 远程追踪分支与原始版本库是一个单向关系
- 用
--origin
来改变默认名 - 提取规则:
fetch = +refs/heads/*:refs/remotes/origin/*
-
远程版本库
git remote
:创建、删除、操作和查看远程版本库git fetch
:提取远程版本库的对象及元数据git pull
:git fetch
+git merge
(或git rebase
)git push
:转移对象及相关的元数据到远程版本库git ls-remote
:显示一个给定的远程版本库(在上游服务器上)的引用列表。
-
追踪分支
- 分类
- 远程追踪分支:与远程版本库相关联,专门用来追踪远程版本库中每个分支的变化。
- 本地追踪分支:与远程卓在那个分支相配对。它是一种集成分支,用于收集本地开发和远程追踪中的变更。
- 特性/开发分支:任何非追踪分支
- 远程分支:设在非本地的远程版本库的分支
- 在常规特性分支上可以执行的所有操作都可以在追踪分支上执行。然而,需要遵守一些限制和指导:
- 因为远程追中分支专门用于追踪另一个本本库中的变化,因此应该当成只读
- 不应合并或提交到一个远程追踪分支,这样做会导致你的远程追踪分支变得和远程版本库不同步。
- 分类
12.2 引用其他版本库¶
-
引用远程版本库
/path/rto/repo.git
file:///path/to/repo.git
git://example.com/path/to/repo.git
git://example.com/~user/path/to/repo.git
ssh://[user@]example.com[:port]/~user2/path/to/repo.git
user
与user2
不同表示的是,user是验证会话的用户,user2
是访问主目录的用户
user@example.com:~user/path/to/repo.git
:scp格式http://example.com/pth/to/repo.git
https://example.com/path/to/repo.git
rsync://example.com/path/to/repo.git
:不鼓励使用rsync
-
refspec:
- 语法:
[+]source:destination
+
:加号表示不会再传输过程中进行正常的快进安全检查。- 在某些引应用,
source
是可选的;在另一些应用中:destination
是可选的
- 默认行为:
- 如果
git push
没有指定远程版本库,它会默认使用origin
- 如果没有refspec,
git push
会将你的提交发送到远程版本库中你与上游版本库共有的所有分支。 - 新分支必须显示的使用分支名来推送:
git push origin branch
git push origin branch:refs/heads/branch
- 如果
- 语法:
-
远程版本库操作
-
添加一个命名的远程仓库:
git remote add origin /home/admin/git-example/
-f
:使用该选项会立即对远程版本库执行fetch
-
查看配置文件:
cat .git/config
-
创建远程追踪分支:
git remote update [remote_name]
- 该命令会更新远程追踪分支
-
查看分支:
git branch -a
-
在
master
分支开发 - 推送变更:
git push origin master
- 这里分成两步:
- 把
master
的改动推送到origin
命名的远程版本库 - 把
master
的改动添加到远程追踪分支origin/master
- 把
- 这里分成两步:
-
查看远程版本库的分支信息:
-
git ls-remote origin
-
git remote show origin
-
-
设置上游,用于默认pull:
git branch --set-upstream-to=origin/master master
-
查看配置文件:
cat .git/config
git { .highlight=[10,11,12] } [admin@iZj6ciigioovfebodr8251Z local-git-example]$ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = /home/admin/git-example/ fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master
pull
第一步是fetch
更新;第二步则是合并。- 合并的依据是基于
merge
配置:将远程版本库的refs/heads/master
合并到master
分支。 remote
:表示分支对应的远程版本库。
- 合并的依据是基于
-
-
获取上游更新:
git pull [--rebase] [repository] [<refspec>...]
- 如果没有指定
repository
则使用默认origin
- 如果没有指定
refspec
则使用远程版本库的fetch
的refspec
。 - 如果指定了
repository
,但是没有指定refspec
则抓取远程版本库的HEAD
--rebase
:用变基替代合并-
选择合并还是变基:
- 通过合并,每次拉取将有可能产生额外的合并提交来记录更新同时存在于每一个分支的变更。从某种意义上说,它真实地反映了两条开发路径独立发展然后合并在一起。在合并期间必须解决冲突。每个分支上的每个提交序列都基于原来的提交。当推送到上游时,任何合并提交都将继续存在。有些人认为这些是多余的合并,并不愿意看到它们弄乱历史记录。另一些人认为,这些合并是开发历史记录更准确的写照,希望看到它们被保留。
- 变基从根本上改变了一系列提交是在何时何地开发的概念,开发历史记录的某些方面会丢失。具体而言,你的开发最初基于的原始提交将更改为远程追踪分支新拉取的HEAD。这将使开发出现的比它实际晚一些(在提交序列方面)。如果这样对于你没有问题,那么对于我也没问题。这只是跟合并的历史记录不一样,且更简单。当然,你在变基操作过程中仍然要去解决冲突。由于变基的变更仍然只在你的版本库中,还尚未公布,因此这次变基没有理由害怕“不改变历史记录”的禁咒。
- 我倾向于看到简单线性的历史记录。在我个人的开发过程中,我通常不会太在意把我的变更相对于从远程追踪分支抓取的同事的变更做轻微地重新排序,因此我更喜欢变基选项。
- 设置选项来实现自己的期望:设置
branch.autosetupmerge
和branch.autosetuprebase
为true
、false
或always
- 如果没有指定
-
12.3 图解远程版本库开发周期¶
12.4 远程版本库配置¶
-
Git为建立和维护远程版本库信息提供三种机制(三种机制最终都体现在
.git/config
文件):git remote
命令git config
命令- 直接编辑
.git/config
文件
-
git remote
:用来操作配置文件数据和远程版本库引用git remote [-v | --verbose] git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <url> git remote rename <old> <new> git remote remove <name> git remote set-head <name> (-a | --auto | -d | --delete | <branch>) git remote set-branches [--add] <name> <branch>... git remote get-url [--push] [--all] <name> git remote set-url [--push] <name> <newurl> [<oldurl>] git remote set-url --add [--push] <name> <newurl> git remote set-url --delete [--push] <name> <url> git remote [-v | --verbose] show [-n] <name>... git remote prune [-n | --dry-run] <name>... git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
- 可以添加多个远程版本库URL来实现多源抓取和多目的推送。
-
git config
:直接操作配置文件中的条目-
查看当前配置文件内容:
git config -l
-
示例
-
12.5 使用追踪分支¶
-
使用远程追踪分支名的一个简单的检出请求会导致创建一个新的本地追踪分支,并与该远程追踪分支相关联。(仅当分支名与远程版本库的分支名相同才行)
-
git checkout [-b <branch>] [--track] <remote_branch>
- 当远程分支名非唯一的时候需要使用
--track
指明具体分支 - 当想要创建不同名的分支时,需要使用
-b
来设置分支名- 比如:
git checkout -b test --track remote
- 比如:
- 当远程分支名非唯一的时候需要使用
-
当创建本地追踪分支时,但并不想检出它,使用
git branch --track <local branch> <remote branch>
- 当已经存在一个特性分支,并想与远程版本库相关联,使用
git branch --set-upstream <local branch> <remote brach>
- 查看在
master
但不在origin/master
:git log origin/master..master
- 查看各自新的提交:
git log origin/master...master
12.6 添加和删除远程分支¶
-
在远程版本库中创建分支:推送仅有源的
refspec
:git push origin foo
:在origin
中创建分支foo
git push origin foo:foo
:在origin
中创建分支foo
git push origin foo:refs/heads/foo
:在origin
中创建分支foo
-
在远程版本库中删除分支:推送仅有目的的
refspec
:git push origin :foo
:在origin
中删除分支foo
git push origin --delete foo
:在origin
中删除分支foo
第13章 版本库管理¶
13.1 发布版本库¶
- 带访问控制的版本库:Gitolite
13.1.1 git-daemon¶
-
建立
git-daemon
允许使用Git原生协议导出版本库。- 在裸版本库的顶级目录创建
git-daemon-export-ok
文件来标记版本库可以被导出。 - 为了避免单独标记每个版本库,可以在运行
git-daemon
命令时加上--export-all
选项来发布所有在它的目录列表中可识别的(拥有objects
和refs
子目录的)版本库。
- 在裸版本库的顶级目录创建
-
在服务器上建立
git-daemon
的
13.1.2 HTTP守护进程¶
资源¶
- Git在Github的源码
- 《应用密码学》——Bruce Schneier
- Gitolite项目:提供权限控制功能
遗留问题¶
git diff
的对称差是什么?