Git进阶使用


为 Git 命令设置别名

全局别名

适用于你在系统上所有的 Git 仓库。

git config --global alias.<alias-name> <git-command>

其中,<alias-name> 是你为命令设置的别名,<git-command> 是你希望该别名代替的完整 Git 命令。

例如,要为 git status 设置别名为 s,你可以执行以下命令:

git config --global alias.s status

仓库别名

仅适用于当前的 Git 仓库。

git config alias.<alias-name> <git-command>

同样,<alias-name> 是你为命令设置的别名,<git-command> 是你希望该别名代替的完整 Git 命令。

例如,要为 git commit -m 设置别名为 cm,你可以在项目的 Git 仓库目录下执行以下命令:

git config alias.cm 'commit -m'

使用

git s  # 相当于 git status
git cm "commit message"  # 相当于 git commit -m "commit message"

如何查看 Git 的 Log 中的线

知乎原文链接

查看带有线的提交记录

命令行查看 Git 中的线:git log --oneline --graph --decorate (新版本的 git log 里,–decorate 已经是默认参数了,可以不明确指定出来。)

设置别名:git config --global alias.lg "log --oneline --graph --decorate",这样就可以执行 git lg 来调用这个命令了。

#查看dev和master分支(可以不加分支名,查看全部的提交线)
git log --oneline --graph --decorate dev master
#在我的例子里显示这个结果
*   b4ad2d0 (HEAD -> master) merge dev
|\
| * 68b47aa (dev) hello world
* | 63499fb commit without changes
|/
* 0c9d5ad root commit

父提交

一个 commit 可能有 0 个、1 个、2 个或者多个 parent 。

有 2 个 parent 的 commit 是一个 merge commit,它是在 2 个或者多个分支发生 true merge 时生成的,靠它将另一个或多个分支的历史和修改合并到了当前分支中间。

git log -1 --pretty=raw b4ad2d0
#在我的例子里显示这样的结果
commit b4ad2d07f31956d907ed25849f8b7783bfb45817
tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
parent 63499fb5e91a662697e7ddb7b81309fcf1367cd5
parent 68b47aadcb81af36476615f966672ec89cad1eab
author ElpieKay <elpie@kay.com> 1531145291 +0800
committer ElpieKay <elpie@kay.com> 1531145291 +0800

    merge dev

可以看到这个 commit 有 2 个 parent,按照顺序,63499 是 first parent,68b47 是 second parent。git 中允许将 1 个或者多个分支 同时 合并到另一个分支上,这里是最常见的合并一个分支到另一个分支上。如果同时合并多个分支到一个分支上,称为 octopus merge。这样生成的 merge commit 就有 2 个以上的 parent。

parent 的第一第二这样的顺序是有实际意义的。知道一个 commit,可以通过 commit^n(n 是非负整数)来引用它第 n 个 parent。n 取 0 的时候,就是它本身。此外,如果一个 commit 有第一和第二 parent,就可以知道生成这个 merge commit 的时候,是把当时第二 parent 所在分支 merge 到了当时第一 parent 所在分支上,发生了 true merge。

HEAD~ 与 HEAD^

博客链接

git reset --hard HEAD~ 中的 HEAD~ 解释

HEAD~ 和 HEAD^ 等同于 HEAD~1 和 HEAD^1 ,这几个命令都是指上一个父提交。后面的数字省略则默认为 1。

  1. HEAD~n 表示 回退 n 步,数字 n 表示后退的步数。
  2. HEAD^n 表示 后退 1 步,数字 n 表示是第 n 个父提交。

所以 HEAD~ 和 HEAD^ 效果是一样的,只有后面的数字大于 1 的时候才会不同。比如当有 merge 的时候,merge commit 就会有两个父提交,这时候可以通过 HEAD^1 或者 HEAD^2 来选择哪一个父提交。

~ 和 ^ 混合使用

比如需要从当前到第一个父提交上,在退一步到第一个父提交上,然后退一步到第二个父提交上,最后退一步到第一个父提交上。

可以这样实现 HEAD^1^1^2^1 ,也可以省略掉 1: HEAD^^^2^,也可以这样写:HEAD~2^2^,

分离 HEAD 和根据某个 commit 创建分支

高级篇中可进行实践

分离 HEAD(不推荐使用)

git checkout <commitID>

使用 checkout 命令将 HEAD 设置到指定 commitID 上,在这上面做操作不会影响原来的分支,通过 git checkout branch-name 即可回到分支最新提交处。

根据某个 commit 创建分支

git branch -f <new-branch-name> <commitID或HEAD~n>

Merge 出现 Merge branch ‘xxx’ of 的 commit 信息

多人协作开发项目,在上传代码时通常会先 pull 一下远程代码,使本地与远程同步更新,但是如果远程此时与自己代码存在冲突,在解决冲突后提交有时会出现“Merge branch ‘master’ of …”这条信息。这是因为 pull 其本质是 fetch+Merge 的结合。通常会分为以下两种情况:

  1. 如果远程分支超前于本地分支,并且本地也没有 commit 操作,此时 pull 会采用’fast-forward’模式,该模式不会产生合并节点,也即不产生 “Merge branch ‘master’ of …” 信息。
  2. 如果本地有 commit 提交,此时若存在冲突,pull 拉取代码时远程和本地会出现分叉,会进行分支合并,就会产生 “Merge branch ‘master’ of …” 信息。

如果不希望出现多余的信息可以使用 git pull --rebase。

merge 参数

git merge --ff/--no-ff/--ff-only

默认情况下你直接使用 git merge 命令,没有附加任何选项命令的话,那么应该是交给 git 来判断使用哪种 merge 模式,实际上 git 默认执行的指令是 git merge -ff 指令(默认值)。

--no-ff 意为强行关掉 fast-forward,所以在使用这种方式后,分支合并后会生成一个新的 commit。

--ff-only 只会按照 Fast-forward 模式进行合并,如果不符合条件(并非当前分支的直接后代),则会拒绝合并请求并且推出

resbase -i 命令

介绍

git rebase 命令有标准和交互两种模式,之前的示例我们用的都是默认的标准模式,在命令后添加 -i 或 --interactive 选项即可使用交互模式。

两种模式的区别

rebase 是「在另一个基端之上重新应用提交」,而在重新应用的过程中,这些提交会被重新创建,自然也可以进行修改。在 rebase 的标准模式下,当前工作分支的提交会被直接应用到传入分支的顶端;而在交互模式下,则允许我们在重新应用之前通过编辑器以及特定的命令规则对这些提交进行合并、重新排序及删除等重写操作。

两者最常见的使用场景也因此有所不同:

  1. 标准模式常用于在当前分支中集成来自其他分支的最新修改。
  2. 交互模式常用于对当前分支的提交历史进行编辑,如将多个小提交合并成大的提交。

用法

git rebase -i  [startpoint]  [endpoint]

其中-i 的意思是–interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint] 则指定了一个编辑区间,如果不指定 [endpoint],则该区间的终点默认是当前分支 HEAD 所指向的 commit(注:该区间指定的是一个前开后闭的区间)。

git rebase -i HEAD~3 // 3=> 代表的是最近三次(HEAD~3指的是倒数第四次提交,但是不包括,所以代表最近3次)

resbase -i 操作示例

常用指令说明:

pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d)

恢复被删除且已经被提交的文件

使用 git checkout 命令来恢复被删除的文件。

git checkout <commit-hash> -- <file-name>

示例:

git checkout abc123 -- deleted_file.txt

使用 git add 命令将恢复的文件添加到暂存区,然后提交。

worktree 命令

worktree 使用参考

git worktree 介绍

git worktree 是 Git 命令,用于管理多分支工作区。

使用场景:

  • 同时维护不同分支,隔离分支依赖差异:从原有项目开辟一个分支作为另一个新项目,当两个项目依赖差距越来越大时,每次切换分支后都需要重新安装依赖。通过 git worktree 可以隔离两个分支的依赖,并且两个分支可以互相 merge、cherry-pick。
  • 多个分支同步开发:允许在同一存储库中的不同分支上同时进行工作,而不需要频繁切换分支,这对于需要同时处理多个功能或修复多个 bug 的情况非常有用。
  • 进行实验性更改:在不影响主工作目录的情况下,尝试进行实验性的更改或调试。
  • 同时进行长期和短期任务:有助于同时处理长期开发任务和短期修复任务,而无需相互影响或混淆。

以下是 git worktree 的子命令:

  • add:在当前分支下创建一个新工作区。
  • remove:删除一个已添加的工作区。
  • list:显示所有已添加的工作区。
  • lock:锁定工作区以防止在合并或其他操作时被意外删除。
  • unlock:手动取消工作区的锁定。

git worktree add <path> [(-b | -B) <new-branch>]

在当前分支下创建一个新工作区,效果类似于 git clone ,但新旧工作区属于同一个仓库,可以正常 add、commit、merge 等操作。

  • <path>:要创建的新工作区的路径,一般建立在当前目录的上一层,如 ../newpath。
  • <new-branch>:要在哪个分支上创建工作区。如果未指定 <new-branch>,则表示基于当前分支 HEAD 创建新分支 <path>。-b 表示基于当前分支 HEAD 创建新分支 <new-branch>,-B 表示强制创建。

注意:

  • 原始仓库默认是一个工作区,关联当前 checkout 的分支!
  • 只能创建未被关联的分支,或者通过 -b 指令创建新分支!
  • 可以理解为一个工作区一个 HEAD,但是不能多个 HEAD 指向同一个分支。

打补丁 patch

Git 提供了两种补丁方案,一是用 git diff 生成的 UNIX 标准补丁 .diff 文件,二是 git format-patch 生成的 Git 专用 .patch 文件。

.diff 文件只是记录文件改变的内容,不带有 commit 记录信息, 多个 commit 可以合并成一个 diff 文件。

.patch 文件带有记录文件改变的内容,也带有 commit 记录信息, 每个 commit 对应一个 patch 文件。

在 Git 下,我们可以使用.diff 文件也可以使用.patch 文件来打补丁,主要应用场景有:Code Review、代码迁移等。

优先使用 git format-patch 生成 .patch 文件,带有提交信息。

diff/apply 方案

此方案使用 diff 命令生成 patch 文件,后使用 apply 命令应用 patch 到分支,从而实现修改复刻。其大致流程如下:

# 生成补丁
git diff > commit.patch
# 检查补丁
git apply --check commit.patch
# 应用补丁
git apply commit.patch

diff 生成 patch

  1. git diff

    没有指定任何版本,那默认就是对 lastCommit 和 working copy(就是工作区) 之间作比较。

    这里就会出现两种情况:如果当前的 working copy 已经 commit 过了,那么 lastCommit 就是目前 working copy 自身,所以 diff 不会输出任何内容;如果当前的 working copy 未 commit,那么 diff 就会输出本次修改的内容。

  2. git diff commitId

    指定了一个 commitId,那就是对 commitId 和 working copy 之间作比较。同样的,如果你的 working copy 已经 commit 过了,那么这个命令会等价于 git diff commitId lastCommitId。

  3. git diff commitId1 commitId2

    指定了两个 commitId(注意把时间早的 commitId 放在前面),这种情况就是对 commitId1 和 commitId2 之间作比较。

本地修改未 commit

希望把修改的内容生成 patch,可以如下操作:

git diff > commit.patch

本地修改已 commit

希望把最近一次的修改生成 patch:

# 注意:commitId为倒数第二个提交ID
git diff commitId > commit.patch

apply 应用 patch

生成 patch 文件后,我们切换到希望应用 patch 的分支,然后按下面的步骤操作:

# 检查patch是否可以应用
git apply --check commit.patch
# 打单个补丁
git apply commit.patch
# 打多个补丁
git apply ../patch/*.patch

打完补丁后再 add/commit 进行提交。

format-patch/am 方案

这个方案是使用 format-patch 命令生成一系列 patch 文件,然后使用 am 命令批量应用 patch。

# 生成patch
git format-patch -1
# 检查patch
git apply --check 0001-made-some-change.patch
# 应用patch
git am 0001-made-some-change.patch

format-patch 生成 patch

某次提交以后的所有 patch

git format-patch commitId

从根到指定提交的所有 patch

git format-patch --root commitId

某两次提交之间的所有 patch

注意 commitId1 需要比 commitId2 先提交。patch 中不包含 commitId1

git format-patch commitId1..commitId2

某次提交(含)之前的几次提交

# -n指patch数
git format-patch -n commitId 

# 比如单次提交,生成commitId这次提交的patch
git format-patch -1 commitId

# 生成commitId前一次到commitId这两次修改的patch
git format-patch -2 commitId

am 应用 patch

git am 可以一次合并一个文件,或者批量合并一个目录下所有的 patch。

打单个补丁

# 先检查patch文件:
git apply --stat newpatch.patch
# 检查能否应用成功:
git apply --check newpatch.patch
# 应用patch
git am newpatch.patch

批量应用补丁

git am patch/*.patch

为什么有了 diff 还要有 format-patch 呢?

主要还是使用场景的不同:

  • diff 仅保留了文件重 A 状态变成 B 状态的差别,而不会保留 commit 记录消息等信息,而 format-patch 则是完整保留了每次提交的完成信息,这也解释了 diff 可以多个 commit 生成单个 patch,而 format-patch 则是每个 commit 都生成一个 patch 文件。
  • 两者是成对使用的,即 git apply 使用的补丁必须是 git diff 生成的;git am 的补丁必须是 git format-patch 生成的。当然 补丁检查都是用 git apply –check/–stat。

文章作者: 草莓多多
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 草莓多多 !
  目录