更直观地学习 Git 命令


theme: condensed-night-purple

前言

本文参考于 Learn Git Branching 这个有趣的 Git 学习网站。

在该网站,可以使用 show command 命令展示所有可用命令。

你也可以直接访问网站的sandbox,自由发挥。

本地篇

基础篇

git commit

git commit将暂存区的修改提交到本地版本库并创建一个新的提交,新提交会指向前一个提交。

bash 复制代码
git commit -m "C2"

main分支是默认主分支。*表示当前分支所在,也是HEAD指针指向。我们的提交记录会提交到当前分支。

直接运行git commit会打开Git默认编辑器。

git branch

git branch在此前工作的基础上创建新的分支来指向当前的提交,以此来进行新的工作。

创建一个名为newImage的分支:

bash 复制代码
git branch newImage

*号依然位于main,说明当前分支仍然是main。

使用以下命令切换到新分支上:

bash 复制代码
git checkout newImage

Git 2.23 版本引入了 git switch <branch-name> 命令来取代 git checkout命令众多功能中的切换分支功能。

最后,git branch <branch-name>和git checkout <branch-name>可以合并为一条命令:

bash 复制代码
git checkout -b <branch-name>

git merge

在新建分支开发完新功能或修复完 Bug 后,我们可以将其合并回主分支。

合并bugFix分支到main分支:

bash 复制代码
git merge bugFix


非快进合并

可以看到,合并分支时创建了新提交,新提交的parent提交是两个分支合并之前的最新提交。

再把main分支合并到bugFix:

bash 复制代码
git switch bugFix
git merge main


快进合并(Fast Forward)

git rebase

git rebase是另一种分支合并的方式,称为变基,具体是把一系列的提交记录复制到另一个地方。

使用git rebase可以使得提交历史更加线性。

bash 复制代码
git rebase main

git rebase main将当前分支(*所在)bugFix从与main分支的公共祖先提交部分开始的提交记录复制 到以main分支的最新提交为起始的地方,但复制不是真的复制,是解决冲突后的复制,如图复制后的C3'提交已经与原提交C3不同了。

git rebase main中的main就是基分支 ,当前分支bugFix就是变基分支。

我们可以写成,git rebase main bugFix来主动指定基分支和变基分支。

高级篇:在提交树上移动

HEAD 简介与 HEAD 分离

HEAD 是一个指针,通常指向当前分支名,偶尔也会指向当前提交。

bash 复制代码
git checkout C1 # 切换到 C1 提交
git checkout main # 切换到 main
git commit -m "C2" # HEAD 指向的 main 指向 C2
git checkout C2 # 切换到 C2 提交

像git checkout C1这样将 HEAD 指针指向提交C1的行为称为 HEAD 分离 (Detached HEAD)。分离前,HEAD 指向的是分支引用 main。

git checkout C1中的C1其实是提交的HASH值。

查看提交记录的哈希值

bash 复制代码
git log

HASH 值很长,但我们只需要其前面几位可以唯一标识提交的即可,例如63dbb453fbae3d151622f4e91d3aadf90069552d我们取63dbb。

查看 HEAD 指向

bash 复制代码
cat .git/HEAD

如果 HEAD 指向引用而非提交,也可以像下面查看 HEAD 指向:

bash 复制代码
git symbolic-ref HEAD

相对引用

前文使用 HASH 值来指定提交记录,这也叫做直接引用 ,难免不方便,Git 提供了相对引用的方式来指定提交:

  • <branch-name>^:branch-name指向的提交的上一个提交。
  • <branch-name>^^:branch-name指向的提交的前面第二个提交。
  • <branch-name>~<num>:branch-name指向的提交前面第num个提交。
bash 复制代码
git checkout main^

强制修改分支位置

除了切换分支,相对引用用的最多的地方是移动分支。

bash 复制代码
git branch -f main HEAD~3

如果要移动的分支不存在会自动创建。

撤销变更

git reset

git reset回退到直接引用或相对引用指向的提交记录,之后的提交记录就撤销了。

bash 复制代码
git reset HEAD^

C2提交被reset后,就不存在于本地版本库中,但是其变更还在,只不过处于未暂存状态。

git reset只对本地分支有效,无法回退远程分支。

进行git reset后,如果进行推送,Git 会提示远程仓库较新需要git pull,你的代码被远程仓库覆盖。如果想要回退生效,则需要追加-f进行强制推送。

git revert

reset 英文意思有"复位"。

revert 英文意思有"恢复"。

git revert会创建新提交来撤销更改,它与待撤销提交的前面某个提交内容是相同的。

bash 复制代码
git revert HEAD

移动提交记录

git cherry-pick

git chery-pick用于把一些提交复制到当前提交(HEAD)的下面。

bash 复制代码
git cherry-pick C2 C4

交互式 rebase

git rebase 后追加 --interactive 或 -i 选项会打开一个窗口(你配置的编辑器)显示将要被复制到目标分支的提交的哈希值和提交说明。

一个打开的示例窗口如下:

txt 复制代码
pick 63dbb45 2

# Rebase 88e2af2..63dbb45 onto 88e2af2 (1 command)
#
# 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 [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# 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
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
#                       to this position in the new commits. The <ref> is
#                       updated at the end of the rebase
#
# 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.
#

使用git rebase经常会遇到要解决冲突的情况,解决冲突后,执行git add和git rebase --continue。






杂项

只复制一个提交

有时候,我们开发完一个功能,需要与主分支进行合并时,会希望只合并我想要的那个提交。

我们知道,使用git merge的合并有快进合并和非快进合并两种。但是这两种合并会将我们不需要的提交也包含在主分支中。

这时候,我们可以使用git cherry-pick或git rebase -i来完成。

如上图,我想要将修复问题后的提交bugFix合并到main。

如果使用git merge:

bash 复制代码
git checkout main
git merge bugFix

这使得主分支 main 中就包含了 C2、C3 这些提交。

使用git cherry-pick:

bash 复制代码
git checkout main
git cherry-pick C4
git branch -f bugFix main

修改前面的提交但不在主分支创建新提交 1

这样做就唯一的问题就是要进行两次排序,而这有可能造成由 rebase 而导致的冲突。

修改前面的提交但不在主分支创建新提交 2

bash 复制代码
git checkout main
git cherry-pick C2
git commit --amend
git cherry-pick C3

git tag

git tag为提交记录打上标签,它可以像分支一样引用,但不会像分支一样移动。

bash 复制代码
git tag V1
bash 复制代码
git tag V2 C0

git describe

git describe查找离指定引用最近的标签。

git describe <ref>输出格式:<tag>_<numCommits>_g<hash>。

高级话题

多次 rebase

bash 复制代码
git checkout another
git rebase side
git rebase bugFix
git rebase main
git branch -f main another

相对引用^中选择 parent 提交记录

相对引用^可以像~一样在后面加个数字,该数字是选择第几个 parent 提交记录。

bash 复制代码
git checkout main^2

^和~的链式操作;

bash 复制代码
git checkout HEAD~^2~2

纠缠不清的分支

bash 复制代码
git checkout one
git cherrt-pick C4 C3 C2
git checkout two
git cherrt-pick C5 C4 C3 C2
git branch -f three C2

远程篇

Git 远程仓库基本操作

git clone

bash 复制代码
git clone <远程仓库URL>

远程仓库实际上就是你的本地仓库在另一台电脑上的拷贝。

git clone从远程仓库克隆一个副本到本地。

远程分支

上一节中的克隆产生的o/main分支就是远程分支,o是远程仓库名。你使用git clone克隆完一个仓库时,已经将远程仓库名设置为origin了。

查看远程仓库信息:

bash 复制代码
git remote -v

修改远程仓库名:

bash 复制代码
git remote rename <远程仓库名>

远程分支反映了远程仓库的状态。

切换到远程分支会自动切换到 HEAD 分离状态。

bash 复制代码
git checkout o/main

git fetch

bash 复制代码
git fetch <远程仓库URL>

git fetch从远程仓库下载缺失的提交记录,并更新远程分支指针。实际上就是更新远程分支。

但是git fetch不会改变你的本地分支状态,也就是不会改变你的本地仓库文件。

git fecth

git pull

使用git fetch只更新了远程分支,并未更新本地分支,还需要我们进行进一步合并,使用下面方法之一:

  • git cherry-pick origin/main
  • git merge origin/main
  • git rebase origin/main

不过,Git 的 git pull 命令将拉取和合并操作结合在了一起。

git pull是git fetch和git merge的结合。

git push

git push 不带任何参数时的行为与 Git 的一个名为 push.default 的配置有关。

查看 push.default 值。

bash 复制代码
git config --get push.default

push.default 通常具有以下几种值:

  • simple:这是 Git 2.0 及以后版本的默认值。它会将当前分支的 push 操作限制为其上游(通常是 origin 远程仓库)的同名分支。
  • current:这也是一个常见的值,它将 push 操作限制为当前分支。
  • nothing:这个选项会禁用默认的 push 行为,您需要明确指定要 push 的分支和远程仓库。
  • matching:在 Git 1.x 版本中的默认行为,它会将本地的所有分支与远程仓库的同名分支进行匹配,然后将它们都 push 到远程仓库。

修改 push.default:

bash 复制代码
git config push.default <new_value>

git push 后还更新了 o/main 的位置。

偏离的提交历史

当你使用 git push 推送代码时,会遇到无法推送,要先与远程仓库合并的提示。这是由于远程仓库已经被你的同事修改了,你调用 API 可能已经不存在或改名了,也就是发生了冲突。

如何解决冲突?

① 使用 git rebase

bash 复制代码
git fetch
git rebase o/main
git push o main

② 使用 git merge

bash 复制代码
git fetch
git merge o/main
git push o main

使用 git merge 会多一个合并提交。

当然 git fetch 和 git merge 可以合并为 git pull。

③ 使用 git pull --rebase,即 git fetch 与 git rebase 的结合

bash 复制代码
git pull --rebase
git push

锁定的 main

Git 锁定 main 分支是防止主分支遭到未经审核的修改。

如果你直接提交到 main,然后 push,不仅会被拒绝,还会无法再次推送。

你需要 reset main 分支到远程分支处,与远程服务器保持一致。然后新建一个 feature 分支,推送 feature 分支到服务器。

Git 远程仓库高级操作

推送主分支

想想如何完成。

使用 git merge 或 git rebase 都行,仁者见仁智者见智。

远程追踪

main 分支默认指定为跟踪远程分支 origin/main ,跟踪 地意思是指定了 origin/main 为 push 的目的地和 fetch 后合并的目标。

如何自己设置分支跟踪远程分支?

方式一

bash 复制代码
git checkout -b <local branch> <remote branch>

比如 git checkout -b foo origin/main。

方式二:

bash 复制代码
git branch -u <remote branch> <local branch>

例如 git branch -u origin/main foo,如果当前就在 foo 分支,还可以省略 foo。

git push 的参数

实际 git push 的语法是:

bash 复制代码
git push <remote> <place>

意思是将 remote 的 place 分支没有的而本地 place 分支有的提交记录添加到 remote 的 place 分支。不仅会更新远程的 place 分支,也会更新本地的 remote/place 分支。

而 git push 没有参数时,会根据当前分支和它所追踪的远程分支来自动设置参数。

git push 的参数 2

将本地 source 分支推送到远程仓库的 destination 分支:

bash 复制代码
git push origin <source>:<destination>

准确地说,source 不一定是分支,也可能是标签、提交的 HASH 部分值和相对引用等等 Git 可以识别的位置。

如果目的分支 destination 不存在怎么办?

Git 会帮你自动创建的。

git fetch 的参数

bash 复制代码
git fetch origin foo

拉取远程仓库 origin 的远程分支 foo 的新提交,并放到本地的远程追踪分支 origin/foo 上,而不会影响 foo 分支。

也可以使用 <source>:<destination> 的参数格式:

bash 复制代码
git fetch origin foo~1:bar

上面命令的作用是将远程 foo 分支的上一个提交拉取到本地的 bar 分支上。

如果 bar 分支不存在就新建,这个和 git push 的参数行为是一致的。

没有指定参数的 git fetch 则会拉取所有提交记录。

source 参数为空的 git push 和 git fetch

1)git push orgin :foo

传空值 source 删除了远程的 foo 分支,连带着删除了本地的 origin/foo 分支。

2)git fetch origin :foo 则会在本地新创建一个 foo 分支。

git pull 的参数

git pull 是 git fetch 和 git merge 的结合体。

git pull origin foo 相当于

bash 复制代码
git fetch origin foo
git merge origin/foo

git pull origin foo^:bar 相当于

bash 复制代码
git fetch origin foo^:bar
git merge bar
相关推荐
海绵宝宝转agent13 小时前
learn-claude-code第1-5章开源学习笔记分享
人工智能·笔记·python·学习
每天都要写算法(努力版)13 小时前
【行业前沿报告】DAgger:让智能体在自己走到的状态上学习
人工智能·学习·机器学习
sunshine22 girl17 小时前
Java学习六 Java常见API-2--String-3-字符串的截取替换和其他方法
java·开发语言·学习
sunshine22 girl18 小时前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
Answer1st18 小时前
【嵌入式学习】嵌入式原理知识-DMA(九)
学习
传奇开心果编程19 小时前
【ArkUI进阶练中学】第11课:安全与合规进阶
学习·ui·华为·harmonyos
Mr_Macallon20 小时前
C++回顾(基础)(01)
c语言·开发语言·c++·学习
sunoo-22921 小时前
【ARM嵌入式学习笔记第十天】i.MX6ULL eLCDIF控制器驱动RGB LCD全解析(硬件原理+寄存器配置+裸机代码)
arm开发·笔记·学习
kaiyou20261 天前
用户研究岗秋招,统计分析能力怎么学习和证明?
数据库·python·学习
知识分享小能手1 天前
C++ 学习教程,从入门到精通,string类和标准模板库 — 完整知识点详解(16)
开发语言·c++·学习