先在本地新建一个仓库,创建一个文件,并提交到版本库:
# 初始化本地仓库
PS D:\Agit\gitbranch> git init
Initialized empty Git repository in D:/Agit/gitbranch/.git/
# 创建一个文件
PS D:\Agit\gitbranch> touch hello-branch.txt
# 提交到版本库
PS D:\Agit\gitbranch> git add hello-branch.txt
PS D:\Agit\gitbranch> git commit -m 'one'
[master (root-commit) 8b265e7] one
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 hello-branch.txt
上述流程一套走完,很自然的就知道现在有3个Git对象,一个文件blob,一个暂存区tree,一个提交commit,而且暂存区文件和commit文件一致的情况下,commit 管理的 tree 和暂存区的 tree 是同一个Git对象:
# 查看 commit 的对象ID
PS D:\Agit\gitbranch> git show head
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345 (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
# 查看 commit 管理的 tree
PS D:\Agit\gitbranch> git cat-file -p 8b265
tree 683bee3051ca63c12fc2162e056d705fc730abed
# 查看 暂存区的 tree
PS D:\Agit\gitbranch> git write-tree
683bee3051ca63c12fc2162e056d705fc730abed
# 查看 tree 管理的 blob
PS D:\Agit\gitbranch> git ls-tree 683bee3
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 hello-branch.txt
以上是上一篇学到的内容,这一篇我们就看看 Git 每次 commit 会组成怎么样的链条,分支是咋回事,版本回退又是什么。
这里推荐一个学习分支的网站:学习Git分支
HEAD 与 master 分支
在 Git 中,一个分支可以被理解为仓库中的一个独立的开发线。它是指向某次提交(commit)的指针,包含了从该提交开始的所有提交历史。通过使用分支,开发者可以并行地进行多个不同的工作,而不会相互干扰。
通过 status 命令可以看到当前工作分支是在 master 上:
PS D:\Agit\gitbranch> git status
On branch master
nothing to commit, working tree clean
我们已经知道暂存区在GIt中对应 .git/index 文件,commit 也对应某一个 Git 对象文件,那 master 分支对应什么文件呢?指向 master 分支的“指针” HEAD 又是对应什么文件呢?
可以在 .git 文件夹下找到 HEAD 文件,它就是那个所谓的指针吗:
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/master
发现 HEAD 文件中只有一行,引用(ref)了一个位置 refs/heads/master:
PS D:\Agit\gitbranch> cat .git/refs/heads/master
8b265e73e1c8a26dcde4f1a647954fd888a33345
引用的 master 也是一个文件,里面存放的就是本次 commit 的对象ID。
至此清晰明了,HEAD 就是对分支的引用,而分支则是对 commit 对象的引用。
.git/refs/heads 文件夹下的文件都是分支,如 master 文件就是 master 分支。对于分支,既可以使用长格式的表示,如:refs/heads/master,也可以去掉前面的两级目录用 master 来表示。
提交链
目前本地仓库只有一个 master 分支上,而且分支上只有一个 commit,可以将每次 commit 提交看作是分支上的一个节点,commit 对象与 commit 对象会通过 parent 指针形成一个单向链表。
再次创建一个文件,并提交到版本库:
PS D:\Agit\gitbranch> touch hello-branch-2.txt
PS D:\Agit\gitbranch> git add hello-branch-2.txt
PS D:\Agit\gitbranch> git commit -m 'two'
[master 204111f] two
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 hello-branch-2.txt
看一下 HEAD,发现 HEAD 还是引用 master 分支,但 master 分支保存的 commit 对象ID变了,变成最新一次提交了。
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/master
PS D:\Agit\gitbranch> cat .git/refs/heads/master
204111f0413f906ba770097e13c69d3cca08d65f
这说明,HEAD 指针是指向分支最新一次提交的。
为了演示,再创建一个文件,并提交到版本库:
PS D:\Agit\gitbranch> touch hello-branch-3.txt
PS D:\Agit\gitbranch> git add hello-branch-3.txt
PS D:\Agit\gitbranch> git commit -m 'three'
[master 61c2f69] three
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 hello-branch-3.txt
可以发现 master 分支指向又变了,当然 HEAD 指向随着 master 分支的变化而变化:
PS D:\Agit\gitbranch> cat .git/refs/heads/master
61c2f69b18cfefdd0953a372ec6e3576cf190b3e
PS D:\Agit\gitbranch> git show HEAD
commit 61c2f69b18cfefdd0953a372ec6e3576cf190b3e (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:31:25 2024 +0800
three
diff --git a/hello-branch-3.txt b/hello-branch-3.txt
new file mode 100644
index 0000000..e69de29
可以是 cat-file 命令查看提交对象的内容,包括本次提交的 parent 指向,进而追踪到第一次提交。
PS D:\Agit\gitbranch> git cat-file -p HEAD
tree 8aad084f1628fd135470196bd327fc08f8b6b198
parent 204111f0413f906ba770097e13c69d3cca08d65f
author Cango King <806784622@qq.com> 1715070685 +0800
committer Cango King <806784622@qq.com> 1715070685 +0800
three
PS D:\Agit\gitbranch> git cat-file -p 2041
tree 93c92c2d5c86738c170deec69aa40bea04b7c8ba
parent 8b265e73e1c8a26dcde4f1a647954fd888a33345
author Cango King <806784622@qq.com> 1715070377 +0800
committer Cango King <806784622@qq.com> 1715070377 +0800
two
PS D:\Agit\gitbranch> git cat-file -p 8b26
tree 683bee3051ca63c12fc2162e056d705fc730abed
author Cango King <806784622@qq.com> 1715050797 +0800
committer Cango King <806784622@qq.com> 1715050797 +0800
one
一个个找太麻烦了,还有啥命令可以直观的看到提交记录吗?
git log 提交日志
git log 是用来查看历史提交记录的,基本语法如下:
git log [<options>] [<revision range>] [--] [<path>...]
其中 <options> 为选项;<revision range>为可选参数,用于限制显示的提交范围; <path> 用于限制只显示与指定路径相关的提交。
比如:
⨳ git log:显示包括 HEAD 位置及其之前的提交:
⨳ git log --grep="keyword":显示只包含指定关键词的提交历史:
⨳ git log -n 5:显示最近5条提交记录:
⨳ git log -- <path/to/file>:显示特定路径下的提交记录:
⨳ git log --author="authorname" --since="YYYY-MM-DD" --until="YYYY-MM-DD":显示特定作者在特定时间范围内的提交记录:
如下查询本地仓库的提交记录:
PS D:\Agit\gitbranch> git log
commit 61c2f69b18cfefdd0953a372ec6e3576cf190b3e (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:31:25 2024 +0800
three
commit 204111f0413f906ba770097e13c69d3cca08d65f
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
one
--oneline 选项可以将提交记录压缩成一行:
PS D:\Agit\gitbranch> git log --oneline
61c2f69 (HEAD -> master) three
204111f two
8b265e7 one
还有几个常用的选项:
⨳ --graph :以图表形式查看分支的提交记录,这对查看多分支提交很好用。
⨳ -p, --patch:显示每个提交的具体更改内容(补丁)。
git diff 版本比较
上一节,已经讲过 git diff 比较版本库、暂存区和工作区中文件的不同了,这次使用 git diff 比较两次提交的不同:
git diff 提交对象ID 提交对象ID
比如,比较 HEAD(第三次提交) 和 第二次提交:
PS D:\Agit\gitbranch> git diff HEAD 20411
diff --git a/hello-branch-3.txt b/hello-branch-3.txt
deleted file mode 100644
index e69de29..0000000
竟然显示 hello-branch-3.txt 被删除了,这是因为 diff 显示的是 HEAD 到提交 20411 变化。将 HEAD 和 20411 调换一下位置即可:
PS D:\Agit\gitbranch> git diff 20411 HEAD
diff --git a/hello-branch-3.txt b/hello-branch-3.txt
new file mode 100644
index 0000000..e69de29
提交对象ID太不易读了,也容易写错,于是 Git 提供了对象ID的元字符:HEAD~数字:
⨳ HEAD~1:可以省略数字 1 ,HEAD~ ,即 HEAD 指向提交的父提交。
⨳ HEAD~5 : 即 HEAD 指向提交的父提交、的父提交、的父提交、的父提交、的父提交,也可以这样写:HEAD~~~~~。
当然 HEAD 可以换成任意提交ID,~数字的语义不变,如 61c2f6~2。
PS D:\Agit\gitbranch> git diff HEAD~ HEAD
diff --git a/hello-branch-3.txt b/hello-branch-3.txt
new file mode 100644
index 0000000..e69de29
git reset 重置
上一节,已经讲了 git reset 命令可以将当前分支的 HEAD 指针移动到指定的提交,不同选项会影响不同的区域:
⨳ --soft:只移动 HEAD 指针,保留暂存区(index)和工作区中的文件不变;
⨳ --mixed(默认):移动 HEAD 指针外,重置暂存区域,但不改变工作区中的文件;
⨳ --hard:移动 HEAD 指针外,重置暂存区域,重置工作区;
reset 重置一般用于提交撤销,比如第三次提交(HEAD)有点问题,那就可以回退到父版本(第二次提交)再重新提交,一般回退的时候仅重置暂存区即可,不会改变工作区,所以 --mixed 选项是默认的。
PS D:\Agit\gitbranch> git reset HEAD~
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/master
PS D:\Agit\gitbranch> cat .git/refs/heads/master
204111f0413f906ba770097e13c69d3cca08d65f
PS D:\Agit\gitbranch> git show HEAD
commit 204111f0413f906ba770097e13c69d3cca08d65f (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
PS D:\Agit\gitbranch> git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello-branch-3.txt
nothing added to commit but untracked files present (use "git add" to track)
reset 命令通过移动 HEAD 指针,来回退版本, HEAD 指针变了,提交记录也变了:
PS D:\Agit\gitbranch> git log
commit 204111f0413f906ba770097e13c69d3cca08d65f (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
one
毕竟 git log 只显示包括 HEAD 位置及其之前的提交,那如果还想回到 commit three 怎么办,怎么找到它的提交ID。当然可以去分支日志去看:
但 GIT 提供了一个分析该日志的命令:reflog(Reference log)。
git reflog 引用记录
git reflog 命令用于查看 Git 仓库的引用日志(reference log)。引用日志记录了引用(例如分支和标签)的更改历史,包括引用的创建、删除、重命名以及移动等操作。
PS D:\Agit\gitbranch> cat .git\logs\refs\heads\master
0000000000000000000000000000000000000000 8b265e73e1c8a26dcde4f1a647954fd888a33345 Cango King <806784622@qq.com> 1715050797 +0800 commit (initial): one
8b265e73e1c8a26dcde4f1a647954fd888a33345 204111f0413f906ba770097e13c69d3cca08d65f Cango King <806784622@qq.com> 1715070377 +0800 commit: two
204111f0413f906ba770097e13c69d3cca08d65f 61c2f69b18cfefdd0953a372ec6e3576cf190b3e Cango King <806784622@qq.com> 1715070685 +0800 commit: three
61c2f69b18cfefdd0953a372ec6e3576cf190b3e 204111f0413f906ba770097e13c69d3cca08d65f Cango King <806784622@qq.com> 1715076207 +0800 reset: moving to HEAD~
master 分支引用文件记录了 master 分支指向的变迁,最新的改变会追加到文件的末尾。
通过 git reflog 命令,就可以方便地查看这些引用的变更历史,当然也包括 commit 和 reset 命令:
PS D:\Agit\gitbranch> git reflog
204111f (HEAD -> master) HEAD@{0}: reset: moving to HEAD~
61c2f69 HEAD@{1}: commit: three
204111f (HEAD -> master) HEAD@{2}: commit: two
8b265e7 HEAD@{3}: commit (initial): one
简单来说,reflog 就是将引用日志倒叙展示了。而是 reflog 对提交ID的简写形式也是使用 HEAD+特殊字符+数字的形式,只不过这个特殊字符不是 ~,而是 @,数字也用大括号括起来了,这个数字指的是 HEAD 指针前几次变动。
如果想还原 commit three,再此使用 reset 命令即可:
PS D:\Agit\gitbranch> git reset HEAD@{1}
PS D:\Agit\gitbranch> git log
commit 61c2f69b18cfefdd0953a372ec6e3576cf190b3e (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:31:25 2024 +0800
three
commit 204111f0413f906ba770097e13c69d3cca08d65f
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
one
PS D:\Agit\gitbranch> git status
On branch master
nothing to commit, working tree clean
git checkout 检出
前文介绍了 checkout 可以从暂存区和分支检出文件,其实它还可以移动头指针,但是效果却和 reset 有些差别。下面移动头指针到 commit one:
PS D:\Agit\gitbranch> git checkout HEAD~~
Note: switching to 'HEAD~~'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 8b265e7 one
git checkout 提交ID 在效果上相当于 git reset --hard 提交ID,这就意味着暂存区和工作区的文件都会被该提交覆盖掉。
但检出时的提示信息很丰富:说的是正处于“分离头指针(detached HEAD)”状态,这意味着你的工作树不再随着任何分支的移动而移动,而是直接连接到了某个具体的提交。你可以进行一些实验性的更改,甚至提交它们,但这些提交不会影响到任何分支。如果你想要保留这些提交,可以根据提示创建一个新的分支来保存它们。
如果你只是想临时查看这个提交的内容,而不需要进行任何更改,那么在完成后可以通过 git switch - 命令来撤销这个操作,将工作树切换回原来的状态。
那什么是分离头指针呢?看一下头指针 HEAD 文件:
PS D:\Agit\gitbranch> cat .git/HEAD
8b265e73e1c8a26dcde4f1a647954fd888a33345
PS D:\Agit\gitbranch> cat .git/refs/heads/master
61c2f69b18cfefdd0953a372ec6e3576cf190b3e
PS D:\Agit\gitbranch> git status
HEAD detached at 8b265e7
nothing to commit, working tree clean
PS D:\Agit\gitbranch> git log
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345 (HEAD)
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
one
PS D:\Agit\gitbranch> git reflog
8b265e7 (HEAD) HEAD@{0}: checkout: moving from master to HEAD~~
61c2f69 (master) HEAD@{1}: reset: moving to HEAD@{1}
204111f HEAD@{2}: reset: moving to HEAD^
61c2f69 (master) HEAD@{3}: commit: three
204111f HEAD@{4}: commit: two
8b265e7 (HEAD) HEAD@{5}: commit (initial): one
简单来说,分离头指针就是让头指针不再指向 master 分支引用,而是指向一个具体的提交。这里的 HEAD 就直接指向了 master 第一次的提交,master 分支引用指向的提交还是最新一次提交 commt three 。
提示还说可以在 checkout 检出的分支上再次提交,但这些提交不会影响到任何分支,这一点我们一会再看,先把单条提交链摸透。
PS D:\Agit\gitbranch> git status
HEAD detached at 8b265e7
nothing to commit, working tree clean
PS D:\Agit\gitbranch>
先按照提示的执行 git switch - 命令,恢复 HEAD 指向 master 分支的状态:
PS D:\Agit\gitbranch> git switch -
Previous HEAD position was 8b265e7 one
Switched to branch 'master'
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/master
PS D:\Agit\gitbranch> cat .git/refs/heads/master
61c2f69b18cfefdd0953a372ec6e3576cf190b3e
git revert 撤销
git reset 提交ID,是将 HEAD 指向分支引用的提交ID改成命令给出的提交ID,;git checkout 提交ID 是将 HEAD 指向命令给出的提交ID,不改变分支引用;而 git revert 提交ID 则是已创建一个新的提交的方式,撤销的给定提交的更改。
git revert 不会修改历史记录,而是在历史记录中添加一个新的提交来反转指定提交的更改,比如不想要 commit two 了:
PS D:\Agit\gitbranch> git revert HEAD~1
## 弹出输入框,写一下提交文案
Revert "two"
This reverts commit 204111f0413f906ba770097e13c69d3cca08d65f.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Changes to be committed:
# deleted: hello-branch-2.txt`
revert two
revert 之后,再看提交记录,发现多了一个新的提交,提交ID也是新的:
PS D:\Agit\gitbranch> git log
commit d686457ccfc18dddda3f0139da54d7686101525c (HEAD -> master)
Author: Cango King <806784622@qq.com>
Date: Mon May 13 17:05:48 2024 +0800
Revert "two"
This reverts commit 204111f0413f906ba770097e13c69d3cca08d65f.
revert two
commit 61c2f69b18cfefdd0953a372ec6e3576cf190b3e
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:31:25 2024 +0800
three
commit 204111f0413f906ba770097e13c69d3cca08d65f
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345
Author: Cango King <806784622@qq.com>
Date: Tue May 7 10:59:57 2024 +0800
one
PS D:\Agit\gitbranch>
如果想撤销撤销,同样可以使用 reset 命令。这里就不演示了。
分支
上述命令演示的都是 master 分支上的操作,master 分支也仅仅是.git/refs/heads下的文件(或称引用)而已。
为了直观表示,现在画个图展示一下 master 分支与 master 分支上的提交节点。
git branch 分支操作
git branch 常用于创建、列出或删除分支。
⨳ git branch:查看所有的分支以及当前所在的分支;
⨳ git branch -r:查看远程分支;
⨳ git branch <branch_name>:创建一个新的分支;
⨳ git branch -d <branch_name>:删除一个已存在的分支;
⨳ git branch -D <branch_name>:强制删除分支,即使它的更改没有被合并到其他分支中;
⨳ git branch -m <old_branch_name> <new_branch_name>:重命名分支;
现在我们创建一个新分支 b1:
PS D:\Agit\gitbranch> git branch
* master
PS D:\Agit\gitbranch> git branch b1
PS D:\Agit\gitbranch> git branch
b1
* master
在 master 分支上创建分支,本质就是创建一个分支引用文件 b1,引用文件指向原分支提交节点:
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/master
PS D:\Agit\gitbranch> cat .git/refs/heads/b1
d686457ccfc18dddda3f0139da54d7686101525c
PS D:\Agit\gitbranch> cat .git/logs/refs/heads/b1
0000000000000000000000000000000000000000 d686457ccfc18dddda3f0139da54d7686101525c Cango King <806784622@qq.com> 1715648286 +0800 branch: Created from master
创建分支的意义在于:“想要基于这个提交以及它所有的 parent 提交进行新的工作。”
注意,使用 branch 创建完分支后,HEAD 还是指向 master 并没有指向新分支 b1。
git switch 切换分支
git checkout 可以用来切换分支,但 checkout 的功能太繁杂了,既可以创建/切换分支,又可以移动 HEAD 指针(分离头指针),又可以从暂存区或分支中检出文件。
branch 已经将创建分支的功能拆分出来了,那切换分支的功能就用 switch 来完成。
PS D:\Agit\gitbranch> git switch b1
Switched to branch 'b1'
PS D:\Agit\gitbranch> git branch
* b1
master
PS D:\Agit\gitbranch> cat .git/HEAD
ref: refs/heads/b1
分支切换的本质就是改变 HEAD 指向的分支。
使用 switch 的 -c 选项也可以将创建分支和切换分支合成一步:
PS D:\Agit\gitbranch> git switch -c b2
Switched to a new branch 'b2'
PS D:\Agit\gitbranch> git show HEAD
commit d686457ccfc18dddda3f0139da54d7686101525c (HEAD -> b2, master, b1)
Author: Cango King <806784622@qq.com>
Date: Mon May 13 17:05:48 2024 +0800
Revert "two"
This reverts commit 204111f0413f906ba770097e13c69d3cca08d65f.
revert two
现在三个分支引用文件都指向同一个提交节点,目前 HEAD 指向 b2分支。
除了切换分支,git switch 还可以用于检出特定的提交,和 checkout 一样,检出后会出现分离头指针的情况。
PS D:\Agit\gitbranch> git log --oneline
d686457 (HEAD -> b2, master, b1) Revert "two"
61c2f69 three
204111f two
8b265e7 one
PS D:\Agit\gitbranch> git switch --detach 8b26
HEAD is now at 8b265e7 one
PS D:\Agit\gitbranch> git show HEAD
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345 (HEAD)
Author: Cango King <806784622@qq.com>
PS D:\Agit\gitbranch> git status
HEAD detached from 8b265e7
nothing to commit, working tree clean
现在 HEAD 指向,即不再 master 分支上,也不在 b1 分支上,也不在 b2 分支上,而是在某一个特点提交节点。
那我如果在这个提交节点上做出变更,再次提交会出现什么状况呢?
PS D:\Agit\gitbranch> touch detach_head_1.txt
PS D:\Agit\gitbranch> git add detach_head_1.txt
PS D:\Agit\gitbranch> git commit -m 'detach one'
[detached HEAD 3046b5e] detach one
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 detach_head_1.txt
PS D:\Agit\gitbranch> git log --oneline
3046b5e (HEAD) detach one
8b265e7 one
PS D:\Agit\gitbranch> git cat-file -p HEAD
tree f8eea5787b2d41a17c94c982411aa8f879190c34
parent 8b265e73e1c8a26dcde4f1a647954fd888a33345
在分离头指针状况下,还是可以再此提交节点基础上再次提交的,新的提交也会通过 parent 属性与关联到前边的提交节点,只是这些提交节点不属于任何已有分支。
git reset 重置
分离头指针的情况下提交不会影响现有分支,那如果使用 reset 命令呢?
前边讲过,git reset 可以通过改变分支引用提交节点的方式改变 HEAD 指向,本意就是回撤提交节点,那如果 reset 到前边一个节点,再次提交会出现什么状况呢?
先切换到 master 分支:
PS D:\Agit\gitbranch> git switch master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
3046b5e detach one
If you want to keep it by creating a new branch, this may be a good time
to do so with:
git branch <new-branch-name> 3046b5e
Switched to branch 'master'
Git 给出了一个警告,指出离开的提交节点并没有连接到你的任何分支上。如果想保留这个提交,可以使用 git branch <new-branch-name> <commit-hash> 命令来创建一个新的分支,以保留这个提交。
分离头指针一般在想看看某个提交节点的内容时使用,如果真想保留分离头指针时的提交,也不需要建立分支这么麻烦,直接切换到 master 分支,合并这一个 提交节点即可。
reset 到 commit one 提交:
PS D:\Agit\gitbranch> git log --graph --oneline
* d686457 (HEAD -> master, b2, b1) Revert "two"
* 61c2f69 three
* 204111f two
* 8b265e7 one
PS D:\Agit\gitbranch> git reset --hard HEAD~3
PS D:\Agit\gitbranch> git log --graph --oneline
* 8b265e7 (HEAD -> master) one
reset 之后,重置节点之后的节点都看不到了,这并不意味着之后的节点不存在了,reflog 可以找到这些节点,切换分支也能找到这些节点,如切换到 b1 分支:
PS D:\Agit\gitbranch> git switch b1
Switched to branch 'b1'
PS D:\Agit\gitbranch> git log --graph --oneline
* d686457 (HEAD -> b1, b2) Revert "two"
* 61c2f69 three
* 204111f two
* 8b265e7 (master) one
那如果再切换到 master 分支上,在 commit one 节点上提交,会发生什么呢?
PS D:\Agit\gitbranch> git switch master
Switched to branch 'master'
PS D:\Agit\gitbranch> touch reset_head_1.txt
PS D:\Agit\gitbranch> git add reset_head_1.txt
PS D:\Agit\gitbranch> git commit -m 'reset master one'
[master 795661c] reset master one
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 reset_head_1.txt
PS D:\Agit\gitbranch> git log --graph --oneline
* 795661c (HEAD -> master) reset master one
* 8b265e7 one
可以看到,分离头指针的情况下新的提交不属于任何分支,不分离头指针的情况下新的提交就属于HEAD指向的分支。
git merge 合并
为了讲解多种合并情况,先切换到 b1 分支做个提交,再切换到 b2 分支做个提交:
PS D:\Agit\gitbranch> git switch b1
Switched to branch 'b1'
PS D:\Agit\gitbranch> touch b1_one.txt
PS D:\Agit\gitbranch> git add b1_one.txt
PS D:\Agit\gitbranch> git commit -m 'b1 one'
[b1 86006a1] b1 one
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b1_one.txt
PS D:\Agit\gitbranch> git switch b2
Switched to branch 'b2'
PS D:\Agit\gitbranch> touch b2_one.txt
PS D:\Agit\gitbranch> git add b2_one.txt
PS D:\Agit\gitbranch> git commit -m 'b2 one'
[b2 ad36970] b2 one
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b2_one.txt
使用 log 的 --all 选项显示所有分支上的提交历史。
PS D:\Agit\gitbranch> git log --all --graph --oneline
* ad36970 (HEAD -> b2) b2 one
| * 86006a1 (b1) b1 one
|/
* d686457 Revert "two"
* 61c2f69 three
* 204111f two
| * 795661c (master) reset master one
|/
* 8b265e7 one
还是画图直观一点:
注意,分离头指针时的提交因为不属于任何分支,并不会在 log 中展现。
合并是将多个分支的更改整合到一起的方法。git merge 命令可以将一个或多个分支的修改合并到当前分支。要合并分支,首先要切换到接收更改的目标分支,然后运行如下命令:
git merge <要合并的分支名>
那就以 master 作为主体,先把分离头指针后的提交节点合并进 master:
PS D:\Agit\gitbranch> git switch master
Switched to branch 'master'
PS D:\Agit\gitbranch>
PS D:\Agit\gitbranch> git merge 3046b5e
Merge made by the 'ort' strategy.
detach_head_1.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 detach_head_1.txt
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 919945f (HEAD -> master) Merge commit '3046b5e' merge detach head one
|\
| * 3046b5e detach one
* | 795661c reset master one
|/
| * ad36970 (b2) b2 one
| | * 86006a1 (b1) b1 one
| |/
| * d686457 Revert "two"
| * 61c2f69 three
| * 204111f two
|/
* 8b265e7 one
master 合并完分离头指针的提交节点后,该提交节点出现在 log 中了,所以说 merge 不仅仅可以合并分支也可以合并节点,毕竟分支就是对节点的引用:
合并后会生成一个新的提交节点,指向两个父节点:
PS D:\Agit\gitbranch> git cat-file -p HEAD
tree 657279e5be8cc7048196672fb422103b76493132
parent 795661ce9345fd4a108a43496e522b4d9d4e83d6
parent 3046b5e4c69a23f8d718a07da9d3205cc2811bec
author Cango King <806784622@qq.com> 1716166067 +0800
committer Cango King <806784622@qq.com> 1716166067 +0800
Merge commit '3046b5e'
merge detach head one
再合并 b1:
PS D:\Agit\gitbranch> git merge b1
Merge made by the 'ort' strategy.
b1_one.txt | 0
hello-branch-3.txt | 0
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b1_one.txt
create mode 100644 hello-branch-3.txt
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 61d5457 (HEAD -> master) Merge branch 'b1' merge b1
|\
| * 86006a1 (b1) b1 one
* | 919945f Merge commit '3046b5e' merge detach head one
|\ \
| * | 3046b5e detach one
* | | 795661c reset master one
|/ /
| | * ad36970 (b2) b2 one
| |/
| * d686457 Revert "two"
| * 61c2f69 three
| * 204111f two
|/
* 8b265e7 one
合并完的分支并不会自动删除,要使用 git branch -d <branch_name> 命令:
PS D:\Agit\gitbranch> git branch -d b1
Deleted branch b1 (was 86006a1).
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 61d5457 (HEAD -> master) Merge branch 'b1' merge b1
|\
| * 86006a1 b1 one
* | 919945f Merge commit '3046b5e' merge detach head one
|\ \
| * | 3046b5e detach one
* | | 795661c reset master one
|/ /
| | * ad36970 (b2) b2 one
| |/
| * d686457 Revert "two"
| * 61c2f69 three
| * 204111f two
|/
* 8b265e7 one
PS D:\Agit\gitbranch>
b2 不想合并了,也想删除,这就需要强制删除了:
PS D:\Agit\gitbranch> git branch -d b2
error: the branch 'b2' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D b2'
hint: Disable this message with "git config advice.forceDeleteBranch false"
PS D:\Agit\gitbranch> git branch -D b2
Deleted branch b2 (was ad36970).
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 61d5457 (HEAD -> master) Merge branch 'b1' merge b1
|\
| * 86006a1 b1 one
| * d686457 Revert "two"
| * 61c2f69 three
| * 204111f two
* | 919945f Merge commit '3046b5e' merge detach head one
|\ \
| * | 3046b5e detach one
| |/
* / 795661c reset master one
|/
* 8b265e7 one
到此为止,版本库中只有一个 master 分支了:
PS D:\Agit\gitbranch> git branch
* master
PS D:\Agit\gitbranch>
还有一个小问题,前边讲过,可以使用 HEAD~ 数字的形式代替 HEAD 前几个父节点,那现在 61d5457 有两个父节点,HEAD~ 代表哪个父节点呢?
PS D:\Agit\gitbranch> git show HEAD~
commit 919945fdff8d3e82ce94be9059501e0572fa3137
Merge: 795661c 3046b5e
Author: Cango King <806784622@qq.com>
Date: Mon May 20 08:47:47 2024 +0800
Merge commit '3046b5e'
merge detach head one
HEAD~ 默认是第一个分支的父节点,准确地说是 git log --graph 显示的最左边的一条主分支:
PS D:\Agit\gitbranch> git show HEAD~~
commit 795661ce9345fd4a108a43496e522b4d9d4e83d6
PS D:\Agit\gitbranch> git show HEAD~~~
commit 8b265e73e1c8a26dcde4f1a647954fd888a33345
如果我想选择从左往右数第二条分支怎么办?用 ^ 即可, HEAD^ 后面加数字表示在第几个分支上回退。
⨳ 默认数字是1:如 HEAD^^ 等同于 HEAD~~,HEAD^1 等同于 HEAD~1,HEAD^1^1 等同于 HEAD~1~1,^的个数表示回退几次,和~一样。
⨳ 当 HEAD^ 后面的数字大于1,则表示切换到第几个分支上退回:如 HEAD^2,表示退一步到第二个父提交上, HEAD^^3 表示先在第一个分支上后退一步,再在第三个分支上后退一步 。
⨳ ~ 和 ^ 可以组合使用:如HEAD~2^2 表示先在第一个分支上后退两步,再选择第二个分支上后退一步。
了解了这些,就可以选取任意节点了,如定位 commit two 204111f,就可以先使用 ^2 回退到 HEAD 第二个分支的父节点上,再后退三次就可以了:
PS D:\Agit\gitbranch> git show HEAD^2~3
commit 204111f0413f906ba770097e13c69d3cca08d65f
Author: Cango King <806784622@qq.com>
Date: Tue May 7 16:26:17 2024 +0800
two
git cherry-pick 挑选樱桃
将代码从一个分支转移到另一个分支是常见需求。如果你需要另一个分支的所有代码变动,那么就可以采用合并(merge)。但是如果你只需要部分代码变动(某几个提交),这时可以采用 Cherry pick。
Cherry pick 用于将一些提交复制到当前所在的位置(HEAD):
git cherry-pick <提交号>...
比如在 commit tree 提交节点处新建一个分支 b3:
PS D:\Agit\gitbranch> git branch b3 61c2f69
PS D:\Agit\gitbranch> git log --all --oneline --graph
* 61d5457 (HEAD -> master) Merge branch 'b1' merge b1
|\
| * 86006a1 b1 one
| * d686457 Revert "two"
| * 61c2f69 (b3) three
| * 204111f two
* | 919945f Merge commit '3046b5e' merge detach head one
|\ \
| * | 3046b5e detach one
| |/
* / 795661c reset master one
|/
* 8b265e7 one
使用 Cherry pick 可以在 master 分支上挑选几个分支节点作为 b3 的提交节点:
PS D:\Agit\gitbranch> git cherry-pick d686457 3046b5e
[b3 08d8edd] Revert "two"
Date: Mon May 13 17:05:48 2024 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 hello-branch-2.txt
[b3 998abdb] detach one
Date: Tue May 14 09:55:27 2024 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 detach_head_1.txt
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 998abdb (HEAD -> b3) detach one
* 08d8edd Revert "two"
| * 61d5457 (master) Merge branch 'b1' merge b1
| |\
| | * 86006a1 b1 one
| | * d686457 Revert "two"
| |/
|/|
* | 61c2f69 three
* | 204111f two
| * 919945f Merge commit '3046b5e' merge detach head one
| |\
| | * 3046b5e detach one
| |/
|/|
| * 795661c reset master one
|/
* 8b265e7 one
PS D:\Agit\gitbranch>
虽然分支 b3 的两个提交是从 master 分支上 pick 出来的,但提交ID是不一样的,这意味着提交对象是不一样的,这意味着彼此互不影响。
git rebase 变基
开发中经常会出现一种情况,以 master 分支为基底新建开发分支,开发分支开发一段时间后,master 分支也有了新的提交,这是如果开发分支想合并 master 分支新的提交,怎么办?
当然可以使用捡樱桃的方式将新的提交捡到开发分支,但也可以使用 rebase,将开发分支的基底变成最新的 master 分支提交。
假设现在的 b3 分支是当时 master 分支提交到 commt three 时新建的,但现在 b3 分支已经开发了一段时间,master 分支也开发了一段时间,二者已分道扬镳很久了,那就可以使用 rebase 将 b3 分支的基底再次变到 master 分支最新提交上,而 b3 分支的提交就相当于在 master 分支最新提交上更改的:
PS D:\Agit\gitbranch> git branch
* b3
master
PS D:\Agit\gitbranch> git rebase master
warning: skipped previously applied commit 08d8edd
warning: skipped previously applied commit 998abdb
hint: use --reapply-cherry-picks to include skipped commits
hint: Disable this message with "git config advice.skippedCherryPicks false"
Successfully rebased and updated refs/heads/b3.
PS D:\Agit\gitbranch> git log --oneline --graph --all
* 61d5457 (HEAD -> b3, master) Merge branch 'b1' merge b1
|\
| * 86006a1 b1 one
| * d686457 Revert "two"
| * 61c2f69 three
| * 204111f two
* | 919945f Merge commit '3046b5e' merge detach head one
|\ \
| * | 3046b5e detach one
| |/
* / 795661c reset master one
|/
* 8b265e7 one
按理说,b2 rebase 后,b2 指向 master 指向提交后两个提交,但因为 b3 的两个提交是从 matser 分支挑选出来的,所以 GIT 自动跳过了这两个提交(08d8edd,998abdb),使得 b2 和 master 指向同一个提交。
到目前为止,已经讲了三种合并代码的方式了:
⨳ merge:将两个分支的提交合并,产生一个新的合并提交。
⨳ cherry-pick:选择一个或多个提交应用到当前分支,每个选择的提交都会产生一个新的提交。
⨳ rebase:将当前分支的提交逐个应用到目标分支上,并移动当前分支指针,不会产生合并提交。
当然本次演示是没人任何冲突的情况,直接合并就可以了。
rebase 还可以合并本地的多条提交(commit)记录,我们知道,每一个的 commit 都会形成一个git 提交节点,一个 log 日志,如果我们把这些节点都 push 到远端,就会使项目的日志很乱,这时,就可以在 push 之前,使用 rebase 将几次本地的 commit 操作合并,这样,我们推送到远端的 commit 操作就只有一个了,更利于项目管理。
因为 master 在第一个分支上往前推3个节点就是最初的提交节点,那先合并 master 分支:
git switch master
PS D:\Agit\gitbranch> git rebase -i HEAD~3
执行完后命令后,Git 将会打开一个交互式(interactive)界面,显示可用操作和需要合并或修改的提交记录列表。在交互式界面中,将需要合并的提交记录前面的 pick 操作修改为 squash(或者 s 简写),表示将该提交合并到前一个提交。
pick 795661c reset master one
squash 3046b5e detach one
squash 204111f two
squash 61c2f69 three
squash d686457 Revert "two"
squash 86006a1 b1 one
保存退出,合并完成后,看一下 log 可以发现 master 直接从 8b265e7 到合并后的提交 960a59b:
PS D:\Agit\gitbranch> git log --all --graph --oneline
* 960a59b (HEAD -> master) reset master one
| * 61d5457 (b3) Merge branch 'b1' merge b1
| |\
| | * 86006a1 b1 one
| | * d686457 Revert "two"
| | * 61c2f69 three
| | * 204111f two
| |/
|/|
| * 919945f Merge commit '3046b5e' merge detach head one
| |\
| | * 3046b5e detach one
| |/
|/|
| * 795661c reset master one
|/
* 8b265e7 one
以相同的方法 合并 b3,最后就留下了一个起始节点和两个合并节点:
* 571b9b4 (HEAD -> b3) reset master one
| * 960a59b (master) reset master one
|/
* 8b265e7 one
暂存
git stash 贮藏
有分支就有切换分支,如果在一个分支开发新功能,还没开发完毕,做到一半时有反馈紧急bug需要处理,但是新功能开发了一半又不想提交,这怎么办呢?
PS D:\Agit\gitbranch> git status
On branch b3
nothing to commit, working tree clean
PS D:\Agit\gitbranch>
如果文件在工作区和暂存区和该提交对象的版本不一致,会出现以下问题:
⨳ 文件未被git追踪:未追踪的文件由于不受git管理,切换分支时,未被追踪的文件还会留着工作区,即使切换分支,这就污染了切换分支后的工作区;
⨳ 文件在暂存区中,但是未提交到版本库:切换分支时,在暂存区中的文件依然在暂存区,这会污染切换分支后的工作区和暂存区。
⨳ 文件在暂存区、和版本库中,但工作区版本被改过:强制切换分支,该文件会被切换目的分支中的文件替换。
即使前两种状态下也可以切换分支,但还是不建议这样做,下面演示一下第三种,在 b3 分支新建一个文件提交到版本库,并在工作区修改该文件,使得工作区与版本库中的文件不一致:
PS D:\Agit\gitbranch> touch b3.txt
PS D:\Agit\gitbranch> git add b3.txt
PS D:\Agit\gitbranch> git commit b3.txt -m 'commit b3'
[b3 1fafb4c] commit b3
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 b3.txt
PS D:\Agit\gitbranch> echo ‘modify something’ >> b3.txt
PS D:\Agit\gitbranch> git status
On branch b3
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: b3.txt
no changes added to commit (use "git add" and/or "git commit -a")
这时切换到 master 分支会报错,说的是该文件会被 master 版本库所覆盖:
PS D:\Agit\gitbranch> git switch master
error: Your local changes to the following files would be overwritten by checkout:
b3.txt
Please commit your changes or stash them before you switch branches.
Aborting
将对该文件的修改提交到暂存区也不行:
PS D:\Agit\gitbranch> git add b3.txt
PS D:\Agit\gitbranch> git switch master
error: Your local changes to the following files would be overwritten by checkout:
b3.txt
Please commit your changes or stash them before you switch branches.
Aborting
其实不只是切换分支,reset、checkout 某一提交节点最好都需要一个干净的工作区,使得工作区和暂存区和该提交对象的版本一致,那在分支上改的东西怎么办?又想保留,又想切换分支,又不想提交,这就需要 stash 命令了:
使用 git stash 会将当前的工作区和暂存区的修改内容保存到一个栈中,并将工作区和暂存区还原到最后一次提交的状态。这样就可以让你在一个干净的工作区上继续其他工作,而不必提交尚未完成的修改。
PS D:\Agit\gitbranch> git stash
Saved working directory and index state WIP on b3: 1fafb4c commit b3
stash 贮藏工作区和暂存区的本质是啥呢?其实也是一个 commit 对象:
PS D:\Agit\gitbranch> cat .git/refs/stash
57bdf986e47333fe92c6a5aca2dceb2cfccaf96a
PS D:\Agit\gitbranch> git show 57bdf986e47333fe92c6a5aca2dceb2cfccaf96a
commit 57bdf986e47333fe92c6a5aca2dceb2cfccaf96a (refs/stash)
Merge: 1fafb4c 7acf705
Author: Cango King <806784622@qq.com>
Date: Tue May 21 08:56:45 2024 +0800
WIP on b3: 1fafb4c commit b3
PS D:\Agit\gitbranch> git log --graph --all --oneline 57bdf98
* 57bdf98 (refs/stash) WIP on b3: 1fafb4c commit b3
|\
| * 7acf705 index on b3: 1fafb4c commit b3
|/
* 1fafb4c (HEAD -> b3) commit b3
* 571b9b4 reset master one
| * 960a59b (master) reset master one
|/
* 8b265e7 one
1fafb4c 是 b3 分支最新一次提交,7acf705 是对暂存区保存的提交,如果工作区和暂存区不一致,还会有一个对工作区保存的提交,这三次提交的合并形成的提交就是所谓的 stash 引用。
既然工作区已经干净了,就可以切换到 master 了,干了一段时间后再切到 b3,可以使用 list 查看储藏历史,使用 show 看储藏了啥,使用 apply 恢复储藏到暂存区和工作区,使用 drop 来删除不再需要的储藏。
PS D:\Agit\gitbranch> git switch master
Switched to branch 'master'
PS D:\Agit\gitbranch> git switch b3
Switched to branch 'b3'
PS D:\Agit\gitbranch> git stash list
stash@{0}: WIP on b3: 1fafb4c commit b3
PS D:\Agit\gitbranch> git stash show stash@{0}
b3.txt | Bin 0 -> 38 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
PS D:\Agit\gitbranch> git stash apply stash@{0}
On branch b3
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: b3.txt
no changes added to commit (use "git add" and/or "git commit -a")
PS D:\Agit\gitbranch> git stash drop stash@{0}
Dropped stash@{0} (57bdf986e47333fe92c6a5aca2dceb2cfccaf96a)
git tag 标签
tag 是对提交的标签,为啥需要给提交打标签呢?
比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,反正对于重要的提交节点,都可以打上标签,打上标签的提交就是所谓的里程碑。
更难得的是,里程碑不会随着新的提交而移动,也不能切换到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。
要创建一个新标签,可以使用 git tag <tag_name> 命令。因为标签与提交相关联的,因此在创建标签之前,确保你在正确的提交上,或者指定某个提交也行。比如在 b3 分支上选个提交作为里程碑:
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 1fafb4c (HEAD -> b3) commit b3
* 571b9b4 reset master one
| * 960a59b (master) reset master one
|/
* 8b265e7 one
PS D:\Agit\gitbranch> git tag v1.1 571b9b4
PS D:\Agit\gitbranch> git log --graph --oneline --all
* 1fafb4c (HEAD -> b3, tag: list) commit b3
* 571b9b4 (tag: v1.1) reset master one
| * 960a59b (master) reset master one
|/
* 8b265e7 one
PS D:\Agit\gitbranch> git show v1.1
commit 571b9b43c345b86e07b281c997d77f99328f82a5 (tag: v1.1)
Author: Cango King <806784622@qq.com>
Date: Tue May 14 10:57:59 2024 +0800
标签也是一个提交引用而已:
PS D:\Agit\gitbranch> cat .git\refs\tags\v1.1
571b9b43c345b86e07b281c997d77f99328f82a5
远程分支
前文大致讲了一下本地仓库与远程仓库交互的几个命令:push 推送、fetch 拉取、pull 拉取与合并。
这里在码云上建一个远程仓库:https://gitee.com/Cango_King/gitbranch.git
首先,建立一下关联:
PS D:\Agit\gitbranch> git remote add origin https://gitee.com/Cango_King/gitbranch.git
先推送 master 分支到远程仓库:
PS D:\Agit\gitbranch> git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 540 bytes | 15.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Powered by GITEE.COM [GNK-6.4]
To https://gitee.com/Cango_King/gitbranch.git
* [new branch] master -> master
在 ./git/ref 目录下可以看到这个远程分支 master:
PS D:\Agit\gitbranch> cat .git\refs\remotes\origin\master
960a59b39bb0d82ba1a21af23f4255599257b981
PS D:\Agit\gitbranch> git show 960a59b3
commit 960a59b39bb0d82ba1a21af23f4255599257b981 (origin/master, master)
Author: Cango King <806784622@qq.com>
Date: Tue May 14 10:57:59 2024 +0800
所以说,远程分支和本地分支一样都是对 commit 对象的引用,只不过这个引用所在目录不一样(一个是 .git/refs/remotes/,一个是 .git/refs/heads/ ),名字也不一样(一个是 origin/master,一个是 master )。
在远程仓库上,建一个开发分支:
在远程仓库的开发分支上新建一个文件:
使用 fetch 命令可以将这个远程分支拉到本地:
PS D:\Agit\gitbranch> git fetch origin develop
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 964 bytes | 3.00 KiB/s, done.
From https://gitee.com/Cango_King/gitbranch
* branch develop -> FETCH_HEAD
* [new branch] develop -> origin/develop
PS D:\Agit\gitbranch> cat .git\refs\remotes\origin\cat
PS D:\Agit\gitbranch> cat .git\refs\remotes\origin\develop
c23791b2e01a5833209af57d4f15e4b19b26277a
PS D:\Agit\gitbranch> git log --all --graph --oneline
* c23791b (origin/develop) add develop.MD.
* 960a59b (origin/master, master) reset master one
| * 1fafb4c (HEAD -> b3) commit b3
| * 571b9b4 (tag: v1.1) reset master one
|/
* 8b265e7 one
PS D:\Agit\gitbranch>
可以看到 origin/develop 分支是在 origin/master 基础上创建的,那直接切换到该分支进行更改行不行?
PS D:\Agit\gitbranch> git switch origin/develop
fatal: a branch is expected, got remote branch 'origin/develop'
hint: If you want to detach HEAD at the commit, try again with the --detach option.
不行,就是想切换,也只会处于分离头指针状态,就是只能看,不能改,所以说远程分支引用是一种类似于里程碑的引用。
那怎么修改远程分支呢?在远程分支的基础上创建本地分支,在本地分支上修改完后,再推送到远程分支即可:
PS D:\Agit\gitbranch> git branch develop origin/develop
branch 'develop' set up to track 'origin/develop'.
PS D:\Agit\gitbranch> git branch
* b3
develop
master
PS D:\Agit\gitbranch> git switch develop
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
PS D:\Agit\gitbranch> touch develop.txt
PS D:\Agit\gitbranch> git add develop.txt
PS D:\Agit\gitbranch> git commit develop.txt -m '添加开发文件'
[develop c91401c] 娣诲姞寮€鍙戞枃浠
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 develop.txt
PS D:\Agit\gitbranch> git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 255 bytes | 255.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Powered by GITEE.COM [GNK-6.4]
To https://gitee.com/Cango_King/gitbranch.git
c23791b..c91401c develop -> develop
本地的远程分支用完了删掉就行,用的时候再 fetch 或 pull 即可:
PS D:\Agit\gitbranch> git log --graph --oneline --all
* c91401c (HEAD -> develop, origin/develop) 添加开发文件
* c23791b add develop.MD.
* 960a59b (origin/master, master) reset master one
| * 79c95ca (refs/stash) WIP on b3: 1fafb4c commit b3
| |\
| | * f946182 index on b3: 1fafb4c commit b3
| |/
| * 1fafb4c (b3) commit b3
| * 571b9b4 (tag: v1.1) reset master one
|/
* 8b265e7 one
PS D:\Agit\gitbranch> git branch -d --remote origin/develop
Deleted remote-tracking branch origin/develop (was c91401c).
最后再提一下 fetch 和 pull 区别:
⨳ fetch:将数据拉取到本地仓库,创建 origin 分支,不会自动合并或修改当前的工作。
⨳ pull: 将数据拉取到本地仓库,创建 origin 分支,自动与本地同名仓库合并,本地同名仓库的提交节点向前移一位,如无本地同名仓库还会自动创建。
总结
在码云创建仓库时,总要选择一下分支模型,这里稍微介绍一下:
⨳ master 分支:主分支,和生产的上线代码应该一模一样,所有提供给用户使用的正式版本,都在这个主分支上体现,也就是说软件有几个版本,主分支上就应该有几个提交节点。
⨳ develop 分支:开发分支,用于日常开发,派生于 master 分支,但比 master 走的更远。
⨳ feature 分支:特性分支,派生于 develop 分支,每个feature分支只改一个特性,开发完成后需要合并到 develop 分支。
⨳ release 分支:预发布分支,也是派生于 develop 分支,当每个 feature 都合并到 develop 后,可以创建 预发布分支 用于测试。
⨳ hotfix 分支:修复 bug 分支,派生于 master ,用于修改线上代码 bug,修改完成后,再合并进 master 和 develop 分支 。
代码库的常设分支始终只有 master 和 develop,当有新需求就是可以安装功能划分在 develop 分支上派生出多个 feature 分支,这些 feature 分支 可以由多个开发人员进行开发,开发完成则将其合并到 feature 分支,全部特性开发完成,就可以创建一个 release 分支供测试人员测试,测试无误,则可以将其合并到 master 分支进行上线,并将 master 和 develop 以外的分支删除掉,如果生产上有 BUG ,就从 master 分支上派生一个 hotfix 分支,紧急修复。