git

基础知识

文件状态

简写 状态 说明 备注
?? Untracked 未跟踪 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制,
通过git add 状态变为Staged
MM Unmodified 已入库未修改 如果它被修改, 而变为Modified,
如果使用git rm移出版本库, 则成为Untracked文件
M Modified 已修改 通过git add可进入暂存staged状态,
使用git checkout 则丢弃修改过, 返回到unmodify状态
A Staged 已暂存 执行git commit则将修改同步到库中, 库中的文件和本地文件又变为一致,
文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified

其他状态描述:

安装

官网:https://git-scm.com/

在线安装

# ubuntu
apt-get install git

源码安装

# 1. 安装依赖库
apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev

# 2. 安装 git 依赖包
apt-get install asciidoc xmlto docbook2x

# 3. 下载源码压缩文件
wget https://github.com/git/git/archive/refs/tags/v2.42.0.tar.gz

# 4. 编译安装
tar -zxf v2.42.0.tar.gz
cd git-v2.42.0
make configure
./configure --prefix=/usr
make all doc info
make install install-doc install-html install-info

安装完成,可以使用 Git 来获取 Git 的升级

git clone git://git.kernel.org/pub/scm/git/git.git

配置

# 查看所有设置
git config --list

Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置:

在 Windows 系统中,Git 会查找 $HOME 目录下(一般情况下是 C:\Users\$USER)的 .gitconfig 文件。 Git 同样也会寻找 /etc/gitconfig 文件,但只限于 MSys 的根目录下,即安装 Git 时所选的目标位置。

全局配置

初次运行时设置。使用 --global 选项,该命令只需要运行一次,之后无论你在该系统上做任何事情,Git 都会使用这些信息。

# 查看所有设置
git config --list

# 设置用户名
git config --global user.name "xxx" 

# 设置email
git config --global user.email xxx@xxx.xx

# 设置Git Gui中文本文件的编码方式
git config --global gui.encoding utf-8

本地设置

对特定项目使用不同的用户名称与邮件地址。

git config --local user.name "xxx"
git config --local user.email "xxx@xxx.xx"
git config --local -l | grep user
# user.name=xieqi
# user.email=xieqi@leinao.ai

.gitignore

格式规范

global模式

简化的正则表达式 |字符|含义| |:—:|:—| |*|匹配零个或多个任意字符| |?|匹配一个任意字符| |a/**/z|两个星号表示匹配任意中间目录| |[abc]|匹配任何一个列在方括号中的字符 (a或b或c) | |[0-9]|匹配所有 0 到 9 的数字|

常用操作

初始化仓库

# 初始化本地仓库
git init
# 根文件夹下包括所有的源代码,git版本控制相关的文件在.git目录下
# .git目录以及其下的版本历史记录文件,这些版本历史记录文件就存放在.git目录下

# 初始化裸仓库 (无工作区)
git init --bare
# 跟目录下只包括git版本控制相关的文件等。但不包括项目源代码
# 只生成.git目录下面的版本历史记录文件,直接存放在版本库的根目录下面
# 一般用于远端备份或公共版本库

远程库

# 查看已配置的远程仓库服务器, "origin" Git克隆的仓库服务器默认的名字
git remote

# 显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL
git remote -v

# 查看某远程仓库的更多信息
git remote show [remote-name]

# 添加远程仓库。在命令行中可以使用字符串 shortname 来代替整个 URL
git remote add [shortname] [url]

# 重命名远程仓库的简写名
git remote rename [old-name] [new-name]

# 移除远程仓库
git remote rm [remote-name]

删除

要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。

## 删除已缓存文件
git rm [文件/目录/global模式]           # 目录需加 -r 选项

# 强制删除, 删除之前修改过并且已经放到暂存区域
git rm -f [文件/目录/global模式]

# -n 不删除任何文件,展示要删除的文件列表
git rm -r -n --cached [文件/目录]

# 让文件保留在磁盘, 但并不让Git继续跟踪。
#   即把文件从 Git 仓库中删除,但仍然保留在当前工作目录中。
git rm --cached [文件/目录/global模式]

## 移动文件
git rm [src_file] [dst_file]

# 相当于

mv [src_file] [dst_file]
git rm [src_file]
git add [dst_file]

对比

# 本地库 vs 暂存区快照 (index), 即本地库修改之后未暂存的内容
git diff

# 查看简单的 diff 结果
git diff --star

# 已暂存 (staged) vs 上次提交时的快照 (HEAD)。即下一次提交时会提交到HEAD的内容
git diff --cached

# 已暂存 (staged) vs 上次提交时的快照 (HEAD)。即下一次提交时会提交到HEAD的内容
git diff --staged

# 将两个分支上最新的提交做 diff
git diff release master

# 将两个分支上最新的提交做 diff
git diff release..master

# 自 release和 master 分别开发以来,master分支上的变更
git diff release...master

# 当前分支与 release 分支的差别
git diff release

# 本地库 vs HEAD
git diff HEAD

# 比较上次提交和上上次提交
git diff HEAD^ HEAD

# 比较两次提交之间的差异
git diff [commit-id] [commit-id]

# 当前分支目录下的lib目录与上次提交之间的差别
git diff HEAD -- ./lib

远程库→本地库

# 把远程仓库克隆到本地, shortname 默认为 origin
git clone [url] [shortname]

# 在当前目录中使用克隆
git clone -l -s -n . ../copy

# 从现有本地目录借用从上游克隆
git clone --reference /git/linux.git
    git://git.kernel.org/pub/scm/.../linux.git
    mydir

# 创建一个裸存储库以将您的更改发布给公众
git clone --bare -l /home/proj/.git /pub/scm/proj.git

git clone 会自动设置本地 master 分支跟踪克隆的远程仓库的 master 分支, 即本地的 master 分支自动”追踪”origin/master分支, 也可手动建立追踪关系.

#指定master分支追踪origin/next分支
git branch --set-upstream master origin/next

git fetch具体细节分两步:

  1. 创建并更新本地远程分支。即创建并更新 origin/xxx 分支,拉取代码到 origin/xxx 分支上。
  2. 在 FETCH_HEA D中设定当前分支-origin/当前分支对应,如直接到时候 git merge 就可以将 origin/abc 合并到 abc 分支上。

缺点: git fetch 会拉取当前项目的所有分支的 commit。

# 拉取远程仓库中有但本地仓库没有的文件,git fetch 不会自动合并或修改当前的工作区
git fetch [url]

# 设定当前分支的 'FETCH_HEAD' 为远程服务器,origin 的 branch 分支。
#   不会在本地创建本地远程分支
git fetch origin [branch-name]
git fetch origin [branch-name1]:[branch-name2]
# 使用远程 branch-name1 分支在本地创建 branch-name2 (但不会切换到该分支),
# 如果本地不存在 branch-name2 分支, 则会自动创建一个新的 branch2 分支,
# 如果本地存在 branch-name2 分支, 并且是 'fast forward',则自动合并两个分支,否则,会阻止

git fetch origin :[branch-name2]
# 等价于: git fetch origin master:branch-name2

远程库→工作区

git pull 等同于 git fetch + git merge

# 从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支
git pull

git pull --rebase origin master
# 拉取已修改的远程库是, 会产生一个merge commit
# rebase 选项是把本地提交一次一个地迁移到更新了的中央仓库master分支之上
# rebase过程中有冲突时, 会在有冲突的提交处暂停rebase过程
# 出现冲突时(不同分支修改同一个文件时可能出现):
# git status    # 查看冲突文件 -- unmerged paths 中
# git add [file]    # 就该冲突文件后暂存
# git rebase --continue # 继续, 以完成剩下的工作
# git rebase --abort    # 回到你执行git pull --rebase命令前的样子

# 取回origin主机的next分支,与本地的master分支合并
git pull origin next:master

# 取回origin主机的next分支, 与当前分支合并
git pull origin next

# 当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。
# 本地的当前分支自动与对应的origin主机 ”追踪分支 ”(remote-tracking branch)进行合并。
git pull origin

# 当前分支只有一个追踪分支, 可省略远程主机名
#   当前分支自动与唯一一个追踪分支进行合并
git pull

# 如果合并需要采用rebase模式,可以使用–rebase选项。
git pull --rebase <远程主机名> <远程分支名>:<本地分支名>

本地库→远程库

使用本地引用更新远程引用,同时发送完成给定引用所需的对象。

# 将本地的branch-name分支推送到remote-name
#  服务器. 有所克隆服务器的写入权限,并且之前没有人推送过,才能生效.
git push [remote-name] [branch-name]

# 省略本地分支名,表示删除origin主机的master分支
#   推送一个空的本地分支到远程分支
git push origin :master

# 将当前分支推送到origin主机的对应分支
#   当前分支与远程分支之间存在追踪关系,本地分支和远程分支都可以省略
git push origin

# 将当前分支推送到origin主机的对应分支
#   当前分支只有一个追踪分支
git push

# 当前分支与多个主机存在追踪关系
# 使用-u选项设置本地分支去跟踪远程对应的分支
git push -u origin master
# 将本地的master分支推送到origin主机,同时指定origin为默认主机.
# 后面就可以不加任何参数使用 git push 了.

# 不管是否存在对应的远程分支,将所有本地分支都推送到origin主机
git push --all origin

# 推送标签(tag), git push默认不会推送标签
git push origin [tag_name]

# 将当前分支推送到远程的同名
git push origin HEAD

# 将当前分支推送到源存储库中的远程引用匹配主机
git push origin HEAD:master

工作区→暂存区

默认情况下,git add命令不会添加忽略的文件. 但可以用-f(force)选项添加被忽略的文件。

# 将新文件添加到索引, 开始跟踪新文件,
#   把已跟踪的文件放到暂存区,
#   合并时把有冲突的文件标记为已解决状态。
# 如果参数是文件,该命令将跟踪该文件,
# 如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。
git add [文件或目录]

# 把目录中所有跟踪文件中被修改过或已删除文件的信息添加到索引库。
#   它不会处理那些不被跟踪的文件。省略目录表示当前目录。
git add -u [目录] 

# 把目录中所有跟踪文件中被修改过或已删除文件和所有未跟踪的文件信息添加到索引库。
# 它不会处理那些不被跟踪的文件。
# 省略目录表示当前目录。
git add -A [目录] 

# 查看中被所有修改过或已删除文件但没有提交的文件,
git add -i [目录]
# 并通过其revert子命令可以查看目录中所有未跟踪的文件,同时进入一个子命令系统。

暂存区→本地库

将索引的当前内容与描述更改的用户和日志消息一起存储在新的提交中。

# 把文件或目录下的所有文件,放入下一次提交
git commit [文件或目录]

# 跳过 git add, 把所有已经跟踪过的文件暂存起来一并提交
git commit -a

本地库→工作区

# 在当前分支上 取出 tag_name 的版本
git checkout tag_name

提交记录

# 按提交时间列出所有的更新
git log

# 每次提交的内容差异
git log -p

# 最近两次提交的内容差异
git log -p -2

# 每次提交的简略的统计信息
git log --stat

# 提交历史显示模式 oneline (将每个提交放在一行显示)
#   其他模式 short,full,fuller
git log --pretty=oneline

# 定制要显示的记录格式
git log --pretty=format:"%h - %an, %ar : %s"

# 展示分支、合并历史
git log --pretty=format:"%h %s" --graph

# 查看某次提交详细信息
git show [commit-id]

撤销

# git commit后, 先运行 git add 添加漏掉的文件再运行此命令
#   git commit后, 修改提交信息, 再运行此命令
git commit --amend

# 取消暂存的文件, git add 后放置于暂存区的文件
git reset HEAD [file]

# 重置HEAD到commit-id处。
#  --soft 不改变index和working copy中的文件
git reset --soft [commit-id]

# 保留工作区和 HEAD 之间的差异
git reset --keep HEAD

# 把当前目录所有修改的文件 从HEAD中签出并且把它恢复成未修改时的样子
git checkout .

# 放弃当前对文件file-name的修改
git checkout master [file-name]

# 撤销对文件的修改, 有两种情况:
#   1. 文件修改后未放入暂存区, 撤销修改: 回到和版本库一样的状态
#   2. 文件修改后已放入暂存区, 撤销修改: 回到添加到暂存区后的状态
git checkout -- [file]

# 检出索引中的所有C源文件
git checkout -- '*.c'

HEAD指针

每个分支都有一个 HEAD 指针, 它指向当前分支的最新提交.

# 查看当前分支 HEAD 指针的 commit id
cat .git/refs/heads/master
git rev-parse HEAD
git rev-parse --short HEAD

# 查看所有本地库分支、远程库分支、标签所指向的commit id
git show-ref --head

# 查看所有本地库分支所指向的 commit id
git show-ref --heads

# 重置 HEAD 指针, 不会破坏任何东西
git reset --soft [commit-id]

# 重置 HEAD 指针, 从尚未提交的暂存区域还原这些更改.
#   仅从暂存区域恢复更改。对文件的工作副本进行的实际更改不受影响.
#   默认Git复位等效于执行 git reset - mixed
git reset --mixed [commit-id]

# 重置 HEAD 指针, 清除暂存区域.
#   将 HEAD 指针重置为特定提交ID的最新提交,并删除本地文件更改.
git reset --hard [commit-id]

分支

可以从现有的分支创建一个新的分支。 也可以使用特定的提交或标签作为起点创建分支。 如果没有提供任何特定的提交ID,那么将以HEAD作为起点来创建分支。

# 列出所有分支, 当前分支前面标记一个 * 号
git branch

# 创建分支--基于当前分支
git branch [branch-name]

# 切换分支
git checkout [branch-name]

# 创建并切换分支--基于当前分支
git checkout -b [branch-name]

# 创建并切换分支--无父分支
git checkout --orphan [branch-name]

# 创建y一个基于master分支的marys分支, 并且换到marys
git checkout -b marys master

# 合并指定分支到当前分支
git merge [branch-name]

# 重命名分支
git branch -m [old-branch] [new-branch]

# 删除指定分支
git branch -d [branch-name]

# 推送本地分支
git push origin [branch-name]

# 删除远程库中指定分支
git push origin --delete [branch-name]

# 推送一个空分支到远程分支,删除远程库中的分支
git push origin :[branch-name]

标签

打标签的操作发生在我们commit修改到本地库之后.

# 查看本地库所有可用标签
git tag -l

# 查看远程库所有标签
git ls-remote --tags

# 查看指定标签的详细信息
git show [tag-name]

# 标记特定提交
git tag [tag-name] [commit-id]

# 创建带有说明的标签, 标记当前HEAD指针
git tag -a '标签名' -m '标签信息' HEAD

# 创建带有说明的标签, 标记特定提交
git tag -a '标签名' -m '标签信息' [commit-id]

# 获取远程库标签到本地
git fetch origin tag [tag-name]

# 获取所有远程库标签到本地
git fetch origin --tags

# 推送一个本地标签
git push origin [tag-name]

# 推送全部未推送过的本地标签
git push origin --tags

# 从本地存储库中删除标签
git tag -d [tag-name]

# 从远程储库中删除标签
git push origin --delete tag [tag-name]

# 推送一个空tag到远程 tag, 删除远程标签,需先从本地库中删除 tag
git push origin :refs/tags/[tag-name]

补丁

补丁是一个文本文件,其内容类似于git diff,但与代码一样,它也有关于提交的元数据; 例如提交ID,日期,提交消息等。

# 单次提交, 为最新的提交创建补丁, 会创建.path文件
git format-patch -1

# 为指定提交创建补丁,
#   n指从commit-id对应的commit开始算起n个提交
git format-patch [commit-id] -n

# 为某个提交创建补丁
git format-patch [commit-id] -1

# 为某两次提交之间创建补丁
git format-patch [commit-id1] .. [commit-id2]

# 使用补丁, 修改本地文件而不创建提交
git apply [patch-name]

# 使用补丁, 会修改文件并创建提交
git am [patch-name]

子模块

.gitmodules 文件用来保存子模块的信息。

激活子模块

git config submodule.modules/OCR-C.activate true

查看子模块

git submodule
# 已检出子模块代码
cedbe91340dbcff661fa089b116441b11b050d38 themes/hugo-nuo (heads/master)

# 前面带 - 表示未检出代码,子模块是空文件夹
-cedbe91340dbcff661fa089b116441b11b050d38 themes/hugo-nuo (heads/master)

创建子模块

方法1

# 在父项目仓库目录下
git submodule add [url] [path]

# with path
git submodule add http://202.38.69.179:8000/ahxieqi/test-submdl.git testsubmdl
# 在当前本地仓库 testsubmdl 文件夹下创建子模块

# without path
git submodule add http://202.38.69.179:8000/ahxieqi/test-submdl.git
# 在当前本地仓库当前文件夹下创建子模块

方法2

例如我们要创建如下结构的项目

project
  |–moduleA
  |–readme.txt

创建project版本库,并提交readme.txt文件

# 将初始化的裸库,存放在project.git文件夹下
git init --bare project.git

# 将project.git库,存放在project1文件夹下
git clone project.git project1

# 将project.git库,存放在project1文件夹下
cd project1                             # 进入project1文件夹
echo "This is a project." > readme.txt  # 创建并写入 a.txt 文件
git add .                               # 将修改添加到暂存区
git commit -m "add readme.txt"          # 将 a.txt 提交到本地库
git push origin master                  # 将修改推送到远程库
cd ..

创建 moduleA 版本库,并提交a.txt文件

# 将初始化的裸库,存放在moduleA.git文件夹下
git init --bare moduleA.git

# 将moduleA.git库,存放在moduleA1文件夹下
git clone moduleA.git moduleA1

# 进入moduleA1文件夹
cd moduleA1
# 创建并写入 a.txt 文件
echo "This is a submodule." > a.txt
# 将修改添加到暂存区
git add .
# 将 a.txt 提交到本地库
git commit -m "add a.txt"
# 将修改推送到远程库
git push origin master

在 project 项目中引入子模块 moduleA,并提交子模块信息

# 进入project1文件夹
cd project1

# 在moduleA目录下添加子模块
git submodule add ../moduleA.git moduleA

git status

git diff

# 将修改添加到暂存区
git add .

# 将修改添加到本地库
git commit -m "add submodule"

# 将修改添加到远程库
git push origin master

修改子模块

修改子模块之后只对子模块的版本库产生影响,对父项目的版本库不会产生任何影响。如果父项目需要用到最新的子模块代码,我们需要更新父项目中submodule commit id,默认的我们使用 git status 就可以看到父项目中submodule commit id已经改变了,我们只需要再次提交就可以了。

# 在主项目里修改 submodule

cd project1/moduleA
git branch
echo "This is a submodule." > b.txt
git add .
git commit -m "add b.txt"
git push origin master    # 将修改同步到子模块的远程库
cd ..
git status
git diff
git add .
git commit -m "update submodule add b.txt"
git push origin master    # 将修改同步到父项目的远程库

更新子模块

先进入子模块,然后切换到需要的分支,这里是master分支,然后对子模块pull,这种方法会改变子模块的分支。

# 进入子模块目录
cd [submodule_folder]

# 切换到需要的分支,默认子模块分支不是master分支
git checkout master

# 进入父项目目录
cd .. 

# pull 子模块的更新
git submodule foreach git pull

git status

git add .

git commit -m 'update submodule add c.txt'

# 更新父项目下的子模块
git push origin master

克隆包含子模块的项目

方法1

先克隆父项目,再更新子模块

# 1. 克隆父项目
git clone https://github.com/maonx/vimwiki-assets.git assets

# 2. 查看子模块
git submodule
-e33f854d3f51f5ebd771a68da05ad0371a3c0570 assets
# 子模块前面有一个-,说明子模块文件还未检入(空文件夹)。

# 3. 初始化子模块
git submodule init
Submodule 'assets' (https://github.com/maonx/vimwiki-assets.git) registered for path 'assets'
# 初始化模块只需在克隆父项目后运行一次。

# 4. 更新子模块
git submodule update
Cloning into 'assets'...
...
Submodule path 'assets': checked out 'e33f854d3f51f5ebd771a68da05ad0371a3c0570'

方法2

递归克隆整个项目

git clone --recursive https://github.com/maonx/vimwiki-assets.git assets
# 递归克隆整个项目,子模块已经同时更新了,一步到位。

删除子模块

# 1. 删除子模块文件夹
git rm --cached assets
rm -rf assets

# 2. 删除.gitmodules文件中相关子模块信息
[submodule "assets"]
    path = assets
    url = https://github.com/maonx/vimwiki-assets.git

# 3. 删除.git/config中的相关子模块信息
[submodule "assets"]
    url = https://github.com/maonx/vimwiki-assets.git

# 4. 删除.git文件夹中的相关子模块文件
rm -rf .git/modules/assets

参考链接:

Gitflow

Gitflow分支

命名约定

分支 名称 作用
master 主分支 存储正式发布的历史
hotfix 上线分支 bug情急修复分支
release 发布分支 发布上线的时候用
develop 开发分支 功能的集成分支
feature 功能分支 开发新功能都会有对应的feature分支

git-flow流程中最主要的五个分支分别为 master,release,develop,feature,hotfix。

分支概述

开发流程

1. 创建远程仓库,并拉到本地。

# 默认master分支的
git clone ...

2. 创建develop分支

master 分支上面是不允许进行开发的,创建长期开发分支 develop

3. 开发新功能

# 开发新功能 a,在 develop 分支创建新功能分支 a
git checkout develop
git checkout -b feature/a

# 将该功能分支推送到远程
git push origin feature/a:feature/a

# 成员可将该分支拉下来
git fetch origin feature/a:feature/a

4. 完成新功能

# 切换到develop分支
git checkout develop

# 拉取远程仓库的最新内容,更新本地 develop 分支
git pull origin develop

# 将 feature/a 分支合并到 develop 分支
git merge --no-ff feature/a   # --no-ff 参数可以保存feature/a分支上的历史记录

# 推送更新后的 develop 分支到远程仓库
git push origin develop

# 合并完成后,删除本地 feature/a 分支
git branch -d feature/a

# 删除远程 feature/a 分支
git push origin --delete feature/a

5. 发布新功能

当新功能基本完成之后,我们要开始在release分支上测试新版本,在此分支上进行一些整合性的测试,并进行小bug的修复以及增加例如版本号的一些数据。版本号根据 master 分支当前最新的 tag 来确定即可,根据改动的情况选择要增加的位.

# 创建发布分支
git checkout -b release/1.2.0 # 在 develop 分支中开
git push origin release/1.2.0:release/1.2.0   # 将分支推送到远程(如果有必要)

# 保证本地的release分支处于最新状态
git pull origin release/1.0.0 # 将本地的release分支内容更新为线上的分支

# 制定版本号
# commit 一个版本, commit的信息为版本升到 1.2.0
# git commit -a 相当于 git add . 再 git commit
git commit -a -m "Bumped version number to 1.2.0"

# 将已制定好版本号等其他数据和测试并修复完成了一些小bug的分支合并到主分支
git checkout master   # 切换至主要分支
git merge --no-ff release/1.2.0   # 将release/1.2.0分支合并到主要分支
git tag -a "1.2.0" HEAD -m "新版本改动描述"   # 上标签

# 将 release 分支合并回开发分支
git checkout develop  # 切换至开发分支
git merge --no-ff release/1.2.0   # 合并分支

# 推送到远程仓库
git push origin develop   # 将开发分支推送到远程
git push origin master    # 将 master 分支推送到远程

# 删除分支
git branch -d release/1.2.0
git push origin --delete release/1.2.0

6 修补线上Bug

此修复 bug 针对的是线上运行的版本出现了 bug,急需短时间修复,无法等到下一次发布才修复,区别于开发过程中 develop 上的 bug,和测试过程中的 release 上的 bug,这些 bug,在原分支上改动便可以。

# 在master根据具体的问题创建 hotifix 分支,并推送到远程
git checkout master
git checkout -b hotfix/typo
git push origin hotfix/typo:hotfix/typo

# 制定版本号,commit一个版本, commit的信息是版本条
git commit -a -m "Bumped version number to 1.2.1"

# 修正后 commit 并将本地的 hotfix 分支更新为线上最新的版本
git commit -m "..."
git pull origin hotfix/typo

# 将刚修复的分支合并到开发分支和主分支
git checkout develop  # 切换到开发分支
git merge --no-ff hotfix/typo # 合并
git checkout master   # 切换到主要分支
git merge --no-ff hotfix/typo # 将hotfix分支合并到主要分支
git tag -a "1.2.1" HEAD -m "fix typo" # 上标签

# 删除修补分支

附录

git clone 速度很慢的解决方法

  1. 浏览器访问 https://www.ipaddress.com/,获取 github.global.ssl.fastly.net、global-ssl.fastly.net、assets-cdn.github.com 和 github.com 的 IP

  2. 修改 hosts,增加 host 映射

     xxx.xxx.xxx.xxx github.global.ssl.fastly.net
     xxx.xxx.xxx.xxx global-ssl.fastly.net
     xxx.xxx.xxx.xxx assets-cdn.github.com
     xxx.xxx.xxx.xxx github.com
    
  3. 更新 DNS 缓存

     # macOS
     dscacheutil -flushcache
     # Windows
     ipconfig /flushdns
     # Linux
     service nscd restart
     # Ubuntu
     sudo /etc/init.d/dns-clean start
    
  4. 只获取代码的最新版,再获取完整历史信息

     git clone --depth=1 https://github.com/xxx/xxx.git
     cd xxx
     git fetch --unshallow
    

一个仓库管理多个项目

  1. 一个仓库可以管理多个项目,仓库目录如下

    rep-1
      | _ folder-a
        |_project-a
        |_project-b

  2. 各项目之间独立存在
  3. 为各项目打上标签
  4. 标签的压缩文件内不能包含其他项目文件,仓库公有文件除外
  5. 考虑使用 git workflow

1. 克隆父项目远程仓库

可以先创建一个远程空仓库

git clone git@xxxxxx/rep-1.git    # 克隆父项目远程仓库,到当前路径
cd rep-1  # 进入父项目目录,默认进入 master 分支

2. 创建项目公有文件

项目公有文件:各项目都包含的文件,标签的压缩文件内包含公有文件

可在rep-1或folder-a文件夹下创建project-a和project-a的公有文件。在创建项目分支前,创建公有文件,可减少跟踪文件的增删。若不需要项目公有文件可不创建。

# master 分支下,创建并切换到 develop 分支
git checkout -b develop   # 父分支为 master
cd folder-a
touch README.md   # 创建项目公有文件
git add README.md
git commit -m ''
git push origin develop:develop

3. 创建项目分支 – aa

在 develop 分支上创建无父分支的项目分支 aa。

# 当前,folder-a文件夹 develop 分支
git checkout --orphan [branch-name]   # 创建并切换无父分支的新分支
git ls-files    # 查看当前分支追踪的文件,是否有公有文件

# 删除暂存区当前分支不需要追踪的文件或文件夹
git rm --cached -r [folder-name]
git rm --cached [file-name]
git ls-files    # 再次查看,确认删除不需要追踪的文件

cd project-1  # 进入 project-1 的目录
git add .   # 添加当前文件夹中的内容到暂存区
git commit -m 'commit 说明文字'   # 将暂存区的内容提交到本地库
git push origin [branch-name]:[branch-name]   # 将本地库的内容推送到远程库
git tag -a [tag-name] -m '标签说明' [commit-id]   # 打标签
git push origin [tag-name]    # 将标签推送到远程库

4. 创建项目分支 – ab

# 重置 HEAD 到当前 (aa) 分支的第一个 commit
git reset --soft [commit-id]  # --soft 不改变index和working copy中的文件

# 切换到 develop 分支
git checkout [branch-name]

# 查看跟踪文件
git ls-files

# 添加或删除跟踪文件或文件夹,develop 分支只可跟踪公有文件
git add [file]
git rm [file]

# 重置HEAD
git reset --keep HEAD
# 保留工作目录与HEAD的文件差异, 将index将全部复原成HEAD
# 即把在 HEAD 之后的提交清除掉,但保持工作区不变

# 在 develop 分支上创建无父分支的分支 ab
#  同创建分支aa

5. 注意事项

  1. 创建好develop分支并提交公共文件后,在 develop 分支的同一个 commit 处创建项目分支;
  2. 分支切换:

    1. reset –soft 到当前分支的第一个 commit
    2. 切换到 develop 分支
    3. 切换到所需无父分支的分支
    4. 切换后的分支应处在当前分支的第一个 commit
    5. reset –soft 到当前分支最新的 commit

例如

当前 HEAD 在 aa 分支 a811d95 处,要切换到 ab 分支,切换流程如下:

git reset --soft 229bbcd  # 将分支 aa 的 HEAD 指向 229bbcd
git checkout ab   # 切换分支
git reset --soft 307d568  # 将 HEAD 指向当前分支的最新一次提交
git reset --keep HEAD # 重置 HEAD

Git 冲突

git 版本控制下的源码安装

1. 克隆源码

# 方法1
git clone git@github.com:jpe/some_repo.git    # 不包含子模块
git submodule update --init --recursive       # 更新子模块并初始化

# 方法2
git clone --recursive git@github.com:jpe/some_repo.git    # 一次克隆主模块和所有子模块并初始化

2. 更新

# 从远程库获取所有主模块和所有子模块的更新
git fetch --all --recurse-submodules

3. 版本切换

# 只有主分支,使用版本标签创建版本分支。
git checkout -b v1.0.0 v1.0.0     # 从主模块切换到某个版本,创建一个版本分支
git submodule update --recursive --force  # 更新子模块到相应的版本

# 有版本分支
git checkout foo_local_branch     # 从主模块切换到某个分支
git submodule update --recursive --force  # 更新子模块到相应的版本

--force 选项:如果不使用,切换时,如果有文件或目录的增删,会报出警告:

warning: unable to rmdir foobar: Directory not empty

4. 查看

# 列出所有子模块和它们的commit
$ git submodule status --recursive

5. 修改子模块

先在 Github 上 fork 这个仓库并添加远程控制。即便是从 fork 的仓库上克隆的也需添加远程映射。

例如,fruit、fruit/apple 是在主模块中嵌套的子模块:

cd fruit
git remote add joe_fruit_remote git@github.com:joe/fruit_repo.git

cd apple
git remote add joe_apple_remote git@github.com:joe/apple_repo.git

主模块的版本或分支与子模块的版本或分支是相互关联的。也就是说,在子模块的版本或分支上显示 detached HEAD 。此外,这也是子模块提交给父模块的版本。因此,当从主模块切换到一个本地分支并且在层次结构的子模块中修改文件时,典型的流程是:

例如,fruit 和 fruit/apple 是主模块中嵌套的子模块:

git checkout -b foo_local_branch origin/foo_remote_branch
git submodule update --recursive

cd fruit
git checkout -b fruit_local_branch
vim change_some_files_in_fruit_submodule.sh


cd apple
git checkout -b apple_local_branch
vim change_some_files_in_apple_submodule.sh

git add change_some_files_in_apple_submodule.sh
git commit -m "Changes in fruit/apple go first"

cd ..
git add change_some_files_in_fruit_submodule.sh
git commit -m "Changes in fruit go next"

cd ..
git add -u
git commit -m "Commit new fruit submodule revision to main module"

6. 推送到 fork 的库

修改完成之后,将修改的内容推送到 fork 的库 (自己的库)。一般将创建的子模块的本地分支推送到 fork 的远程分支。因为之后的 pull request 会很简单。继续上面的例子:

cd fruit
cd apple

git push joe_apple_remote apple_remote_branch

cd ..
git push joe_fruit_remote fruit_remote_branch

cd ..
git push origin foo_remote_branch

7. 发送 pull request

修改完成之后,将修改的内容推送到 fork 的源库 (其他人的库)。

Table of Contents