惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
有赞技术团队
有赞技术团队
Jina AI
Jina AI
H
Help Net Security
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Blog — PlanetScale
Blog — PlanetScale
Hugging Face - Blog
Hugging Face - Blog
罗磊的独立博客
MyScale Blog
MyScale Blog
N
Netflix TechBlog - Medium
B
Blog RSS Feed
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
T
The Blog of Author Tim Ferriss
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
MongoDB | Blog
MongoDB | Blog
美团技术团队
M
MIT News - Artificial intelligence
阮一峰的网络日志
阮一峰的网络日志
博客园 - 司徒正美
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家

Dejavu's Blog

甲骨文 ARM 实例部署 Gemma 4 模型 Headscale + Tailscale 组建虚拟专用网 在 Linux 上使用 Yubikey OpenPGP 应用 BuyVM VPS 块存储挂载教程 Alpine Linux 服务器配置指南 Alpine Linux 安装 Cloudflared Docker 多容器共享中心数据库 安装 Komari 服务器监控工具 Scaleway VPS 安装 Debian Linux Debian 13 下部署 AsmBB 论坛 使用 Kopia 自动化备份服务器数据 给 Docker 启用 IPv6 支持 Netcup 服务器安装自定义 ISO 镜像 烽火 HG5582A 光猫开启桥接模式 Docker 自托管 Shlink 短链服务 部署 Obsidian LiveSync 实时同步服务指南 我的 2025 年不完全回顾 Linux 下 Intel 核显驱动配置与硬件加速 Fedora Linux 安装配置记录 2025 年优雅地自托管 RSS 服务 Woodpecker CI 和 Gitea 实现 Hugo 自动部署 Gitea/Forgejo 集成 Woodpecker CI/CD 在 Blinko 中使用 Ollama 作为 AI 供应商 Docker 部署 Gitea/Forgejo Plausible CE 启用城市级地理位置识别 Blinko 开源 AI 知识库 Docker 部署指南 Netcup 免税账号注册及购买服务器全记录 Docker 自托管 Cloudreve Pro 私有网盘服务 GiffGaff SIM 卡使用体验和注意事项 简体中文互联网在变得糟糕吗?
Git 版本控制学习笔记(二)
Dejavu Moe · 2023-02-25 · via Dejavu's Blog

准备

在本文中,我们使用 GitHub 作为远程仓库服务器,先创建一个新的 Git 远程仓库,我们的 Git 之旅从这里开始。本文记录过程中,创建的远程仓库: MoeOffice/Git-Travel

Git 中的命令和参数繁多且灵活,不可能全部都记得,本文在 ChatGPT 的帮助下完成,作为方便自己日后查阅的 Cheat Sheet

git init 初始化

在本地初始化一个 Git 仓库

mkdir Git-Travel
cd Git-Travel
git init
# 输出
Initialized empty Git repository in /Users/dejavu/MoeOffice/Git-Travel/.git/

git remote 远程仓库

git remote 命令用于管理本地 Git 仓库与远程仓库之间的连接

add 添加

添加远程仓库,用法

git remote add <name> <url>

# 比如
git remote add origin [email protected]:MoeOffice/Git-Travel.git

注意: origin是默认的远程仓库名称,通常是指 Git 仓库的主机或者托管平台。当你使用 git clone 命令从一个远程 Git 仓库中克隆代码库时,Git 会自动为你设置一个名为 origin 的远程仓库,以便在将来能够与原始代码库进行交互。

通常情况下,origin 是指向远程代码仓库的 URL 地址,通过它可以访问和操作远程代码库。在进行 Git 操作时,你可以使用 origin 来指代远程代码库,例如 git push origin master 表示将本地分支的变更推送到远程仓库 origin 上的 master 分支。

需要注意的是,origin 只是一个默认的远程仓库名称,你可以使用其他名称来代替它。当你需要与多个远程仓库交互时,可以使用不同的名称来标识它们,例如 git remote add upstream <url> 可以将一个新的远程仓库添加到本地仓库中,并指定它的名称为 upstream

remove 删除

从本地 Git 仓库删除远程仓库

git remote remove <name>

# 比如
git remote remove origin

# 现在查看信息,应该输出为空
git remote -v

为了继续学习,我们将远程仓库添加回来

rename 重命名

重命名远程仓库

git remote rename <old-name> <new-name>

# 比如
git remote rename origin upstream

# 现在查看信息
git remote -v
upstream	[email protected]:MoeOffice/Git-Travel.git (fetch)
upstream	[email protected]:MoeOffice/Git-Travel.git (push)

为了继续学习,我们将远程仓库命名改回来

git remote rename upstream origin

git pull 拉取

我在远程仓库做了一些更改,现在我想让本地仓库同步这些更改,用法

git pull <remote> <branch>

# 比如
git pull origin master
From github.com:MoeOffice/Git-Travel
 * branch            master     -> FETCH_HEAD

git push 推送

我在本地仓库做了一些更改

# 先提交更改
git add . 
git commit -m 'add push.txt'
[master f6e02ff] add push.txt
 1 file changed, 1 insertion(+)
 create mode 100644 push.txt

现在要把本地仓库的更改推送到远程仓库用法

git push <remote> <branch>

# 比如
git push origin master
# 输出
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 467 bytes | 467.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:MoeOffice/Git-Travel.git
   c8fa602..f6e02ff  master -> master

使用 git push 删除远程仓库

# 方式一:推送删除远程仓库
git push origin --delete need-deleted
# 输出
To github.com:MoeOffice/Git-Travel.git
 - [deleted]         need-deleted
 
# 方式二:推送一个空分支到远程仓库
git push origin :another-need-deleted
# 输出
To github.com:MoeOffice/Git-Travel.git
 - [deleted]         another-need-deleted

git fetch 获取

现在有其他协作人员修改了远程仓库(新的代码提交、新的分支或标签),这时候我就可以从远程仓库获取最新的信息(但不会自动合并到本地分支),用法

git fetch <remote>

# 比如
git fetch origin master

# 或者直接 git fetch (因为我们现在只有一个远程仓库)
git fetch
# 输出
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 582 bytes | 194.00 KiB/s, done.
From github.com:MoeOffice/Git-Travel
 * [new branch]      master     -> origin/master

git fetch -p 获取远程仓库最新信息,并执行「修剪」操作,比如删除已经不存在的远程分支引用,下面是个例子

# 模拟情况
# 新建一个分支
git checkout -b push/need-p 
# 推送分支
git push
# 输出
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'push/need-p' on GitHub by visiting:
remote:      https:#github.com/MoeOffice/Git-Travel/pull/new/push/need-p
remote:
To github.com:MoeOffice/Git-Travel.git
 * [new branch]      push/need-p -> push/need-p
branch 'push/need-p' set up to track 'origin/push/need-p'.

# 切换回主分支 master
git checkout master

现在我们在远程仓库删除这个分支 push/need-p,查看现在的本地分支

git branch -a

# 输出
  dev
* master
  new-branch
  push/need-p	# 注意这里
  remotes/origin/dev
  remotes/origin/master
  remotes/origin/new-branch
  remotes/origin/push/need-p	# 注意这里

注意上面输出信息,虽然仓库已经删除了 push/need-p 分支,但是本地仓库现在还不知道,仍然有对该分支的远程引用

# 如果直接使用 git fetch
git fetch
git branch -a
# 输出
  dev
* master
  new-branch
  push/need-p
  remotes/origin/dev
  remotes/origin/master
  remotes/origin/new-branch
  remotes/origin/push/need-p

可以看到并没有删除对已经删除分支的远程引用,这时候我们就需要 git fetch -p

git fetch -p
# 输出
From github.com:MoeOffice/Git-Travel
 - [deleted]         (none)     -> origin/push/need-p

pull vs fetch

git pull 和 git push 似乎有点让人迷惑,他们有什么不同呢?我们可以理解为:git pull = git fetch + merge

git fetchgit pull 都是用于从远程 Git 仓库中获取更新并合并到本地代码库的命令。虽然它们都可以获取远程仓库中的最新代码,但它们的行为和用途有所不同。

具体来说,git fetch 命令会将远程 Git 仓库中的所有更新都下载到本地仓库中,但并不会自动合并这些更新到本地分支中。因此,如果你想查看远程仓库中的最新更新,或者想在本地工作的分支中手动合并远程分支,可以使用 git fetch 命令来获取更新。

另一方面,git pull 命令会自动将远程分支中的最新更新合并到当前本地分支中,相当于在 git fetch 命令之后自动执行了 git merge 命令。因此,如果你只是想快速地将最新更新合并到本地分支中,可以使用 git pull 命令。

总之,git fetchgit pull 的区别在于:

  • git fetch 只是将远程分支的最新更新下载到本地,不会自动合并到当前本地分支中,需要手动执行合并命令。
  • git pull 则会自动将远程分支中的最新更新合并到当前本地分支中,相当于在 git fetch 命令之后自动执行了 git merge 命令。

通常情况下,建议先使用 git fetch 命令查看远程仓库的最新更新,然后再根据需要手动执行合并命令或者使用 git pull 命令快速合并更新。这样可以更好地控制代码库的状态和变更历史。

git checkout 切换

git checkout 是 Git 命令中的一个非常常用的命令,主要用于在不同的分支、标签和提交之间进行切换。常见的用法:

  • git checkout <branch>:切换到名为 <branch> 的分支。例如 git checkout develop 切换到名为 develop 的分支
  • git checkout -b <new-branch>:创建一个新的名为 <new-branch> 的分支,并切换到该分支。例如 git checkout -b feature-branch 创建一个名为 feature-branch 的新分支,并切换到该分支
  • git checkout <commit>:切换到指定的提交,其中 <commit> 可以是提交哈希值或分支名称。例如 git checkout a1b2c3d4 将切换到哈希值为 a1b2c3d4 的提交
  • git checkout <tag>:切换到指定的标签。例如 git checkout v1.0.0 切换到 v1.0.0 标签
  • git checkout -- <file>:将指定的文件恢复到上一次提交的状态。例如 git checkout -- index.html 将 index.html 文件恢复到上一次提交的状态(放弃修改)
  • git checkout <branch> -- <file>:将指定分支中的文件复制到当前分支中。例如 git checkout feature-branch -- index.htmlfeature-branch 分支中的 index.html 文件复制到当前分支中
# 比如
git checkout -b new-branch
Switched to a new branch 'new-branch'

接下来就可以对新分支做一些提交了

git branch 分支管理

git branch 分支管理是 Git 工作流中很重要的功能,基本操作

  • git branch:列出本地所有分支,当前分支前面会有一个星号 (*)。
  • git branch -r:列出远程仓库的所有分支。
  • git branch -a:列出本地和远程仓库的所有分支。
  • git branch <branchname>:创建一个名为 branchname 的新分支。
  • git branch -d <branchname>:删除名为 branchname 的分支。分支必须先被合并到其他分支才能被删除。
  • git branch -D <branchname>:强制删除名为 branchname 的分支,即使该分支没有被合并。
  • git branch -m <newbranchname>:重命名当前所在的分支为 newbranchname
  • git branch -f <branchname> <commit>:将 branchname 分支指向指定的 commit
  • git branch --set-upstream-to=<remote>/<branchname>:设置本地分支跟踪远程分支。
  • git branch --merged:列出已经被合并到当前分支的分支。
  • git branch --no-merged:列出还没有被合并到当前分支的分支。

git tag 标签管理

git tag 命令用于给 Git 仓库中的某个提交打标签,可以理解为 Git 版本库的快照。通过给某个重要的提交打上一个有意义的标记,以便在后续查找和管理。

Git 中的标签分为两种:

  • 轻量标签(lightweight tag): 指向某个提交的引用,没有附加信息,它只是指向该提交的指针
  • 附注标签(annotated tag):一个独立的 Git 对象,它有自己的校验和,包含标签名称、标签创建者、标签日期、标签注释等附加信息
# 创建一个轻量标签
git tag v1.0.0

# 创建一个附注标签
git tag -a v1.0.0 -m "First version released" 123b45b1

在 GitHub 上,如果我们要发布一个 Releases,必须要指定一个 Tag,这个 Tag 一般是指向 GitHub Releases 的轻量标签。简单实践:

# 创建一个轻量标签
# 附加 -s 选项进行签名
git tag -s 0.1.1

# 查看这个标签的信息
git show 0.1.1
# 或者
git tag -v 0.1.1
# 输出
object f64b9334924167684e7a0689767249592771c59a
type commit
tag 0.1.1
tagger DejavuMoe <[email protected]> 1677308704 +0800

new tag 0.1.1
gpg: Signature made Sat Feb 25 15:05:20 2023 CST
gpg:                using EDDSA key D513573ED5AC8495FE0E47788422222222222222
gpg: Good signature from "Dejavu Moe <[email protected]>" [ultimate]

# 将标签推送到远程仓库
# 推送所有标签
git push origin --tags 
# 或者推送指定标签号
git push origin 0.1.1
# 输出
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 323 bytes | 323.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:MoeOffice/Git-Travel.git
 * [new tag]         0.1.1 -> 0.1.1

# 删除指定标签
git tag -d <tagname>
# 删除远程仓库指定标签
git push origin :refs/tags/<tagname>

仓库有其他协作者发布了新的标签,我们可以获取指定标签

git fetch origin <tagname>
// 比如
git fetch origin 0.1.2

git reset 回退

git reset 用于取消已经提交的变更、移动 HEAD 指针、重置暂存区和工作目录等操作,用法:

  • git reset --mixed <commit>:将 HEAD 指针和索引(暂存区)都移动到指定的提交,同时撤销当前提交之后的所有变更,但是保留工作目录中的修改,这是默认的操作模式。例如 git reset --mixed HEAD~2 可以撤销最近的两次提交并保留修改。
  • git reset --soft <commit>:将 HEAD 指针移动到指定的提交,但不会改变索引和工作目录,也不会撤销提交。这个模式常用于撤销一次提交,但是仍然希望保留提交所做的变更,以便在之后再次提交。例如 git reset --soft HEAD~1 可以将 HEAD 指向上一个提交,并将上一个提交的变更保留在暂存区中,以便在之后再次提交。
  • git reset --hard <commit>:将 HEAD 指针、索引和工作目录都移动到指定的提交,同时完全撤销当前提交之后的所有变更,这个模式 非常危险,因为它会丢失未提交的变更。只有在确定要放弃所有未提交的变更时才应该使用这个模式。例如 git reset --hard HEAD~3 可以将 HEAD 指向三次提交之前的状态,并完全撤销后续的所有变更。

git revert 撤销

git revert 命令可以用于撤销一个或多个提交,同时创建一个新的提交来保存撤销操作,这样可以 保留 原有的提交 历史记录,并在之后的开发中进行修改和调整。用法

git revert <commit>

# 同时撤销多个提交
git revert <commit1> <commit2> ...

# 撤销最近的提交
git revert HEAD

# 撤销某一段提交
git revert <start>..<end>

git merge 合并

将一个分支的更改合并到另一个分支,这是 Git 中最常用的操作之一。用法

  • git merge <branch>:将名为 <branch> 的分支合并到当前分支。例如 git merge feature-branch 将把 feature-branch 分支中的修改合并到当前分支中。
  • git merge --no-ff <branch>:执行不快进合并(non-fast-forward merge),将名为 <branch> 的分支合并到当前分支,同时创建一个新的合并提交。例如 git merge --no-ff feature-branch 将把 feature-branch 分支中的修改合并到当前分支中,并创建一个新的合并提交。
  • git merge <commit>:将指定的提交合并到当前分支。例如 git merge a1b2c3d4 将把提交哈希值为 a1b2c3d4 的提交合并到当前分支中。
  • git merge --abort:取消当前的合并操作,并返回到合并之前的状态。
# 比如
git checkout master
git merge new-branch
# 输出
Updating f6e02ff..0f2009a
Fast-forward
 new-branch.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 new-branch.txt

git base 重放

git rebase 是 Git 命令中的一个常用命令,用于将一个分支的修改合并到另一个分支中。与 git merge 命令不同,git rebase 命令会将本地未提交的修改「移动」到目标分支的最新提交之上。下面是一些常见的用法:

  • git rebase <branch>:将当前分支的修改“移动”到名为 <branch> 的分支的最新提交之上。例如 git rebase master 将当前分支的修改“移动”到 master 分支的最新提交之上。
  • git rebase -i <commit>:以交互模式(interactive mode)执行 rebase,可以让开发者自定义合并策略。例如 git rebase -i HEAD~3 将进入交互模式,让开发者自定义合并策略。
  • git rebase --continue:在解决完冲突后继续执行 rebase 操作。
  • git rebase --abort:取消当前的 rebase 操作,并回到 rebase 操作之前的状态

现在我创建了一个新的分支 dev,并在上面做了一些提交,然后将 dev 合并到 master 分支

git checkout master
git merge dev

# 输出
Updating 0f2009a..f64b933
Fast-forward
 dev1.txt | 0
 dev2.txt | 0
 dev3.txt | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 dev1.txt
 create mode 100644 dev2.txt
 create mode 100644 dev3.txt

现在我想让 dev 分支的提交基于 master 分支进行重放(使用交互模式)

# 将最近三次的提交合并为一次
git rebase -i HEAD~3

在交互模式下,我们可以看到可以使用的操作

pick 56a630f Add dev1.txt
pick 306483f Add dev2.txt
pick f64b933 Add dev3.txt

# Rebase 0f2009a..f64b933 onto 0f2009a (3 commands)
#
# 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
#
# 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.

编辑完成后

Successfully rebased and updated refs/heads/master.

git stash 贮藏

假如现在我们在某个分支做了一些工作(但是还没完成,暂时不能 push),这时候这个分支急需进行操作(比如 rebase)。

git stash 命令用于将工作区中的修改暂时贮藏起来,以便稍后恢复。这在需要切换分支或者打补丁时非常有用,用法:

  • git stash save "message":保存当前工作目录中的修改,并添加一条描述信息,以便在后续的工作中更好地区分每个 stash。例如 git stash save "Save unfinished work"
  • git stash list:列出所有已保存的贮藏操作
$ git stash list
stash@{0}: Save unfinished work
stash@{1}: WIP on feature-branch: 1c53f7f Add new feature
  • git stash show stash@{n}:查看指定 stash 的详细信息,例如 git stash show stash@{0}
  • git stash apply stash@{n}:将指定 stash 中的修改应用到当前分支上,这会将 stash 中的修改应用到当前分支上,并将这些修改从 stash 中删除。例如 git stash apply stash@{0}
  • git stash pop:将最新的 stash 中的修改应用到当前分支上,这会将最新的 stash 中的修改应用到当前分支上,并将这些修改从 stash 中删除。例如 git stash pop
  • git stash drop stash@{n}:放弃(删除)指定 stash 中的修改。例如 git stash drop stash@{1}
  • git stash clear:清空所有的 stash

注意 恢复 stash 贮藏操作,如果与当前工作目录暂存区的修改冲突,也要先修改冲突才能再进行 stash 应用的相关操作。

git cherry-pick 挑选提交

我们知道,Git 的每次提交(commit)都会生成一个唯一的哈希值。git cherry-pick 命令可以将某个提交的更改应用到当前分支中(而无需将整个分支合并过来),并自动生成一个新的提交,这个新的提交包含了指定提交的更改,用法:

注意 git cherry-pick 会自动解决冲突。

git reflog 回溯

我感觉将 git reflog 称为「回溯」应该还是挺形象的,它用于查看本地仓库历史操作记录的命令。在平时的 Git 操作中,除了 git log 查看提交日志,用的比较多的就是 git reflog

f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{0}: checkout: moving from push/need-p to master
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{1}: checkout: moving from master to push/need-p
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{2}: checkout: moving from dev to master
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{3}: checkout: moving from new-branch to dev
0f2009a (origin/new-branch, new-branch) HEAD@{4}: checkout: moving from master to new-branch
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{5}: rebase (finish): returning to refs/heads/master
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{6}: rebase (start): checkout HEAD~3
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{7}: rebase (abort): updating HEAD
0f2009a (origin/new-branch, new-branch) HEAD@{8}: rebase (start): checkout HEAD~3
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{9}: merge dev: Fast-forward
0f2009a (origin/new-branch, new-branch) HEAD@{10}: checkout: moving from dev to master
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{11}: checkout: moving from master to dev
0f2009a (origin/new-branch, new-branch) HEAD@{12}: checkout: moving from dev to master
f64b933 (HEAD -> master, origin/master, origin/dev, dev) HEAD@{13}: commit: Add dev3.txt
306483f HEAD@{14}: commit: Add dev2.txt
56a630f HEAD@{15}: commit: Add dev1.txt
0f2009a (origin/new-branch, new-branch) HEAD@{16}: checkout: moving from master to dev
0f2009a (origin/new-branch, new-branch) HEAD@{17}: merge new-branch: Fast-forward
f6e02ff HEAD@{18}: checkout: moving from new-branch to master
0f2009a (origin/new-branch, new-branch) HEAD@{19}: commit: Create a new branch
f6e02ff HEAD@{20}: checkout: moving from master to new-branch
f6e02ff HEAD@{21}: commit: add push.txt
c8fa602 HEAD@{22}: initial pull