- 所有文章/
Git 版本控制学习笔记(二)
本文目录
准备 #
在本文中,我们使用 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 fetch
和 git pull
都是用于从远程 Git 仓库中获取更新并合并到本地代码库的命令。虽然它们都可以获取远程仓库中的最新代码,但它们的行为和用途有所不同。
具体来说,git fetch
命令会将远程 Git 仓库中的所有更新都下载到本地仓库中,但并不会自动合并这些更新到本地分支中。因此,如果你想查看远程仓库中的最新更新,或者想在本地工作的分支中手动合并远程分支,可以使用 git fetch
命令来获取更新。
另一方面,git pull
命令会自动将远程分支中的最新更新合并到当前本地分支中,相当于在 git fetch
命令之后自动执行了 git merge
命令。因此,如果你只是想快速地将最新更新合并到本地分支中,可以使用 git pull
命令。
总之,git fetch
和 git 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.html
将feature-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