Git

撷英采华 —— Git 从炼气到大乘

git
git

Quick Start

First-Time Git

集中化版本控制要求所有的代码都存储在中央服务器上,需要通过网络连接到中央服务器来进行版本控制。分布式版本控制不依赖于中央服务器,每个本地仓库都具有完整的版本历史记录和代码库。

通过 git config --list --show-origin 可以查看所有的 Git 配置及其所在:

  • /etc/gitconfig 包含系统上每位用户及其对应仓库的通用配置
  • ~/.gitconfig~/.config/git/config 针对当前用户的配置文件
  • .git/config 是当前使用仓库的项目配置

设置用户名与邮件地址是必不可少的,与此同时,如果使用了 --global 选项,那么设置命令在运行一次后,后续的 Git 操作都会使用这些信息。

$ git config --global user.name "<name>"
$ git config --global user.email "<email>"

检查全局和仓库级别的配置可以使用 git config --list。查看某个特定配置的值,可以使用 git config <key> 命令,其中 <key> 为要查找的配置项的键名。

$ git config user.name

Git 中的对象主要有三种类型:Blob、Tree 和 Commit:

  • Blob 对象(二进制大对象):代表文件内容,存储文件的数据,即文件的快照
  • Tree 对象:代表目录,存储了一组文件或目录的信息,包括文件名、类型、权限等
  • Commit 对象:代表一个提交,记录了某一时刻工作目录的状态

Git Index may be defined as the staging area between the workspace and the repository. The major use of Git Index is to set up and combine all changes together before you commit them to your local repository.

在 Git 中 Index 也称为 Staging Area,即暂存区(索引区),用于保存下一次将要提交的修改,实际上就是一个名为 .git/index 的二进制文件。

在使用 git add 命令后,被添加的文件会存储到 Git 对象库的 .git/objects 目录中,并使用索引进行定位。而后续执行 git commit 命令时,提交的是当前索引中的内容,而不是工作区的内容。同时,在执行提交命令后,.git/objects 目录中会新增 commit 类型和 tree 类型的对象。

注意,.git/objects 目录下每个文件夹的名称前两个字符是对象的 SHA-1 值的前两个字符,每个文件夹中存储了以该 SHA-1 值为名称的对象文件。这些对象文件包含了对象的类型、大小和内容等。

$ git branch -D dev # 删除一个名为 dev 的分支
$ Deleted branch dev (was 45de930).
$ git cat-file -t 45de930 # 查询指定哈希值的对象类型
commit
$ git cat-file -p 45de930 # 查看提交对象的详细信息
tree efb888bed3302a0cb8808046c501d9f3e2a881e8
parent 8c9588c5965a18bd89ccc3f7006a03cb0a0a463b
author ...
committer ...
$ git cat-file -p efb888 # 查看树对象的详细信息
100644 blob 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6	.DS_Store
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c	README.md
100644 blob 38f8e886e1a6d733aa9bfa7282568f83c133ecd6	dev.txt
$ git cat-file -p 38f8e886 # 查看被误删的文件 dev.txt 的内容
dev # 垃圾对象 —— 原文件的内容

在上述代码中查看树对象的详细信息时,有使用到树对象哈希值的前六位,也被称为短哈希。因为哈希值是根据内容计算得到的,所以前几位相同的哈希值代表的对象内容也应该是相同的。通常情况下,建议至少使用前六位,以确保准确性。

SHA-1 算法生成的散列值长度为 160 位,也就是 20 个字节,每个字节可以表示成两位十六进制数,所以通常呈现的散列值长度为 40 个字符。

哈希算法是一种将任意长度的数据映射到固定长度输出的算法,输入数据的任何细微变化都会在输出中体现出来,因此常被用于数据完整性验证、密码存储、数据比对等领域。

如果将多个完全相同的文件添加到对象时,只会存在唯一一个文件对象被存储(文件内容唯一性),这也是得益于 SHA-1 摘要算法。

一个 commit 对象会引用一个 tree 对象,而 tree 对象会引用若干个 blob 对象或其他 tree 对象。因此,一个 commit 对象所引用的 tree 对象包含了本次提交的所有文件,而每个 blob 对象则包含了一个文件的具体内容。

.git/
├── COMMIT_EDITMSG
├── HEAD
...
├── objects
│   ├── 1e/
│   │   └── 6a4608f5fe92ea0169fa033eeac1d38049aa63
│   ├── 05/
│   │   └── 2c88abb60a55ab8be2bca4b5c4cf29271c2de2
...
$ git cat-file -t 1e6a4608f
commit
$ git cat-file -p 052c88ab
100644 blob 9cf106272ac3b56b0c4c80218e8fc10a664ca5f4	LICENSE.md
100644 blob ce72ebbfe645054cd7020b980292b37abae34203	README.md
040000 tree 3a654b3398171dc10a94cc1ab0b73ea195307f2f	doc
...

HEAD is the reference to the most recent commit in the current branch. This means HEAD is just like a pointer that keeps track of the latest commit in your current branch. Detached HEAD occurs when you check out a commit that is not a branch.

.git/HEAD 存储了当前分支的指针,而 .git/refs/heads/ 目录下存储了所有本地分支的指针,分支指针所指向的是相应分支最新提交的 SHA-1 值。在切换分支时,HEAD 会指向不同的分支或提交。

cat refs/heads/master # 最新的 commit 分支
8c9588c5965a18bd89ccc3f7006a03cb0a0a463b
cat HEAD
ref: refs/heads/master

如果 HEAD 不再指向某个分支而是指向了某个提交,就会进入 "detached HEAD" 状态。此时,如果执行 git log 命令,将只会看到该 commit 前的历史记录。

Undoing Things

如果在提交后发现有文件遗漏,或是提交信息弄错,可以使用 git commit --amend 来重新提交。

$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend # 将新的修改合并到上一次提交中,并保留上一次提交的信息
$ git commit --amend -m "<new msg>" # 会将上一次提交的 commit message 替换

在 Git 中,restore 和 reset 都是用于撤销修改的命令。前者主要用于恢复工作区中的文件,但并不会影响 Git 历史记录。后者则可以将当前 HEAD 重置为指定状态,从而修改 Git 历史记录。

git restore --staged <file> 可以用来撤销 git add <file> 命令所做的暂存操作,将文件状态从暂存区恢复到工作区。而 git restore <file> 则可以用来撤销工作区中对文件的修改,将文件状态恢复到最近一次提交时的状态。注意,git restore <file> 只能用来还原文件的内容,而不能还原文件的添加或删除操作。如果要撤销文件的添加或删除操作,需要使用其他命令,如 git rmgit add

# 修改 README 文件
nvim README.md
# 在修改 README 文件后放弃工作目录中的更改
git restore README.md # "git restore <file>..." to discard changes in working directory
# 修改 README.md 后,将工作区的更改添加到暂存区
git add README.md
# 移除已添加到暂存区的文件,但改动内容不会消失
git restore README.md --staged # "git restore --staged <file>..." to unstage

git reset --soft 会将指定提交后的所有更改放回到暂存区,并将之前暂存区内的更改移动到工作区,工作区的内容不会修改(可能需要手动解决冲突)。

git reset 默认的模式是 --mixed,等价 git reset --mixedgit reset --mixed 将指定提交后的所有更改放回到工作区,并将之前添加到暂存区的更改移除暂存区。

git reset --hard 将指定提交后的所有更改全部删除(工作区和暂存区)。

注意,如果在执行上述 git reset 命令时未指定提交标识符,则会将当前分支指向上一次的提交。

GitTreeMovementsVisualized_v1v2nm

git commit -m "ctx"
# 撤消提交或暂存快照,即执行 commit 后,还没执行 push 时想要撤销这次的 commit
git reset --soft HEAD^ # 成功撤销 commit; HEAD^ 表示上一个版本,即上一次的 commit,也可以写成 HEAD~1; 如果进行两次的commit,想要都撤回,可以使用HEAD~2
git reset --hard HEAD^ # 连着 add 也撤销 —— 删除工作空间的改动代码(在 README.md 中的改动完全消失)
git reset --mixed HEAD^ # 当前分支的指针会移动到上一次的提交,将之前已经添加到暂存区中的修改移除暂存区,但是这些修改并没有被删除,而是留在了工作区中等待重新提交

建议在删除分支时使用 --delete,而非 -D 强制删除。删除分支只会删除该分支指向的特定提交记录的指针,并不会删除这些提交记录本身。如果需要恢复已删除的分支,可以通过切换到该分支指向的提交记录来实现。在误删分支的情况下,通常需要 git reflog 来查找被删除的分支的历史记录。

直接键入 git diff 的情况较少,通常都会使用编辑器或者相关工具。Visual Studio Code 中查看 Git 的 diff 可以通过 Source Control 面板来实现。

  • git diff 比较工作区和暂存区之间的差异
  • git diff <commit> 比较工作区和某个提交之间的差异
  • git diff <commit1> <commit2> 比较两个提交之间的差异
  • git diff <branch1> <branch2> 比较两个分支之间的差异
  • git diff <commit1> <commit2> <path/to/file> 比较某个文件在两个提交之间的差异
  • git diff <dir1> <dir2> 比较两个目录之间的差异

上述 <commit> 表示一个 Git 提交对象的标识符,可以是提交的 SHA-1 值、分支名称、标签名称等。

Mysterious State

After detaching the HEAD by checking out a particular commit, it's allowing us to go to the previous point in the project’s history.

detached HEAD state 可以让开发者对项目历史中的某个节点进行 Experimental Changes,如果是不小心的误入,可以直接键入 git switch <branch-name>git checkout <branch-name> 离开。

git checkout 8c9588c5
Note: switching to '8c9588c5'.

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 8c9588c ctx

If we want to keep changes made with a detached HEAD, we just create a new branch and switch to it. We can create it right after arriving at a detached HEAD or after creating one or more commits.

如果在 detached HEAD state 有进行实验性的修改,且有保留这些操作的需求时,可以通过新分支来实现,即使用 git switch -c <new_branch_name>git checkout -b <new_branch_name>

使用上述命令的原因是 detached HEAD state 指向的是某个特定的提交,而不是任何分支。因此,为了保存这些更改,需要创建一个新的分支来指向这些提交,将这些更改保存下来。

Remote Repo

连接后,.git/config 中会出现额外的配置,如 [remote "origin"][branch "main"]。在 refs 以及 logs 中会增加 remotes 以及 origin 目录,对应的在 origin 里会出现 master or main 文件。

cat .git/refs/remotes/origin/maincat .git/refs/heads/main 可验证两者是相同的 commit 类型对象。即本地的 main 分支与远程的 main 分支为同步的。若在修改文件后再执行 commit 操作,则 main 分支会领先 origin 分支一次提交,此时通过 push 进行同步。

$ cat .git/refs/remotes/origin/main
8c9588c5965a18bd89ccc3f7006a03cb0a0a463b
$ git cat-file -t 8c9588
commit
$ cat .git/refs/heads/main
8c9588c5965a18bd89ccc3f7006a03cb0a0a463b

Object Compaction & Garbage Cleanup

在 Git 中,文件内容存储在 blob 对象,而 Git 对象压缩主要指 blob 对象的压缩。在执行 git add 操作后,Git 会将新增的文件保存为 blob 对象并将其压缩,从而使得 .git/objects 目录中对应的文件体积缩小。

但每一次通过 commit 修改内容后,Git 仍然会累积存储整个对象的压缩版本,因为每个 commit 都是一个新的对象,无法复用之前的 blob 对象。为了优化存储,Git 通过 git gc 命令对不再被引用的对象进行清理和压缩,从而生成 .pack.idx 两个文件,这些文件包含了压缩后的 Git 对象和索引信息,用于加速 Git 的读取和查询操作。在传输到远程仓库时,Git 会将这些压缩后的对象传输到本地,以提高传输效率。使用 unpack 命令可以将这些 pack 文件解压,但是需要注意不能将其放在 objects 目录下解压,以免与现有的对象发生冲突。

# 人类可读性查看文件夹空间使用
$ du -h
#  清理不必要的文件并优化本地存储库
$ git gc
$ tree .git/objects
.git/objects
├── info
│   ├── commit-graph
│   └── packs
└── pack
    ├── pack-643cfb32d355bf2f34006da363c59b5dfd6af348.idx
    └── pack-643cfb32d355bf2f34006da363c59b5dfd6af348.pack

2 directories, 4 files
$ ls -lh .git/objects/pack
total 44728
-r--r--r--  1 xzy  staff   155K Sep 18 00:18 pack-643cfb32d355bf2f34006da363c59b5dfd6af348.idx
-r--r--r--  1 xzy  staff    22M Sep 18 00:18 pack-643cfb32d355bf2f34006da363c59b5dfd6af348.pack
# 查看 pack 文件到底存储的对象详细信息
git verify-pack -v .git/objects/pack/pack-643cfb32d355bf2f34006da363c59b5dfd6af348.idx
.....
.....
# 解压从 github 下载的压缩文件
mv .git/objects/pack/pack-xxx.pack .git/
git unpack-objects < .git/pack-xxx.pack
du -sh .git/objetcs # ??G

文件的提交通常会产生三种对象,commit、tree 和 blob。但是多次修改会产生中间状态的 blob 对象残余,这些未被引用的对象就是垃圾对象,可使用 git gc 对其进行清理和压缩,以节省磁盘空间。

使用 git gc 生成 pack 文件夹后,垃圾对象会存留在 .git/objects 目录中,可通过 git prune 修剪残余。

这里存在一个特殊的场景:当一个分支提交了改动,未经过合并就被强制删除,那么就会留下未被引用的文件在 .git/objects 目录。这些垃圾对象只与被删除的分支相关,可以通过一些命令来找回删除的分支。但是,这些垃圾对象使用 git prune 是无法删除的,而且它们会被 git gc 压入 pack 文件中,这会占用更多的磁盘空间。此时可以参考一些解决方案,如使用 git reflog 命令来找回被删除的分支,或使用第三方工具如 BFG Repo-Cleaner 来清理未被引用的对象。SOF的解决方案

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 \
  -c gc.rerereresolved=0 -c gc.rerereunresolved=0 \
  -c gc.pruneExpire=now gc "$@"
...
├── eb
│   └── 5ba8320582830bc125255c614a99f783eee4e4
├── f4
│   └── c3a4fd871786674ad955a1346aff85deb21ece
├── f6
│   └── 2bc87aab0e26dcfad058bac0922df2cc018440
├── f7
│   └── 5048e6ee2f678a883ea01c30e5c9ef7e5e3fcc
├── fd
│   └── acdf72972e722927c6179a4a8dfc0386d1f0de
├── info
│   ├── commit-graph
│   └── packs
└── pack
    ├── pack-349bdde18259ef3a0141e73ecc82de3bd9dc4a25.idx
    └── pack-349bdde18259ef3a0141e73ecc82de3bd9dc4a25.pack

56 directories, 66 files
$ git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 \
  -c gc.rerereresolved=0 -c gc.rerereunresolved=0 \
  -c gc.pruneExpire=now gc "$@"
Enumerating objects: 4402, done.
Counting objects: 100% (4402/4402), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3260/3260), done.
Writing objects: 100% (4402/4402), done.
Total 4402 (delta 901), reused 4402 (delta 901), pack-reused 0
$ tree .git/objects
.git/objects
├── info
│   ├── commit-graph
│   └── packs
└── pack
    ├── pack-349bdde18259ef3a0141e73ecc82de3bd9dc4a25.idx
    └── pack-349bdde18259ef3a0141e73ecc82de3bd9dc4a25.pack

2 directories, 4 files

Hooks

在 Git Hooks 中,示例 Hook 脚本的文件名一般以 .sample 结尾,这是为了方便用户自定义脚本而提供的示例文件。当需要使用这些示例 Hook 脚本时,应将其复制到对应的 Git Hooks 目录下,并移除文件名中的 .sample 后缀,使其成为可执行的 Hook 脚本。

举个例子,假设需要使用示例的 pre-commit.sample Hook 脚本。可以执行以下命令将其复制到项目的 .git/hooks/ 目录下,并移除文件名中的 .sample 后缀。

cp .git/hooks/pre-commit.sample .git/hooks/pre-commit

注意,应该保证 pre-commit 文件是可执行的,即可执行 chmod +x .git/hooks/pre-commit

pre-commit是一个可以帮助 JavaScript 生成提交前测试的开发包。

Tagging

标签是用来给某个特定的提交打上标记的一个引用,通常被用来标记某个版本的代码,便于在未来可以快速地找到某个特定的版本。标签分为两种类型:Lightweight Tag 和 Annotated Tag。

轻量标签是一个指向某个提交的引用,本质上就是一个指向某个 commit SHA-1 值的指针,并不包含额外的信息。

git tag <tag_name> <commit_SHA>

附注标签是一个包含额外信息的标签,除了指向某个提交,还包含了标签的创建者、创建时间、标签说明等信息。

git tag -a <tag_name> -m "<tag_message>" <commit_SHA>

Branch Management

Merge Strategies in Git

Fast forward merge can be performed when there is a direct linear path from the source branch to the target branch.

快进合并通常是指从当前分支到目标分支存在一个直线路径的情况。在这种情况下,若想要将当前分支合并到目标分支,Git 会自动将当前分支的 HEAD 指向目标分支的最新提交。快进合并不会产生额外的提交,也不会更改分支历史记录中的顺序。

fast-forward

$ git checkout master && git merge <feature>

默认情况下 Git 的 merge 命令是不加选项的,既可以进行快进合并,也可以进行非快进合并。在使用 --ff 选项进行快进合并时,会直接丢失被合并分支的分支信息。

如果想保留被合并分支的分支信息,可以考虑使用 No-fast-foward --no-ff 选项进行普通合并。--no-ff 不等同于三方合并,而是意味着无论源分支的历史记录是怎样的,Git 都将创建一个新的合并提交,以保留源分支和目标分支之间的区别。这样可以在合并后的提交中记录分支的历史信息。如果当前分支包含着其他分支没有的提交时,git merge 默认会使用 --no-ff 合并选项。

In Recursive merge, after you branch and make some commits, there're some new original commits on the 'master' or 'main'. When it is time to merge, git recurses over the branch and creates a new merge commit.

递归合并是 Git 在进行多个分支合并时所采用的默认策略。首先执行两个分支的 3-way merge 操作,然后再将合并后的结果与另外一个分支进行 3-way merge 操作,依此类推,直到合并所有的分支。

Three Way Merge 的实现基于三次不同的提交,即分支的共同祖先、Master / Main 分支与 Feature 分支。在找到这三个版本的最近共同祖先,然后将这三个版本的差异应用于最近共同祖先上,生成一个新的合并结果。这种策略可以避免因简单地将两个分支的代码直接合并而导致的冲突与错误。

3-way-merge

如上图所示,共同祖先是 C3,即分支产生前的代码,Master 分支是在该分支上最后一次执行提交操作的 C6,Feature 分支是在该分支上最后一次执行提交的 C5。最后,Git 会创建新提交来合并更改。

注意,Merge Conflict 问题通常会出现在 Three Way Merge 的过程中,此时需要手动解决冲突。

Rebasing is a process to reapply commits on top of another base trip. It is used to apply a sequence of commits from distinct branches into a final commit.

在使用 git merge 合并分支时,会产生一个新的合并提交,并将源分支的历史记录保留在一个单独的分支上。合并提交保留了分支的完整历史记录,但是会产生导致分支历史变得复杂。

相比之下,git rebase 会将源分支上的每个提交重新设置到目标分支的最新提交之后,从而形成一条更线性的提交历史。这种合并方式被称为变基。变基可以保持提交历史的线性,并使得分支历史变得更加清晰。

在通过 git mergegit rebase 命令进行分支合并时,会对合并时的冲突解决策略进行指定:

  • ours 选择当前分支上的更改,而忽略其他分支上的更改
  • theirs 选择其他分支上的更改,而忽略当前分支上的更改
  • xours 发生冲突时选择当前分支上的更改;不冲突时仍然选择其他分支上的更改
  • xtheirs 发生冲突时选择其他分支上的更改,不冲突的情况下仍然选择当前分支上的更改

例如,命令 git merge -s ours 会忽略其他分支的更改,直接使用当前分支的内容作为合并结果。

Octopus Merge strategy resolves cases with more than two heads but refuses to do a complex merge that needs manual resolution. It is primarily meant to be used for bundling topic branch heads together. This is one of the merge strategies that Git can use when pulling or merging more than one branch.

Octopus Merge 策略用于合并包含两个以上头的情况,但是不会做需要手动解决的复杂合并。主要用于将主题分支头捆绑在一起。这是 Git 在拉取或合并多个分支时可以使用的合并策略之一。

$ git merge -s octopus

当需要将三个或以上的分支合并到一个分支中时,就可以使用该策略。比如现在有三个分支,每个分支都有不同的修改,并想要将这些修改合并到一个新的分支中。

git checkout -b new_branch
git merge A B C

上述代码将创建一个新分支 new_branch 并使用 Octopus Merge 策略将 A、B 和 C 分支合并到新分支中。如果存在冲突,Git 会暂停合并,等待手动解决冲突。

使用 git addgit commit 提交解决方案后,Git 会自动创建一个新提交包含所有分支的修改。

Resolve strategy can only resolve two heads (i.e. the current branch and another branch you pulled from) using a 3-way merge algorithm. It tries to carefully detect criss-cross merge ambiguities and is considered generally safe and fast."

$ git merge -s resolve

Resolve 策略是 git merge 的默认策略。这种自动合并使用 Three-Way Merge 算法来尝试着将两个分支的更改合并到一起。如果有冲突,则需要手动解决。

Subtree is a modified recursive strategy. When merging trees A and B, if B corresponds to a subtree of A, B is first adjusted to match the tree structure of A, instead of reading the trees at the same level. This adjustment is also done to the common ancestor tree.

当想要将一个子项目的仓库合并到父项目的仓库中时,可以使用 Subtree Merge 策略。

例如,有两个仓库,一个是父项目,一个是子项目。子项目中包含着一些可以被父项目重用的代码或文件。如果想要将子项目的内容合并到父项目,那么可以使用 Subtree Merge 策略。

进入父项目的目录后执行以下命令,父项目的仓库中会包含一个指向子项目仓库的子模块,并且该子模块会在父项目的仓库中被记录下来,以便其他人在克隆父项目的仓库时也能同时克隆子项目的仓库。

git submodule add <子项目仓库的 URL> <子模块的存放路径>
git add .
git commit -m "Add child submodule"

那么在执行以下命令后,可以将子项目的一个特定分支合并到父项目的当前分支中,并将子项目的历史记录合并成一个新的提交。这个新的提交包含了子项目的全部内容,但是不保留子项目的历史记录。最终,父项目的仓库中将包含子项目的内容,并且可以像使用普通的父项目的代码一样使用它们。

git merge -s subtree --squash <branch>

Fetch & Pull

The git pull command fetches and downloads content from the remote repo and integrates changes into the local repository.

The git fetch is a primary command used to download contents from a remote repository.

git pull 等价于执行 git fetch 后立即键入 git merge 将远程分支的更新合并到当前分支。这个操作会自动合并远程分支的更新,但是如果合并有冲突,需要手动解决冲突。

git fetch 命令用于从远程仓库下载最新的提交、分支等,并将其保存在本地仓库中,但并不会自动与当前工作分支进行合并。

.git/FETCH_HEAD 记录了在最近一次运行 git fetch 时,Git 从远程仓库获取到的所有分支的最新提交对象。在 .git/FETCH_HEAD 中,每个分支都有对应的一条记录。其中,第一条记录往往是 git fetch 时指定远程仓库的某个分支的最新 commit 对象。

如果切换分支后没有重新运行 git fetch 命令,那么 .git/FETCH_HEAD 中的记录不会发生变化。

当远程仓库中的分支发生了改变,却没有再重新运行 git fetch,那么 .git/FETCH_HEAD 中的记录有可能已经过期。

Push Tip

git push -u 用于将本地分支的代码推送到远程仓库,并设置本地分支与远程分支的关联。-u--set-upstream 表示设置本地分支与远程分支的关联,即在推送代码的同时,建立本地分支与远程分支之间的链接。

如果本地仓库的分支已经与远程仓库中的分支建立了关联,那么在推送时可以直接使用 git push

如果本地仓库中有新的分支,且这些分支还没有和远程仓库中的分支建立关联,那么直接使用 git push 会出现错误。

此时需要使用 git push --set-upstream 命令并指定要关联的远程分支,这样本地分支就会与指定的远程分支建立起关联,且以后在推送时就可以直接使用 git push

Cherry Pick

The cherry-pick command enables arbitrary Git commits to be picked by reference and appended to the current working HEAD.

挑选提交会尝试将指定提交应用在当前所处的分支,并生成一条新的提交,包含原提交的修改内容。若该过程中出现冲突,需要手动解决冲突并键入 git cherry-pick --continue 以继续应用提交。如果需要取消操作,可以运行 git cherry-pick --abort 命令。

注意,挑选提交会生成一条新的提交,并且会在提交历史中保留原提交的作者、提交时间等信息,但是不会保留原提交的哈希值。这意味着,通过 git cherry-pick 复制的提交,其历史记录与原提交不同,但其包含的修改内容是相同的。

简单来说,假设在分支 A 上开发了某个功能,然后在分支 B 上也需要用到这个功能,那么可以使用 git cherry-pick 命令将 A 分支上的这个提交应用到 B 分支上。在解决可能出现的冲突后,会创建一个新的提交,这个提交会包含 A 分支上的提交所做的更改,同时也会保留 B 分支的作者和提交信息。这样就可以在 B 分支上复用 A 分支上的功能,同时保留 B 分支的提交历史和作者信息。

git cherry-pick <commit-hash>
git cherry-pick <commit-hash-1> <commit-hash-2> ...

Bugs Fix

  1. Unable to create './.../.git/index.lock': File exists.
fatal: Unable to create './.../.git/index.lock': File exists.
Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again. If it still fails, a git process
may have crashed in this repository earlier:
remove the file manually to continue.

此类问题一般发生在 git 同时执行两个命令时;也许一个来自命令提示符,一个来自 IDE。通过删除 .git文件中的 index.lock 解决。SOF

rm -f .git/index.lock
  1. refs/remotes/origin 没有 ref to master? SOF

并非所有引用都必须在文件中。引用可以并且已经 "打包"。目前打包的引用可以在纯文本文件 .git/packed-refs 中找到。Git 将 refs/remotes/origin/master 在 .git/packed-refs. 上。

  1. fatal: refusing to merge unrelated histories

由于两个分支没有取得关联,在需要合并操作后加上 --allow-unrelated-histories。

git merge master --allow-unrelated-histories
git pull origin master --allow-unrelated-histories
  1. fatal: Authentication failed for 'https://github.com/...'

使用 git push --force 时出现授权失败的问题。原因是从 2021-08-14 之后,使用 git 对 github 进行身份验证操作不再接受使用账号密码形式 clone 和 push 代码,而是需要 acces_token 进行验证。在更新 acces_token 后由于提交验证的重置,使用 git push 的方式进行提交验证身份。

Git - Book
Git Book
🌳🚀 CS Visualized: Useful Git Commands
Although Git is a very powerful tool, I think most people would agree when I say it can also be... a...
Lydia Hallie

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!