Git 常用命令

Git Git 相比以前 SVN 的集中式版本控制系统有分布开发的优势,提高了团队、个体的开发效率;其特有的哈希存储系统也保证了内容的完整性(后来的区块链技术也是类似的逻辑)。

基本原理

Git 保存数据的几个(抽象)位置

Git

  • Working directory 工作目录,距离用户最近,是直接操作的那个文件夹
  • Index Staging area 索引操作的舞台(临时区),可以从这里 commit 到真正的仓库
  • Local repository 本地的数据仓库,进入真正的 Git 文件系统保存
  • Upstream(在本地磁盘保存) 远端的 Git 库,用于分布式协作
  • Remote repository(图上未画出) 真正的远端库,用 fetch、push 进行交互。

  • 其中本地仓库又分为 origin 和 local,origin branch 是远端拉取到本地的映射,通常是只读使用;local branch 是自己新创建或基于 origin branch 创建的 branch,可以直接操作
  • 另外,还有一个 stash 栈区域,可以保存临时文件
  • 在执行checkout等绝大多数命令,实际上都是在操作HEAD文件,它标记了当前操作的位置,默认是 Staging area,也可以checkout到某个历史 commit 进入detached只读模式

Git 文件系统中的元素

Git 使用 hash 存储系统,系统中每个元素都形成一个 hash,通过元素中的指针记录相关关系。这样重复的文件只要一个 hash 就可以代表了,避免重复存储。

Git

  • blob 一个文件的 hash 及相关信息
  • tree 一个文件夹的 hash 及其子文件的指针信息
  • commit 一个仓库的快照,以 commit 链表的方式表示历史记录
  • branch 由 commit 链表形成分支结构
  • tag commit 的别名,比如常见别名:
    • HEAD 是当前 branch 最近一次 commit 的别名
    • xxx^ xxx 可以是HEADcommit_hash,表示某次 commit 之前一次 commit
    • xxx^^ 前两次 commit,依次类推
    • xxx~1xxx^,表示前一次 commit,依次类推
  • index 将一个 branch 中的 commit 串联在一个历史轨迹中,index 就是这条轨迹

项目文件结构

.git 文件夹存储除工作目录外的 git 数据

  • config 文件 本项目的配置文件,如果配置文件中没有描述,则使用全局配置文件的设置项
  • HEAD 文件夹 本地创建的 branch 的引用信息
  • refs origin 中的引用信息
  • objects 真正的文件压缩数据
  • hooks 一些脚本
  • modules 子模块

自定义 ignore 文件忽略策略

在项目的各文件夹中可以增加.gitignore文件,在其中配置文件忽略策略。

  • .gitignore文件的作用范围是本文件夹及子文件夹,比如在整个项目中都起作用的策略可以放在根目录的.gitignore文件中
  • 可以通过git config --global core.excludesfile ~/.gitignore设定跨项目的全局忽略策略
  • .gitignore文件中可以添加对文件、文件夹的忽略,从上到下进行规则匹配(下面的覆盖前面的)
  • 如果某文件之前已经被版本控制了,则增加.gitignore不会影响以前的文件,如果想取消之前的文件的版本控制,可以git rm --cache xxx.xx
  • 如果想在 git 项目中保存空文件夹,可以在该文件夹中放入一个.gitignore文件来实现

  • .gitignore文件语法:
    • /开头表示文件夹,可以放在名称开头表示根目录,名称后表示文件夹
    • *匹配 0 到多个字符、?匹配单个字符
    • []包含单个字符的匹配列表
    • !表示不忽略匹配项,用于覆盖父.gitignore策略
    • 示例:
      • fd1/*忽略fd1文件夹下的所有文件,递归影响所有目录,不管是根目录还是子目录都会被忽略
      • /fd1/*忽略根目录中fd1文件夹下的所有文件,只影响根目录

常用命令

config

配置项操作

git config xxx

git config [--local] configName "configValue" 默认将配置项记录在本项目的./.git/config文件中,只在当前项目起作用。

git config --golobal configName "configValue" 使用全局配置文件,在所有未设定的项目中起作用。

git config --unset configName 删除配置项。

git config --system --unset credential.helper 删除系统记录的默认用户名密码,以便重新输入密码。

记住密码

git config --global credential.helper store 在用户主目录的 ~/.gitconfig 文件中生成下面的配置。

1
2
[credential]
	helper = store

如果没有--global,则在当前项目下的.git/config文件中添加。

  • 可以利用 git 缓存将密码记住一段时间 git config --global credential.helper 'cache --timeout=3600' 1 小时内不再重复输入密码

设定 git 操作的账号

1
2
git config user.name "myUserName"
git config user.email "myUser@Email"

PS: 注意,如果不同项目针对的是不同的法律主体,一定不要搞错了,要及时更换默认的 global 设置。否则可能发生用前一家公司的账号向新公司提交的囧况。

设置简写命令

~/.gitconfig中可以添加如下子命令简写:

1
2
3
4
5
6
[alias]
st = status
ci = commit
br = branch
co = checkout
df = diff

init

创建一个仓库

git init 在本文件夹创建一个 git 仓库

git init xxx 在 xxx 文件夹中创建一个 git 仓库

clone

从远程 clone 一个库到本地

git clone xxx 将 xxx 库 clone 到当前文件夹,文件夹命名为 xxx,并且自动 checkout 出来其中的默认 branch,默认为 master。

git clone url_aaa bbb clone 到 bbb 文件夹下,不实用默认的文件夹名

  • -n/--no-checkout 不自动 checkout

clean

清理未被版本控制的文件,从命令执行的文件向下递归执行清理。

  • -n dry run

  • git clean 清理未被版本控制的,但不清理 ignore 的文件。

  • git clean -fX 清除被 ignore 的文件。

remote

管理远端库地址信息

git remote 显示远端库信息,origin 就是默认的一个远端库名称。

  • -v verbose,查看远端库地址及其名称

git remote add originName http://xxx.xxx/xxx.git 添加一个远端库名称及其对应的远端库地址

git remote remove originName 删除远端库

git remote prune origin 参照远端库清理本地远端库分支。 本地库运行时间较长后,远端拉取的 branch 在本地的远端库会有残留,执行上面语句可以清除。

git remote rename oldName newName 重命名远程库名

git remote show origin 显示当前 branch 的远端库

status

最常用的命令,可以查看当前的 branch 及所处的状况

  • -u等价于--untracked-files,显示未跟踪的文件

checkout

直译为”检查”,意思是将文件拿过来查看、检查。在 git 中的作用是改变 HEAD,从而切换当前工作目录所处的 branch 或 commit。checkout 操作并不会影响未进入版本控制的文件。

git checkout branchName 切换到某个已存在的本地 branch 进行开发,如果不存在则自动尝试从 origin checkout 出同名 branch。

git checkout -b branchName1 remotes/origin/branchName2 以 origin branch 为 upstream 创建一个本地 branch

git checkout tagName/commit_hash/'origin/branch_name' 切换 HEAD 到一个 commit 快照或 tag,进入detached HEAD只读模式,通常用来查看某个版本的代码。

git checkout [HEAD] filePath 从特定的 commit_hash 检出,特定文件,覆盖当前工作目录中的文件。如果不传 commit_hash 默认为 HEAD。

switch

用来替代 checkout 的分支管理功能

git switch branch_name <=> git checkout branch_name 切换到分支

git switch -c branch_name <=> git checkout -b branch_name 创建一个 branch

restore

用来替代 checkout 的文件恢复功能

git restore --worktree file_name... <=> git restore -W file_name... <=> git checkout file_name... 将工作区的文件修改还原为修改前的状态

git restore --staged <file>... <=> git restore -S <file>... 将指定的文件还原到变更未 add 的状态,也就是 unstage 状态

git restore -s commit <file>... 将指定的文件还原到某个 commit,其中 commit 可以用 HEAD^ 等填充

add

将文件添加到 stage 准备 commit

git add xxx/xxx.xxx 添加某个文件

git add . 添加所有变更文件,包括以前没有版本控制的文件

  • -u update, 添加所有已存在于 index 的变更文件
  • -A all, 相当于git add .

rm

将文件从 git 版本控制中删除,但是实际文件并不会被删除。

git rm xxx 从 git 版本控制中删除 xxx 文件

  • -r 循环删除文件夹及其中的文件

mv

重命名文件或移动文件位置

commit

将 stage 的代码提交到当前 branch

git commit

  • -a add, 在 commit 前先执行git add -u将已纳入版本控制的变更放入 stage
  • -m message, 后面添加 log 内容

git commit --amend -m "New commit message" 修改最后一次提交的 message,如果已经 push 过,则再次 push 时需要加 -f。

git commit --amend --reset-author 修改最后一次提交的作者信息

reset

git reset [--模式] [commit-hash] 将当前 branch 的 index 从 HEAD 设置到特定的 commit。如果不填 commit,默认是 HEAD。这个过程中会取消 stage 中的文件。

  • --mixed 默认模式,改变 index 但不改变 working directory
  • --soft 只变更 HEAD 指向,index 和 working directory 都不改变,如果指向了过去的 commit,则后来的所有变更都将存在于 stage。
  • --hard 重置 index 和 working directory,丢弃所有变更

git reset 相当于git reset --soft,把所有已 add 到 stage 的文件返回到非 stage 状态

git reset --hard b6918745efad2fe4e5a41b65cfd0571e02410bd4 强制回退当前 branch 的主线

fetch

从远端库拉取到 Upstream 文件区

git fetch [origin] 默认 origin 为远端名称,也可以使用自己添加的远端名称

pull

执行 fetch + merge 动作

--rebase 不执行 merge 动作,改为 rebase

push

将本地版本推送到远端

git push origin br 第一次推送分支到远程

  • -u upstream,在推送的同时设定当前 branch 的远端映射,适用于第一次 push

git push -d origin br 删除远程分支

log

git log [commit] [filePath] 显示每个 commit 对应的 hash 及 log message。默认以当前 branch 为主轴显示,只显示与当前 branch 有关的 commit。如果传入filePath则围绕这个文件显示 log。

  • [commit]只查看指定 commit 及之前的 log
  • [filePath]只查看指定文件变更的 log
  • --graph 增加分支图的显示;
  • --decorate 显示 commit 的引用;
  • --simplify-by-decoration 只显示被 branch 或 tag 引用的关键 commit;
  • --oneline/--pretty=oneline 每个 commit 精简于一行显示,省略细节信息;
  • --all 表示所有 branch,也可以替换为多个 branch 名;
  • -Sstr 又名 pickaxe,对str进行搜索,所有包含特定字符串的增加、修改的 commit 会被列出,用于找到某个特定的字符串变更所涉及的版本。
    • -S "hello world"可以搜索中间有空格的字符串
  • --grep "str" 在历史 commit message 中搜索匹配的字符串,列出这些 commit
  • --author "str" 在历史 commit author 中搜索匹配的字符串,列出这些 commit
  • --patch/-p 显示 log 的同时显示 patch 的内容
  • -5 显示最近 5 条 log

git log commit_hash 显示 commit_hash 之前的 log

git log -p -5 xxx/file 查看某个文件最近 5 次的变更历史,将把变更细节也显示出来。

reflog

reflog(Reference logs) 与远端库没有直接关系,是完全保存在本地的操作记录,记录着 HEAD 的变更历史,通常用来进行回退,即使是reset --hard的误操作都可以回退。

git reflog 查看 HEAD 的整个变更历史,显示如下:

  • 原文:123456 (HEAD -> dev, origin/dev) HEAD@{2}: checkout: moving from aaa to bbb
  • 解释:commit (当前branch, 对应的origin branch) HEAD@{2}: 动作

  • commit每一行开头是 commit 的缩写,可以用来checkout,这样可以回到任何历史版本
  • HEAD@{2}表示 HEAD 指针在两次移动之前的情况
  • 动作可能是commit/checkout/pull

git reflog expire xxx 将过去某一时间之前的 reflog 删除

git reflog delete xxx 删除某一 reflog

git reflog exists <ref> 检查一个 ref 是否有一个 reflog

branch

git branch br 从当前工作目录创建分支,创建时并不会 checkout 到新 branch

git checkout -b new_branch_name [remotes/origin/]old_branch 创建 branch 并 checkout 到该 branch

git branch -vv 查看当前本地 branch 的信息,包括每个 branch 对应的远端 branch (upstream)名。

git branch --set-upstream-to origin/xxx 设置当前 branch 的远端 branch 名

git branch --unset-upstream 取消当前 branch 与远端 branch 的映射关系

git branch -a 查看所有 branch 信息,远端 branch 名。

git branch -D br 删除本地分支

git branch -r -D origin/br 删除本地的远程分支,-r 表示把 remotes 信息也更新

git branch -m oldName newName 重命名分支,可以重命名本地分支,然后删除远程分支重新 push

revert

git revert commit_hash1 [commit_hash1...] 将特定一个或多个 commit 代码回滚,这个回滚会作为新的一次 commit。

  • --contunue/skip/abort/quit 回滚过程中控制流程的开关

tag

为某个版本建立一个快照并打上标签,用于基线控制。

git tag -l ['v0.1.1.*'] 列出所有的 tag,可以增加模式匹配字符串。

git checkout tagName checkout 出 tagName 对应的版本,并进入“detached HEAD” ,只读,无法进行更改。

git checkout -b branchName tagName checkout 出 tagName 并创建为一个 branch,可以后续修改。

git tag tagName [commit_hash] 新建一个轻量级的(lightweight)tag,相当于给 commit_hash 一个别名,默认 commit_hash 是 HEAD。

git tag -a v1.1.1 -m 'this is an annoted tag names v1.1.1' [commit_hash] 新建一个带附注(annoted)的 tag,该 tag 对应的版本将形成一个独立的存储,并包含名字、邮箱号、日期、备注、校验等信息,允许使用 GPG(GNU Privacy Guard)来签署和验证。

git push origin tagName 推送 tag 到远端

git push origin --tags 推送所有 tag 到远端

git tag -d tagName 删除本地的 tag

git push origin :refs/tags/tagName 删除了本地的 tag 后,用这条语句可以删除远端的 tag

show

  • --stat只显示概要信息

git show xxx 显示某一次 commit 的内容

git show tagName 显示 tag 相关信息

diff

--stat 查看概要信息

git diff 查看当前 workspace 已经发生的变更

git diff xxx/xxx 查看具体文件在当前 workspace 下发生的变更

git diff hash1 hash2 --stat 查看两次 commit 的概要差异,左边是旧版本、右边是新版本,这里的 hash1 hash2 只需要输入一部分就可以了

git diff HEAD^ HEAD commit 名称即使用前缀也不容易记忆,可以使用最近几次 commit 的别名:HEADHEAD^HEAD^^…,分别表示 最后一次 commit、倒数第二次、第三次…

git diff hash1 hash2 xxx1/xxx1.xx [xxx2/xxx2.xx] 查看两次 commit 的某[些]文件的具体差异

git diff hash1 hash2 查看两次 commit 的所有文件的具体差异

git diff branch1 branch2 查看两个 branch 的差异,其他操作类似 hash

rev-parse

将特定的引用解析为 commit

git rev-parse HEAD 查看 HEAD 对应的 commit

format-patch

生成 patch 文件,与git apply命令配合可以打补丁包。与 linux 自带的 patch 相比更适合多层级多文件的情况,另外还保留了 commit log。生成的文件名形如:0001-this-is-commit-log.patch,前面是一系列补丁中的序号、后面是由 commit log 中非特殊字符拼接的。

git format-patch hash1[..hash2] 将(hash1, hash2]生成一系列带序号的 .patch 文件。可以用的几个版本别名:--rootHEADHEAD^HEAD^^…,分别表示 初始无 commit 状态、最后一次 commit、倒数第二次、第三次…

git format-patch -1 hash 只生成一个 commit 对应的 patch,-1表示左边版本与右边版本的距离,这里表示前面 1 个版本。

  • -o folderPath 将生成的补丁(组)输出到指定文件夹

patch 文件语法

从上到下描述文件中各个段的意义:

  1. From xxx Mon Sep 17 00:00:00 2001 commit_hash 及 协议版本时间

  2. From: CodeHunter <CodeHunter2006@gmail.com> commit 提交者签名

  3. Date: Tue, 19 Nov 2019 20:11:04 +0800 生成补丁的时间

  4. Subject: [PATCH] [bugfix]: xxx commit 的 log message

  5. 以分割符---开头,表示变更概览内容,变更的文件、文件增+-行数、总的变更数

1
2
3
4
---
 xxx/xxx/xxx.go     | 14 +++++++-------
 aaa/aaa/aaa.txt    |  5 +++++
 2 files changed, 12 insertions(+), 7 deletions(-)
  1. diff --git a/xxx/xxx.go b/xxx/xxx.go 开始描述具体变更的文件,左边a...是变更前文件,右边b...是变更后文件

  2. index 1aa17cf..677c50c 100644 变更前后索引的变化,左边是变更前,右边是变更后

  3. 表示开始描述具体文件变更内容

1
2
--- a/xxx/xxx.go
+++ b/xxx/xxx.go
  1. @@ -544,17 +544,15 @@ func xxx 表示下面的文字内容是从文件的 544 行往后变更了 17 行,变成了 15 行(删除了两行),右边是 544 行对应的文本

  2. 接下来就是具体的行的变更-表示删除,+表示增加,-紧接着+表示替换

1
2
-testA
+testB
  1. 接下来是重复的 6 ~ 10 描述不同文件的变更

  2. 最后是结束分割符和 patch 文件协议版本号

1
2
--
2.23.0

apply

应用一个 patch,将变更加到当前的工作目录,之后需要自己手动进行 commit 的操作。

git apply xxx/xxx.patch 打一个补丁文件

  • --stat 查看 patch 的概要情况
  • --check 检查是否能够打上,如果没有报错则说明可以打上
  • --reject 强行打补丁,冲突的部分会保存为.rej文件

am

apply manager,批量执行 apply 动作,自动进行 commit。

git am xxx/*.patch 按照文件序号打上一系列补丁文件

  • --signoff 在打补丁时在 commit log 中加上自己的签名,有时可能使用的是别人的补丁包

git am --abort 放弃之前打上的补丁

git am --resolved--continue等价,打补丁冲突后,手动解决了冲突,然后执行这条命令继续打 patch

cherry-pick

选定特定的一些 commit,合并到当前工作目录

git cherry-pick <commit>... 将一个或多个 commit 合并到当前工作目录。

  • -n no-commit,合并后不提交,需手动继续操作
  • -ff fast-forward,如果 HEAD 与当前 commit 一致,则自动 fast-forward
  • --continue/skip/abort/quit 合并过程遇到冲突、手动处理后的命令,继续/跳过/放弃/保留现场并退出
  • -m 1/2 如果某个 commit 正好是一次 merge,则它包含两个父节点。用 1 2 区分两个父节点。其中 1 表示时间最近的那次父节点,即 merge 的目标分支。

merge

将源 branch 的所有新增 commit,按顺序合并到当前工作目录 branch。

git merge br

git merge --continue 继续 merge,将源 branch 的 commit 逐步放入当前 branch

git merge --abort 放弃本次 merge 过程,恢复到当前工作目录 branch merge 前的状态

git merge --quit 放弃本次 merge 过程,并停留到当前状态

rebase

有时我们需要将当前 branch 的 commit 全部提出,然后在目标 branch 上有选择行的逐一 commit 上去,并且可能修改 commit log。rebase 命令就为这个场景提供了一系列方便的操作。将当前工作目录所在 branch 的所有 commit 提出到临时区域,直到找到与 rebase 目标 branch 同根的 commit,然后将目标 branch 作为当前 branch 的根,将临时区域的 commit 逐个选择、编辑 log、commit。整个过程 HEAD 将发生逐步移动。

git rebase br 以 br 为目标 branch,进行 rebase 动作。遇到冲突时会暂停,手工解决后继续;或回滚,放弃 rebase。

git rebase --continue 继续 rebase,将缓冲区的 commit 逐步放入当前 branch

git rebase --skip 没有实际的文件冲突,只是有冲突标记,可以跳过本次冲突,继续 rebase

git rebase --abort 放弃本次 rebase 过程,恢复到当前工作目录 branch rebase 前的状态

git rebase --quit 退出 rebase 过程,但保留现场。由于缺少最新代码,将导致当前工作 branch 无法正常工作。

git rebase -i hash_old [hash_new]

以交互模式,将当前的 branch 在(hash_old, hash_new](左开右闭)区间内执行一次 rebase 操作,每条 commit 将被重新给予变更机会。范围可以用HEAD等名称。交互模式将以文本显示、修改、退出、执行的方式进行。

操作流程:

  1. 显示出交互界面——一个 VIM 文本编辑界面(commit 处理方式选择)
  2. 编辑文本、准备好待执行的命令(commit 处理方式选择/位置上下调换)
  3. 退出编辑:q,同时执行编辑中的命令,继续 rebase
  4. 处理冲突/编辑 log
  5. 退出编辑:q,同时执行编辑中的命令,继续rebase --continue
  6. 中间阶段时,会在 4 ~ 5 之间循环,直到完成 rebase 或放弃rebase --abort
  • 注意,不同版本显示的界面版本顺序可能不同,要看清新旧版本是从上到下还是从下到上排列

在交互时修改每条 commit 前的标记,在退出文本后就会按照标记进行 rebase。下面是可用的标记及功能说明:

1
2
3
4
5
6
7
8
9
10
11
# 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

stash

以栈的形式将临时变更缓存在当前 git 库的公共区域,在需要时可以恢复、继续使用。只有在版本控制的文件会被存入缓存区。 show pop drop 丢弃栈顶 也可以加具体缓冲位置为参数stash@{1}距离栈顶 1 个距离的位置

git stash [comment] 将临时未 stage 的变更缓存在栈中

git stash list 查看栈列表

git stash show 显示栈顶变更概要

git stash show -p 显示栈顶变更具体 diff 内容

git stash apply 将栈顶内容应用到当前工作目录

git stash drop 丢弃缓存栈的栈顶

git stash pop 出栈一次,将变更覆盖到当前工作空间,相当于 apply + drop

git stash clear 清空整个缓存栈

git stash save stashname 保存当前变更代码为指定名称

git stash -p

以交互模式执行 stash 动作,选择指定的 hunk(代码块),退出该模式时完成选择执行 stash 动作。

  • y yes stage this hunk
  • n do not stage this hunk
  • q quit; do not stage this hunk nor any of the remaining ones
  • a stage this hunk and all later hunks in the file
  • d drop; do not stage this hunk nor any of the later hunks in the file
  • g select a hunk to go to
  • / search for a hunk matching the given regex
  • j jump; leave this hunk undecided, see next undecided hunk
  • J leave this hunk undecided, see next hunk
  • k leave this hunk undecided, see previous undecided hunk
  • K leave this hunk undecided, see previous hunk
  • s split the current hunk into smaller hunks
  • e manually edit the current hunk
  • ? print help

blame

blame 直译为”责备、归责”,用来查看某行代码是谁在哪个版本修改的。

git blame file_name 以 blame 模式查看某文件,在每行会显示这行最后修改的版本号、日期、时间、作者。 如果找到了某个位置变更的版本号,但是想继续追查,可以 checkout 出对应的版本号,然后再次执行 blame,就能查到更久的历史了。

  • -L startLine [endLine] 显示的行号范围
  • -C 显示该片段的原始 commit,可能是从其他文件 copy 过来的。

bisect

通过交互式的二分查找过程找到引入问题的版本,每次人工判断前 git 会自动 checkout 出一个版本,然后人工检查后输入结果(good/bad),然后自动继续 checkout 出另一个历史版本,直到找到第一次出错的版本。

git bisect start [new_hash [old_hash]] 开启一次二分查找过程,其中new_hashold_hash指定查找范围,如果不指定则查找到当前版本为止的历史 commit。

  • git biset good/bad 人工检查当前版本结果后输入,不断测试,直到最后输出hash_xxx is the first bad commit为止
  • git biset reset 退出本次查错过程,返回到 start 之前的版本

submodule

将项目的子模块信息绑定在当前项目中,提供一些方便的更新操作。

  • 多个 git 项目想依赖同一个子模块时有下面三种办法:
    1. 将子模块的文件夹放在当前项目外面,通过相对地址访问
    2. 将子模块放在当前项目里,设置.gitignore文件忽略子模块文件夹
    3. 用 submodule 将子模块所属 git 版本信息绑定到当前项目,以提供一些方便操作

对于比较复杂的项目,方案 3 比较合适,在提交本项目信息时也将当时子项目版本记录下来一并提交,以便之后别人维护项目时使用。方案 3 中的子项目是一个独立的项目,可能被多个父项目依赖,对父项目的存在无感知,它与父项目的关联关系仅限于在父项目的 config 等位置有版本相关绑定信息。

通常子模块是独立开发,然后在父项目中执行 update 后使用。但是也可以在父项目中直接修改子模块,然后 cd 到子模块文件夹中进行 git 操作提交。

git submodule add sub_git_url [path] 将 sub_git_url 对应的 git 库添加为当前库的 submodule,设置 path 可以指定子模块的目录位置,不设置则放在根目录使用默认的库名称。 执行完命令后,根目录会增加一个.gitmodules文件并且.git/config文件中也会增加对应的子模块信息,此操作本身可以作为一次 commit 提交。

  • -b <branch> 指定 submodule 默认 checkout 出的 branch,如果在创建时没有指定,可以用git submodule set-branch xxx设定
  • --name <name> 指定 submodule 的 name,默认与 submodule 项目名一致

git clone xxx_url --recurse-submodules 在 clone 时同时进行 submodule 的初始化,如果在 clone 时没有初始化,之后可以用git submodule init初始化

git submodule init 在 clone 时没有初始化,可以用这个初始化。

git submodule update 更新所有 submodule

  • --recursive 递归更新 submodule 的 submodule

git submodule deinit sub_module_name 卸载一个子模块,同时从多个文件中删除绑定信息

  • --force 即使 submodule 中有

git submodule foreach 'git checkout dev' 批量对所有 submodule 执行同一个命令

  • --recursive 递归 submodule

archive

类似 svn 的 export 功能,将指定的 branch 导出为压缩文件,不会包含工作空间的多余文件。 注意在根目录使用命令,否则会只导出子目录文件。

git archive --output "../test.zip" master 将 master 中的文件导出到 “../test.zip” 压缩包中,根据”xxx.zip”自动采用 zip 格式压缩

  • --format tar.gz 可以指定压缩算法,如果没有指定,则根据指定的 output 文件后缀自动选择压缩算法

示例场景

从 dev 创建 branch_my 进行开发,完毕后 merge 到 dev 然后提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建branch_my
git checkout -b branch_my dev	(如果已创建branch_my可以直接checkout)
// 进行编辑
git add ...
git commit ...
git stash (保存临时文件)
// 开始合并
git checkout dev
git pull --rebase
git checkout branch_my
git rebase dev
// 解决冲突
git checkout dev
git rebase branch_my
// 再次确保可以push
git pull --rebase
// push
git push
// 回到branch_my继续工作
git checkout branch_my
git stash pop
git checkout

从 dev 创建 my 进行开发,通过 MergeRequest 提交代码到 dev。

1
2
3
4
5
6
7
8
9
10
11
// 创建 my
git checkout -b my origin/dev
// 进行编辑
git add ...
git commit ...
// 合并
git fetch
git rebase origin/dev
// 解决了冲突后,可以push
git push origin my
// 之后可以提交MergeRequest

拷贝了不同 PC 上的 git 项目文件夹,缺少了 upsteam 信息

1
git branch --set-upstream-to=origin/branchName1 branchName2

通过上面语句可以补足上游信息,branchName 可以相同也可以不同。

在 github 上 fork 出一个库,开发后发出 MergeRequest

默认开发 branch 是 dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 首先在 github 上执行 fork 从 http://source.git 创建一个自己的仓库 http://my.git
// clone 到 project 文件夹,不要 checkout
git clone http://my.git project -n
cd project
git checkout dev origin/dev
// 添加远端库名
git remote add srcorigin http://source.git
// 创建自己的 branch
git checkout -b mybranch
// 在自己的 branch 做开发、commit、推动到自己的远程仓库
git push origin mybranch
// 获取远端数据,升级本地的 dev branch
git fetch srcorigin
git checkout dev
git rebase srcorigin/dev
git push
// rebase 自己的 branch
git checkout mybranch
git rebase origin/dev
git push
// 然后在 github 操作,发起一个从 origin/mybranch 到 srcorigin/dev 的 MergeRequest
  • 一般由项目的 Owner 负责 MR(Merge Request) 的 CR(Code Review) 和 Merge 动作, 然后发出 Pull Request,以便各个 Fork 出的 Repository 升级

git format-patch生成的一系列文件补丁打到项目、解决冲突

1
2
3
4
5
6
7
8
9
// 把 patch 文件放在项目外文件夹(如:"../patches"),避免妨碍 git 操作
git am ../patches/*.patch
// 遇到了冲突,停止在 `3.patch` 这个文件上
git apply --reject ../patches/3.patch
// 强行打补丁后,生成了 `3.rej` 文件,描述了冲突点
// 编辑代码,将所有冲突解决,将变更加入 stage
git add .
git am ../patches/*.patch --continue
// 遇到冲突持续解决,直到最后完成补丁

把一些常用的临时代码打成 patch 文件,需要时使用

1
2
3
4
5
6
7
8
9
// 先将临时代码提交到commit
git commit -a -m "temp code function"
// 生成补丁文件
git format-patch HEAD^
// 将补丁文件拷贝到别处备份
mv 0001-temp-code-function.patch ../
// 切换当前的branch、开发、commit
// 将临时代码应用到工作目录
git apply ../0001-temp-code-function.patch