跳到主要内容
  1. 所有文章/

Git 版本控制学习笔记(二)

·5746 字·约 12 分钟

准备 #

在本文中,我们使用 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 git@github.com: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

# 查看远程仓库信息
git remote -v

# 示例输出
origin	git@github.com:MoeOffice/Git-Travel.git (fetch)
origin	git@github.com:MoeOffice/Git-Travel.git (push)

remove 删除 #

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

git remote remove <name>

# 比如
git remote remove origin

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

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

git remote add origin git@github.com:MoeOffice/Git-Travel.git

rename 重命名 #

重命名远程仓库

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

# 比如
git remote rename origin upstream

# 现在查看信息
git remote -v
upstream	git@github.com:MoeOffice/Git-Travel.git (fetch)
upstream	git@github.com: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 <admin@dejavu.moe> 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 <admin@dejavu.moe>" [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 <commit>

注意 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
Dejavu Moe
作者
Dejavu Moe
Not for success, just for growing.