# 比较全面的 Git 命令手册,几乎涵盖日常所有的使用场景(上)

# Git 配置

# 文件类型

  • 系统范围内的配置文件,包括系统上每一个用户及其仓库的通用配置,位于Git安装目录下C:/Program Files/Git/etc/gitconfig
  • 用户级的配置文件,只适用当前用户,位于用户目录下C:/Users/{username}/.gitconfig
  • 项目级的配置文件,只适用某个Git仓库,位于仓库目录下.git/config

  ;Git读取配置文件顺序依次为系统级、用户级、项目级,以上三个级别的配置都会覆盖上一层的配置。

# 文件操作

# 查看

  查看仓库所有配置,其中-l--list简写,可能有重复的变量名,Git会从不同的文件中读取同一个配置,此种情况下,Git会使用它找到的每一个变量的最后一个配置。

git config -l

  查看不同级别配置,其中[]内分别表示不同级别。

git config [--system|--global|--local] -l

  查看某项配置,其中user.name为具体某项配置信息,若无输出则表示不存在该项配置,中文可能输出乱码。也可不设置级别参数,Git根据执行命令路径读取相关配置文件的配置项。

git config [--system|--global|--local] user.name

# 新增

  新增某一配置项,其中section是配置节点,key是节点下的键,section.key新增为true

git config [--system|--global|--local] --add section.key true

# 编辑

  编辑不同级别配置文件,其中-e--edit简写。Git默认编辑器是Vim,进入编辑器后(默认normal模式)键入i进入insert模式,可编辑配置信息,键盘方向键控制光标移动,鼠标左键不生效。编辑完成键入ESCCtrl + C进入normal模式,键入:w(保存)、:q(退出)、:q!(强制退出)、:wq(保存并退出)。不设置级别参数,默认采用--local,编辑项目级配置文件。

git config [--system|--global|--local] -e

  编辑某一配置项的值,section节点key键被设置为false

git config [--system|--global|--local] section.key false

# 删除

  删除某一配置项,section节点key键被删除。

git config [--system|--global|--local] --unset section.key

# 用户信息

  安装完 Git (opens new window) 初始化用户名与邮件地址,Git的每次提交都会使用这些信息,并且会写入到每一次提交中,不可更改。使用--global选项,意味着无论在系统上做任何事情(提交、推送等),Git都会使用这些信息。当针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下使用--local选项的命令来配置。

git config --global user.name username
git config --global user.email username@email.com

# 文本编辑器

  ;Git使用操作系统默认的文本编辑器Vim,配置不同的文本编辑器,例如VS Code

git config --global core.editor code

# Git 命令

# 创建版本库

# 目录初始化

  创建空目录,使用Windows系统,为了避免遇到各种莫名其妙的问题,确保目录名(包括父目录)不包含中文,pwd命令用于显示当前目录路径。

mkdir gitdir
cd gitdir
pwd

  空目录初始化,git init命令将创建一个名为.git的子目录,是Git用来跟踪管理版本库的,ls -a列出目录下的所有文件,包括以.开头的隐含文件。

git init
Initialized empty Git repository in ...

ls -a
./ ../  .git/

  也可以在一个已经存在文件的文件夹(不是空文件夹)中初始化Git仓库并跟踪文件。通过git add命令跟踪指定文件,然后执行git commit提交。

git add --all
git commit -m 'message'

# 克隆仓库

  克隆一份已经存在了的Git仓库,使用git clone命令,Git克隆的是Git仓库服务器上的几乎所有数据, 默认配置下远程Git仓库中的每一个文件的每一个版本都将被拉取下来。

git clone https://github.com/username/repo.git

  当前目录下创建了一个名为repo的目录,并在目录下初始化一个.git文件夹,从远程仓库拉取所有数据放入.git文件夹,然后从中读取最新版本的文件的拷贝。在克隆远程仓库的时候,自定义本地仓库的名字,可以使用如下命令。

git clone https://github.com/username/repo.git repos

  克隆远程仓库某个具体分支,其中-b--branch简写,dev为远程仓库repo的分支。

git clone -b dev https://github.com/username/repo.git

# Git 操作

# 暂存

  将单个文件加入到暂存区,也可将处于未合并unmerged状态的文件标记为冲突已解决。

git add readme.md

  将多个文件加入到暂存区。

git add readme.md file.txt

  将工作区中所有未跟踪或者修改的文件添加到暂存区。其中git add --all,无论在哪个目录都会暂存所有未跟踪、修改或删除的文件。git add .只能暂存当前目录下所有未跟踪、修改的文件,不包括删除的文件。

git add --all
git add .

# 撤销暂存

  将单个文件移出暂存区。

git reset HEAD readme.md

  将暂存区所有文件移出。

git reset HEAD

# 撤销修改

  撤销对单个文件的修改,包括工作区中修改或删除的文件。

git checkout -- readme.md

  撤销工作区所有文件的修改,已暂存的修改不会被撤销,即回到最近一次版本库或git add时的状态。

git checkout .

# 删除

  删除某个文件并将其加入暂存区,可使用git rm readme.md替代如下命令。

rm readme.md
git add readme.md

  之前修改过并且已经放到暂存区的文件,运行git rm会报错,可使用强制删除选项-f--force。报错用于防止误删还没有添加到版本库的数据,Git无法恢复。

git rm -f readme.md

# 放弃跟踪

  把文件从Git版本库中删除但仍然保留在当前工作目录中。 即让文件保留在磁盘,但是并不想让Git继续跟踪。

git rm --cached readme.md
git commit -m 'message'

# 重命名

  修改某个文件的文件名,可使用git mv readme.md file.md代替如下命令。

mv readme.md file.md
git rm readme.md
git add file.md

# 提交

  提交即将暂存区的修改维护至Git版本库纳入版本管理,-m后输入的是本次提交的说明,不加-m参数会调用Vim编辑器提示输入提交说明。

git commit -m 'message'

  ;-a选项会自动把所有已经跟踪过的文件(不包括新增的文件)暂存起来一并提交,从而省略git add步骤。

git commit -a -m 'message'

# 版本回退(可撤销提交)

  ;Git撤销提交可通过版本回退的方式实现,回退到当前版本库的上一个版本,执行如下命令。其中HEAD表示当前版本库,上上一个版本库就是HEAD^^,往上100个版本库为HEAD~100

git reset --hard HEAD^

  回退具体哪个版本库,--hard后为校验和。校验和没必要写全,前几位就可以了。回退至过去某个版本后,git log只能查看之前的所有提交,若要回到未来哪个版本,用git reflog查看提交历史,从而获取校验和。

git reset --hard 8b9b5aa

# 追加提交(修改提交信息)

  某次提交少提交了部分未暂存的修改,即有部分修改未暂存就提交了,不想再另外生成一条提交记录,而是追加到刚才的提交中。首先暂存那部分修改,执行如下命令,重新编辑提交信息。若仅修改提交信息也可执行如下命令。注意修正后会改变提交的校验和。

git commit --amend

# Git 状态

# 概述

  • Untracked:未跟踪,即未被Git纳入版本控制的文件。在一个已经存在文件的文件夹(不是空文件夹)中初始化Git仓库,这个仓库下所有文件均为未跟踪。Git仓库内新建的文件也是未跟踪。
  • Unmodified:未修改,已跟踪状态的一种。克隆某个仓库后,工作目录中的所有文件都处于未修改。暂存区的文件被提交也都处于未修改。
  • Modified:已修改,已跟踪状态的一种。未修改状态文件被编辑或删除,处于已修改。
  • Staged:已暂存,已跟踪状态的一种。已修改被暂存的文件都处于已暂存。

# git status

  查看哪些文件处于什么状态,可以用git status命令。克隆仓库后运行如下命令,提示在master分支没有任何提交的文件,工作区干净,即所有已跟踪文件在上次提交后都未被更改。

git status
On branch master
nothing to commit, working tree clean

  ;git status可打印工作区未跟踪的新文件。注意若.gitignore文件忽略了指定文件,执行git status不会打印。

git status
On branch master
Untracked files:
  ...
     readme.md
  ...

  若已跟踪文件处于未修改状态,编辑后运行git status,文件会被标记为红色modified。删除已跟踪文件,文件会被标记为红色deleted。重命名或移动文件,原名称或原位置文件被标记为红色deleted,新名称或新位置文件为未跟踪状态。不同分支对同一文件做了不同修改合并后产生冲突的文件,被标记为红色both modified

git status
On branch master
Changes not staged for commit:
  ...
         deleted:    file.txt
        modified:   index.js
Unmerged paths:
  ...
        both modified:   readme.md

  运行git add后未跟踪文件、删除文件、修改文件或解决冲突的文件、重命名或移动文件会分别被标记为绿色new filedeletedmodifiedrenamed

On branch master
Changes to be committed:
  ...
        new file:   readme.md
        deleted:    file.txt
        modified:   readme.md
        renamed: o.txt -> n.txt
        renamed: location.txt -> file/location.txt

  ;git status命令输出十分详细,执行如下命令查看简化信息,其中-s--short简写。新添加的未跟踪文件前面有??标记,新添加到暂存区中的文件前面有A标记,修改过的文件前面有M标记,删除的文件前面有D标记。靠右边的M表示该文件被修改了但是还没放入暂存区,靠左边的M表示该文件被修改了并放入了暂存区,D同理。出现MM表示在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。

git status -s
?? ...
A  ...
M  ...
 M ...
MM ...
D  ...
 D ...

# git diff

  ;git status命令的输出不知道具体修改了什么地方,可以用git diff命令。当工作区修改文件,修改未暂存,diff对比的是工作区和最近一次版本库的文件差异。工作区文件修改并暂存,diff对比的是工作区和暂存区的文件差异,即git diff本身只显示尚未暂存的改动。若工作区文件第一次修改并暂存,第二次修改后,执行git diff就能查看尚未暂存的改动。

git diff

  查看暂存区和最近一次版本库的文件差异,其中--staged--cached等价。

git diff --cached

  查看工作区和最近一次版本库的差异,HEAD后可接某次版本库的校验和,即查看工作区和某个版本库之间的差异。

git diff HEAD

  查看不同分支最后一次提交的文件差异,--static查看简略差异。

git diff master dev

# Git 日志

# 常用选项

  ;git log (opens new window) 会按提交时间列出所有的更新,最近的更新排在最上面。 包括每个提交的校验和、作者的名字和电子邮箱、提交时间以及提交说明。

git log

  如果日志很多,Git默认会以分页方式展示,空格可以翻下一页,Ctrl + B翻上一页,q退出。若不分页,运行如下命令显示全部日志。

git --no-pager log

  ;git log有许多选项可以帮助搜寻特定的提交。

  • -p:按补丁格式显示每个更新之间的diff差异,可用于代码审查
  • --stat:显示每个修改的文件增改行统计和此次提交的文件修改统计
  • --shortstat:显示此次提交的文件修改统计
  • --name-only:显示每个已修改的文件
  • --name-status:显示每个已修改的文件并附带修改标记(AMD等)
  • --abbrev-commit:显示部分校验和
  • --relative-date:提交时间显示相对时间
  • --graph:图形显示分支合并历史
  • --pretty:格式化输出信息,包括可用选项oneline(校验和和提交说明)、short(不含提交时间)、full(作者和提交者的名字和电子邮箱)、fuller(日志追加提交者信息)、format(定制要显示的记录格式),其余可参考 官方文档 (opens new window)

# 自定义格式

  若只显示提交说明,运行如下命令。

git log --pretty=format:"%s"

  以下为git log --pretty=format常用的格式占位符写法及其代表的含义。其中作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。当A为某个项目发布补丁,然后某个核心成员BA的补丁并入项目时,A就是作者,而那个核心成员B就是提交者。

  • %H:完整校验和,提交对象哈希字符串
  • %h:部分校验和
  • %an:作者
  • %ae:作者的电子邮箱
  • %ad:作者修订日期,--date选项定制时间格式
  • %ar:作者修订日期(相对时间)
  • %cn:提交者
  • %ce:提交者的电子邮箱
  • %cd:提交日期
  • %cr:提交日期(相对时间)
  • %s:提交说明

# 时间格式

  当前示例时间2020/12/15 18:23:13星期三,若只显示年份运行如下命令 。

git log --pretty=format:"%ad" --date=format:'%Y'

  以下为常用时间格式占位符。

  • %c:格式化输出日期时间Tue Dec 15 18:23:13 2020
  • %x:格式化输出短日期 12/15/20
  • %X:格式化输出短时间 18:23:13
  • %Y:年份 2020
  • %y:年份 20
  • %B:月份 December
  • %b:月份 Dec
  • %d:日期 15
  • %H24小时制 18
  • %I12小时制 06
  • %M:分钟 23
  • %S:秒 13
  • %A:星期 Tuesday
  • %a:星期 Tue
  • %w:星期(0~63
  • %p:上下午(AM/PMPM
  • %j:一年的第几天 350
  • %U:一年的第几周(星期日作为每周的第一天) 50
  • %W:一年的第几周(星期一作为每周的第一天) 50
  • %:时区 +0800

# 筛选

  如下命令筛选作者是xx,时间在2020-12-15 22:20:20后,提交信息包括update,删除或添加字符串cname的最近5条提交。

git log --author=xx --after="2020-12-15 22:20:20" --grep="update" -Scname -5

  以下为Git筛选限制符。

  • -n:最近的n条提交
  • --author:指定作者的提交
  • --committer:指定提交者相关的提交
  • --after--since:指定时间之后的提交
  • --before--until:指定时间之前的提交
  • --grep:提交信息含指定关键字的提交
  • -S:修改内容中删除或添加了某个关键字的提交

# 自定义命令

  配置自定义git log,运行 git lg即可。

git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"

# .gitignore

  工作区有些文件无需纳入Git的管理,也不希望它们出现在未跟踪文件列表。可以创建一个名为.gitignore的文件,列出要忽略的文件模式,文件.gitignore的格式规范如下。注意.gitignore只能忽略那些原来没有被跟踪的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。

  • 所有空行或者以#开头的行都会被Git忽略
  • 可以使用标准的glob模式匹配
  • /开头忽略根目录下文件
  • /结尾忽略指定目录及目录下文件
  • 要忽略指定模式以外的文件或目录,可以在模式前加上!取反
# 忽略所有 .log 结尾的文件
*.log

# 仅忽略项目根目录下的 TODO 文件,不包括 dir/TODO
/TODO

# lib.c 除外
!lib.c

# 忽略 doc/c.md
doc/?.md

# 忽略 dist 及其目录下的所有文件
dist/

# 忽略/foo、a/foo、a/b/foo等
**/foo

  ;glob模式是指shell所使用的简化了的正则表达式。

  • *匹配零个或多个任意字符
  • ?只匹配一个任意字符
  • [abc]abc)匹配任何一个在方括号中的字符
  • [0-9](匹配所有09的数字)在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配
  • 使用**表示匹配任意中间目录,比如a/**/z可以匹配a/za/b/za/b/c/z

# 远程仓库

# 查看

  查看已经配置的远程仓库,运行git remote打印每一个远程仓库的简写。 如果已克隆了远程仓库,至少能看到Git给克隆仓库的默认名字origin

git remote
origin

  查看关联的远程仓库简写及其URL

git remote -v

  查看某一个远程仓库的更多详细信息。

git remote show origin

# 添加

  关联一个新的Git远程仓库,其中github为关联的远程仓库的简写。注意执行如下命令的目录必须是Git仓库,否则无法关联,运行git init初始化后再关联。

git remote add github https://github.com/username/repo.git

# 移除

  移除一个远程仓库。

git remote rm origin

# 重命名

  修改远程仓库的简写,运行如下命令将origin重命名为github

git remote rename origin github

# 标签

# 查看

  ;Git默认以字母顺序列出标签。

git tag

  使用特定的模式查找标签。

git tag -l 'v1.8.5*'

  查看标签信息与对应的提交信息。

git show v1.8.5

# 创建

  ;Git主要使用两种类型的标签,轻量标签和附注标签。其中轻量标签,只是某一次提交的引用。附注标签则包括创建标签的日期时间、创建者的名字和电子邮箱和标签信息,附注标签是存储在Git仓库中的一个完整对象。运行如下命令创建一个轻量标签。

git tag v1.8.5

  创建附注标签时,-m指定存储在标签中的标签信息,若没有指定,Git会运行编辑器要求输入信息。

git tag -a v1.8.5 -m 'version 1.8.5'

  对历史提交记录创建标签,使用如下命令,末尾指定某次提交的校验和或部分校验和。

git tag -a v1.8.0 9fceb02

# 推送

  ;git push命令并不会推送标签到远程仓库,创建完标签必须显示地推送到远程仓库。

git push origin v1.8.5

  一次性推送多个标签,可使用--tags选项。

git push origin --tags

# 删除

  删除本地标签。

git tag -d v1.8.5

  若标签已推送至远程仓库,删除本地标签后,再执行如下命令。其实在路径.git/refs/tags下就存储了所有的tags信息。注意git push origin --delete v1.8.5可以删除远程仓库的标签,当远程仓库分支和标签重名时,运行时会报错,但是输入失误可能删除远程仓库分支,不推荐使用。

git push origin :refs/tags/v1.8.5

# 创建分支

  若想创建一个分支,分支与版本库中某个标签版本完全一样,可使用如下命令。

git checkout -b dev1.8.5 v1.8.5

# Git 别名

  若不想每次都输入完整的Git命令,可以通过git config命令为每一个命令设置一个别名。如下配置了一个git last,显示最后一次提交信息。

git config --global alias.last 'log -1'

# Git 分支

# 概述

  暂存时Git会为每一个文件计算校验和(SHA-1散列,40个十六进制字符组成的字符串,根据Git中文件的内容或目录结构计算出来的),然后会把当前版本的文件快照(blob对象)保存到Git仓库中,最终将校验和加入到暂存区域等待提交。

  提交时Git会计算每一个子目录的校验和,然后将校验和保存为树对象。再创建一个提交对象,包含作者信息、提交信息和指向父提交对象的指针。

  最终仓库中包括若干个blob对象(保存文件快照)、一个树对象(记录目录结构和blob对象索引)以及一个提交对象(包含树对象的指针和提交信息)。

  每次提交产生的提交对象都包含一个指向上次提交对象(父对象)的指针。

  分支其实本质上仅仅是指向提交对象的可变指针。Git默认分支名字是mastergit init初始化仓库后,不存在提交对象,所以也没有分支,手动git branch创建分支也不会生效。首次提交后Git自动创建master分支,并且它会在每次提交操作中自动向前移动。

  ;Git有一个名为HEAD的特殊指针判断当前处于哪个分支,可以理解为HEAD为当前分支的别名。

# 本地分支

# 查看

  查看所有分支,其中*绿色高亮的是当前所处分支,即HEAD指向的分支。

git branch

  查看所有分支最近一次提交的提交信息和部分校验和,其中-v--verbose的简写。

git branch -v

  查看列表中尚未合并到当前分支的分支运行如下命令。 查看哪些分支已经合并到当前分支,可以运行git branch --merged

git branch --no-merged

# 切换

  切换分支执行如下命令,git switch dev也能切换分支,并且语义上更好理解。

git checkout dev

# 创建

  运行如下命令创建本地分支,其中dev为本地分支名。

git branch dev

  创建并切换分支运行如下命令,或者运行git switch -c dev命令。

git checkout -b dev

  提交历史中,指定某一次提交创建新分支。

git branch dev 375ea5e

  远程仓库分支创建本地分支,注意此方式创建的本地分支为跟踪分支。

git branch dev origin/master

# 合并

  合并其他分支的修改到当前分支,其中dev为即将合并到当前分支的分支。若当前分支所指向的提交是dev的直接上游,则Git只是简单的将指针向前移动。当试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候,只会简单的将指针向前推进(指针右移),因为此种情况下的合并操作没有需要解决的分歧,即快进fast-forward

  若当前分支的提交与dev分支的提交没有直接关联,即两个分支一开始有一个共同的父提交对象,但是两分支提交了很多次记录,合并时Git会做一个简单的三方合并,包括父提交对象、两个分支最近一次的提交对象,将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交。以上称为一次合并提交,生成的提交对象有两个父对象。

git merge dev
...
Fast-forward
...

  在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,在合并的时候就会产生合并冲突。此时Git做了合并,但是不会自动地创建合并提交。Git会暂停下来,等待解决合并产生的冲突。 在产生合并冲突后可使用git status命令查看包含合并冲突而处于未合并unmerged状态的文件。

git merge dev
...
Automatic merge failed; fix conflicts and then commit the result.

git status
...
Unmerged paths:
  ...
        both modified:   readme.md

  ;Git会在有冲突的文件中加入标准的冲突解决标记,可以打开包含冲突的文件然后手动解决。出现冲突的文件会包含一些特殊区段,HEAD即当前分支的修改在=======的上半部分,而dev分支所做的修改在=======的下半部分。为了解决冲突,必须选择使用由=======分割的两部分中的一个,或者也可以自行合并这些内容。

<<<<<<< HEAD
hello world
=======
hi world
>>>>>>> dev

  并且不管<<<<<<<=======>>>>>>>标记是否完全删除,只要对冲突文件执行git add,那么则将其标记为冲突已解决。确定之前有冲突的的文件都已经暂存了,可以输入git commit来完成提交。

  禁止快进式合并且生成一个新的提交。由于快进式合并会把dev的零碎提交历史混入,搅乱当前分支的提交历史,若要合并过程仅生成一条合并记录,执行如下命令。

git merge --no-ff dev

  若当前分支合并某个分支dev,但是dev上存在太多琐碎的提交,其实仅仅一条提交记录就行。运行如下命令,接受dev分支上的所有修改,将其压缩成一次变更,然后应用到当前分支上,最后再暂存更改并提交。

git merge --squash dev

# 删除

  删除分支运行如下命令,若某个分支包含未合并的修改,将不能删除分支,运行git branch -D dev强制删除。

git branch -d dev

# 远程分支

# 简述

  远程分支(remote branch)即远程仓库的普通分支。

  远程跟踪分支(remote-tracking branch)是从服务器上获取,以remote/branch形式命名,作用是告诉用户其所跟踪的远程分支的状态(即指向哪一个提交对象),因此用户只读不能移动,在与远程仓库通信(fetchpush等)时会自动移动。

  跟踪分支(tracking branch)即跟踪了远程分支的本地分支。跟踪分支与远程分支有直接关系,在一个跟踪分支上输入git pullGit能自动地识别去哪个服务器上抓取、合并到哪个分支。

  当克隆一个仓库时,会自动创建一个远程跟踪分支origin/master和一个跟踪分支master

  跟踪分支master产生多个提交,推送到远程仓库originmaster分支时,远程跟踪分支origin/master自动移动指向此次提交。

  远程分支master产生多个提交,运行git fetch获取远程分支的最新提交,远程跟踪分支自动指向获取的最新提交。再运行git merge将最新提交合并到跟踪分支mastergit pull等同于获取fetch和合并merge

# 查看

  查看远程引用列表,远程引用是对远程仓库的引用,包括分支、标签等,其中origin为关联的远程仓库简写。

git ls-remote origin

  查看远程仓库更多信息。

git remote show origin

  查看所有远程跟踪分支。

git branch -r

  查看所有分支,-a--all简写。

git branch -a

  查看本地分支与远程分支关联信息,最近一次提交信息和部分校验和,同时显示每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。

git branch -vv

# 推送

  跟踪分支推送修改,运行如下命令,Git自动识别推送到哪个远程仓库的分支。

git push

  本地仓库比远程仓库的版本低,通常情况下是先拉取再推送,运行如下命令强制推送,覆盖远程仓库历史。多人协作模式切勿使用,否则可能会直接覆盖其他人的修改。其中-f--force简写。

git push -f

  推送本地分支到远程仓库,其中HEAD为当前分支,origin为远程仓库简写,master为远程仓库分支,即推送当前分支到远程仓库originmaster分支。若远程没有master分支,会自动创建master分支。

git push origin HEAD:master

  若当前所在分支为dev,推送到远程dev分支,可执行如下命令。

git push origin dev

  推送本地分支到一个命名不相同的远程分支,如下将本地dev分支推送到远端develop分支。

git push origin dev:develop

  推送本地分支同时跟踪远程分支,其中origin为远程仓库简写,master为本地分支。

git push -u origin master

# 跟踪分支

  创建跟踪分支,即创建dev分支跟踪远程仓库origindev分支。

git checkout --track origin/dev

  自定义本地分支名称,其中dev为本地分支名称,origin/dev为远端dev分支。

git checkout -b dev origin/dev

  已有的本地分支跟踪远程分支,或者修改跟踪的远程分支,-u--set-upstream-to等价。

git branch -u origin/dev

# 取消跟踪

  取消跟踪某个远程分支,运行如下命令。

git branch --unset-upstream

# 获取

  跟踪分支执行如下命令,Git自动获取哪个远程仓库的分支的最新提交。

git fetch

  获取远程仓库的全部更新,并拥有那个远程仓库中所有分支的引用,可以随时合并或查看。

git fetch origin

  获取远程仓库某个分支的更新。

git fetch origin master

# 合并

  跟踪分支执行如下命令,Git自动合并哪个远程跟踪分支的最新提交。

git merge

  当前分支合并远程跟踪分支,远程跟踪分支在git fetch拉取的最新数据。

git merge origin/master

  合并没有共同祖先的分支。

git merge origin/master --allow-unrelated-histories

# 拉取

  跟踪分支执行如下命令,Git自动获取并合并最新修改。

git pull

  拉取远程仓库某个分支的更新并合并。

git pull origin master

  拉取没有共同祖先的分支,即合并两个独立分支的历史。

git pull origin master --allow-unrelated-histories

# 删除

  删除远程分支,运行如下命令。

git push origin --delete dev

# 变基

# 概述

  整合来自不同分支的修改的另一种办法,将提交到某一分支上的所有修改都移至另一分支上,使提交历史是一条直线没有分叉。

  原理是首先找到两个分支(即当前分支experiment、目标分支master)的最近共同祖先C2,然后对比当前分支相对于祖先C2的历次提交,提取相应的修改并存为临时文件,最后将临时文件的修改依照顺序应用到C3形成C4'

# 命令

  将当前分支的修改变基到目标分支master,运行如下命令。

git rebase master

  将指定分支的修改变基到目标分支,其中dev为指定分支,master为目标分支。

git rebase master dev

  取出dev分支,找出处于dev分支和develop分支的共同祖先之后的修改,然后把它们在master分支上重放一遍。

git rebase --onto master develop dev

  变基过程也可能产生冲突,Git会暂停变基并告知用户哪个文件导致冲突。

  其中git rebase --abort撤销变基,恢复分支状态为调用git rebase之前。

  ;git rebase --skip跳过冲突文件,即丢弃引起冲突的某次提交的全部修改,后果非常严重,不慎使用可通过git reflog查看部分校验和再版本回退。

  也可手动修改冲突文件,git add暂存更改后运行git rebase --continue继续处理变基的其余部分。

# 风险

  若在已经被推送至公共仓库的提交上执行变基命令,可能出现较大麻烦,造成提交历史混乱,严重将丢弃掉部分修改。之前拉取了公共仓库提交的其他人可执行git pull --rebase命令或者执行如下分解命令。

  一般把变基命令当作是在推送前清理提交使之整洁的工具,并且只对尚未推送的修改执行变基操作清理历史,坚决不对已推送至别处的提交执行变基操作,可避免变基造成的麻烦。

git fetch
git rebase origin/master

# Git 多仓库化

# 协议

  ;Git支持多种传输协议,https://协议、git://协议或者SSH传输协议等。

  本地协议local protocol中的远程版本库就是硬盘内的某一个目录。优点是使用现有的文件权限和网络访问权限,把裸版本库的副本放到其他人可访问的路径,并设置好读写权限即可。缺点是不方便从多个位置多个地点访问。

  ;HTTP协议为运行在标准的HTTP/S端口上并且可以使用各种HTTP验证机制,优点是不同的访问方式只需一个URL并且服务器只在需授权时提示输入授权信息。缺点是在一些服务器上架设HTTP/S协议的服务端可能比较棘手以及管理用户凭证比较麻烦一些,可借助凭证存储工具。

  ;SSH协议支持读写的网络协议,优点是安全性高,传输数据都要经过授权和加密。缺点是不能匿名访问,读取数据也要授权,不利于开源的项目。

  ;Git协议是Git自带的网络协议,优点是没有授权机制,省去了加密和授权的开销。缺点是授权机制不灵活,要么都能推送,要么都不能。

# 本地版本库

  ;D盘根目录下初始化裸仓库(bare repository),即一个没有工作区的仓库,故不能在此目录下执行一般Git命令,通常是作为远端的中心仓库而存在的。其中repo.git仅为文件夹名称,.git后缀可加可不加,但是一般Git版本库都添加.git后缀。

git init --bare repo.git

  克隆本地裸仓库,克隆后的仓库可进行推送push、拉取pull等操作。

git clone D:/repo.git

  首次克隆的仓库内pull会出错,推送一次提交即可,之后克隆的仓库再pull就不会出错。

git pull
Your configuration specifies to merge with the ref 'refs/heads/master'
from the remote, but no such ref was fetched.

  克隆某个仓库为裸仓库,其中D:/respos为本地仓库路径,repos.git为克隆后生成的裸仓库,包括历史提交记录和文件快照。

git clone --bare D:/repos repos.git

# HTTP 免登录

  运行如下命令,Git在验证用户名和密码时会首先向指定的凭据管理器查找凭据,若凭据不存在则提示输入用户名和密码,然后凭据管理器记录凭据,如果凭据存在,则直接使用。

git config --global credential.helper manager

  运行如下命令,则用户名和密码都明文保存在C:/Users/{username}/.git-credentials中,验证通过后用户名和密码会被保存。

git config --global credential.helper store

# SSH 公钥

  运行cd ~/.ssh命令,切换到用户主目录中C:/Users/{username}/.ssh文件夹,切换失败说明文件夹不存在。若切换成功,运行ls查看文件夹下文件,创建过SSH Key会打印id_rsaid_rsa.pub两个文件,id_rsa.pub是公钥,id_rsa是私钥。

cd ~/.ssh
ls
id_rsa  id_rsa.pub

  若.ssh文件夹不存在或者公钥和私钥不存在,运行如下命令创建,其中username@email.com为个人邮箱地址,一路回车使用默认值即可。

ssh-keygen -t rsa -C "username@email.com"

  运行cat ~/.ssh/id_rsa.pub查看生成的公钥内容。

cat ~/.ssh/id_rsa.pub
ssh-rsa ...

  在远端粘贴公钥内容,标题用来区分SSH公钥。本地克隆远端仓库,若.ssh文件夹缺少known_hosts文件,Git提示如下信息,键入yes自动生成。克隆完成后仓库可进行正常的拉取、推送等。

git clone git@github.com:username/repo.git
Cloning into 'repo'...
...
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

# 分布式 Git

# 工作流(workflow)

# 集中式工作流

  一个中心仓库,若干个开发者作为节点与之进行同步。

  只使用master分支进行开发。

  一般开发流程为开发者克隆master分支到本地,本地开发提交后获取远程仓库,若产生冲突本地合并后再推送至远程仓库。

  优点是适用于小团队,工作方式简单。缺点是不同开发人员提交日志混杂,难以定位问题。

# 功能分支工作流

  在集中式工作流基础上,为不同功能开发分配单独的功能分支。

  分支为master主分支和若干feature功能分支。

  开发流程为主分支检出若干feature功能分支,开发者完成某个功能分支推送到中央仓库的功能分支上,然后在中央仓库功能分支上发起full request请求将功能分支合并到master分支,请求后可对代码进行评审(code review)和讨论。

  优点是相较于集中式工作流功能开发更细致,更灵活,代码更加稳定。缺点是对于大型项目而言,不同的分支要分配更加具体的角色。

# Gitflow 工作流

  仍有一个中心仓库和若干开发者作为节点,唯一区别在于项目的分支结构,不同分支赋予不同的角色。

  主要分支为masterdevelop,协助分支featurereleasehotfix

  • master为对外发布分支。只读唯一分支,只能从releasehotfix合并,不可修改。所有在master分支的推送应标记上版本标签
  • develop为主开发分支。只读唯一分支,只能从其他分支合并
  • feature为功能开发分支,可同时存在多个且多个功能并行开发,开发结束合并到develop分支
  • release为预发布分支,只能从develop分支检出,检出分支修复bug后合并到masterdevelop
  • hotfix为热更新分支,线上bug修复分支,只能从master检出,检出分支修复bug后合并到developmaster

  主要工作流程如下。

  • 初始化项目,默认创建master分支,master分支检出develop分支
  • develop分支检出feature分支,多个开发者检出多个feature并行开发
  • feature分支开发完成,合并至develop分支,可删除feature分支,不删除则不可再更改
  • develop分支检出release分支进行提测,测试出bugrelease修复,修复完成合并至develop分支和master分支,合并至master分支时再打上版本标签,可删除release分支,不删除则不可再更改
  • 上线之后发现线上bugmaster分支检出hotfix分支
  • hotfix分支修复测试后合并至develop分支和master分支,合并至master分支时打上修复后标签,可删除hotfix分支,不删除则不可再更改

  优点是分支角色明确,提交日志清晰,出现问题也很容易定位,项目稳定性非常高。缺点是不适用于小团队,过多的分支结构显得复杂。

# Forking 工作流

  分布式工作流,充分利用了Git在分支和克隆上的优势。安全可靠地管理大团队的开发者,能接受不信任贡献者的提交。

  主要分支为远程仓库master分支、feature功能分支。

  主要工作流程如下。

  • 派生(fork)一个服务端的正式仓库A,派生后的仓库B处在远程服务端。
  • 克隆派生后的远程仓库B为本地仓库Cgit clone B
  • 为本地仓库C关联(git remote add upstream A)远程正式仓库A,目的及时地保持本地仓库C和正式仓库A的同步更新
  • 本地仓库C master分支检出feature分支
  • 本地仓库feature分支进行开发工作
  • 本地仓库feature分支完成开发任务,获取正式仓库的更新(git fetch upstream master),合并更新(git merge upstream/master
  • 合并完成推送本地feature分支到远程仓库B feature分支(git push origin feature
  • 远程仓库B发起pull request请求将feature分支合并到正式仓库A
  • 正式仓库A维护者决定是否合并到正式代码库,方式一为直接在pull request中查看代码,比较变更的差异,做评注和执行合并。若出现了合并冲突,执行如下方式二,维护者本地仓库获取派生仓库B feature分支修改并合并,再推送至正式仓库A
git fetch https://github.com/username/B.git feature
git checkout master
git merge FETCH_HEAD

# 拣选

# 命令

  拣选取出某次提交,之后尝试将其重新应用到当前分支。其中cherry-pick后为某次提交的部分校验和。

git cherry-pick 9fceb0

  拣选多个提交。

git cherry-pick 9fceb0 1359a9 ceb036

# 冲突合并

  若拣选提交D,将其应用到F,拣选的是D基于C的补丁。即将D对于C的变化在F上重放一遍。

       C —— D <-- dev
      /
A —— B —— E —— F <-- master

  由于拣选的是某次提交对于父对象的变化,因此可能产生冲突。如上若C新增文件readme.mdD修改readme.md部分信息,而F中根本没有readme.md,此次拣选就会产生冲突,因为readme.md的修改无法应用到F中的readme.md。此时cherry-pick会停下来,由用户决定如何操作。

  • --continue:用户解决冲突后,git add将文件标记为已解决,运行git cherry-pick --continue继续执行
  • --abort:放弃合并,恢复cherry-pick前的状态,即撤销cherry-pick
  • --quit:中断合并,有冲突的文件需手动解决,未冲突的文件会被暂存

# 配置项

  ;git cherry-pick常用配置项如下。

  • -e:若想拣选合并后重新编辑提交信息,其中-e--edit简写
  • -n:若只获取拣选提交的修改并暂存,不生成提交记录,其中-n--no-commit简写
  • -x:在提交信息的末尾追加一行(cherry picked from commit ...),便于查询提交是如何产生的
  • -s:在提交信息的末尾追加一行操作者信息(Signed-off-by: ...),其中-s--signoff简写

# 提交规范

# 书写规范

  提交信息都包括headerbodyfooter三个部分。其中header是必需的,bodyfooter可以省略。

<type>(<scope>): <subject>

<body>

<footer>

  ;type为提交的类型,包括如下几种。

  • feat:新功能
  • fix:修复bug
  • docs:文档修改
  • style:格式,修改空格缩进逗号等
  • refactor:代码重构
  • perf:性能提升
  • test:添加缺失测试或更正现有测试
  • chore:构建过程或辅助工具的变动
  • revert:版本回退

  ;scope小写英文,表示修改的文件或模块范围,若涉及范围较大,可用*代替。

  ;subject提交的简短描述,不超过50个字符。以动词开头,使用第一人称现在时(比如change,而不是changedchanging),第一个字母小写,结尾不加句号(.)。

  ;body补充object,一般不写,修改原因、目的等相关因素。多次少量提交,而不是一次过量提交。

  ;footer非兼容修改(breaking change),以BREAKING CHANGE开头,后面是对变动的描述。也可以在footer部分关闭某些issue(如Closes #123, #245)。

feat(doc,core): a short description of the commit

Add object, generally do not write, modify the
reason, purpose and other related factors.

Closes #123, #245

BREAKING CHANGE: description of changes.

# 提交模板

  用户目录C:/Users/username下新建如下.gitmessage模板文件。

type(scope): subject

  仓库下配置提交信息模板,当git commit提交时编辑器中就会显示模板中的信息占位符。

git config --local commit.template ~/.gitmessage

# 规范提交工具

  初始化npm,安装 commitizen (opens new window)cz-conventional-changelog (opens new window)

npm install commitizen cz-conventional-changelog --save-dev

  初始化配置,使其支持angular的提交格式,其中--save-exact相当于锁定版本号。

npx commitizen init cz-conventional-changelog --save-dev --save-exact

  ;package.json添加脚本命令。

{
  ...
  "scripts": {
    "commit": "npx git-cz"
  }
}

  运行脚本命令。

git add --all
npm run commit

# 检测提交

  规范提交并未限制git commit命令提交,安装检测依赖ghookvalidate-commit-msg,不满足格式的提交会被阻止。

npm install ghooks  validate-commit-msg --save-dev

  配置 package.json。

{
  ...
  "config": {
    "ghooks": {
      "commit-msg": "validate-commit-msg"
    }
  }
}

下一篇

# 🎉 写在最后

🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star (opens new window) ✨支持一下哦!

手动码字,如有错误,欢迎在评论区指正💬~

你的支持就是我更新的最大动力💪~

GitHub (opens new window) / Gitee (opens new window)GitHub Pages (opens new window)掘金 (opens new window)CSDN (opens new window) 同步更新,欢迎关注😉~

最后更新时间: 3/6/2022, 9:06:37 PM