在本文中,我们使用 GitHub 作为远程仓库服务器,先创建一个新的 Git 远程仓库,我们的 Git 之旅从这里开始。本文记录过程中,创建的远程仓库: MoeOffice/Git-Travel
Git 中的命令和参数繁多且灵活,不可能全部都记得,本文在 ChatGPT 的帮助下完成,作为方便自己日后查阅的 Cheat Sheet
在本地初始化一个 Git 仓库
mkdir Git-Travel
cd Git-Travel
git init
# 输出
Initialized empty Git repository in /Users/dejavu/MoeOffice/Git-Travel/.git/
git remote 命令用于管理本地 Git 仓库与远程仓库之间的连接
添加远程仓库,用法
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。
从本地 Git 仓库删除远程仓库
git remote remove <name>
# 比如
git remote remove origin
# 现在查看信息,应该输出为空
git remote -v
为了继续学习,我们将远程仓库添加回来
重命名远程仓库
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 <remote> <branch>
# 比如
git pull origin master
From github.com:MoeOffice/Git-Travel
* branch master -> FETCH_HEAD
我在本地仓库做了一些更改
# 先提交更改
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 <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
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 命令中的一个非常常用的命令,主要用于在不同的分支、标签和提交之间进行切换。常见的用法:
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 工作流中很重要的功能,基本操作
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 仓库中的某个提交打标签,可以理解为 Git 版本库的快照。通过给某个重要的提交打上一个有意义的标记,以便在后续查找和管理。
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 用于取消已经提交的变更、移动 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 <commit>
# 同时撤销多个提交
git revert <commit1> <commit2> ...
# 撤销最近的提交
git revert HEAD
# 撤销某一段提交
git revert <start>..<end>
将一个分支的更改合并到另一个分支,这是 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 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.
假如现在我们在某个分支做了一些工作(但是还没完成,暂时不能 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 popgit stash drop stash@{n}:放弃(删除)指定 stash 中的修改。例如 git stash drop stash@{1}git stash clear:清空所有的 stash注意 恢复 stash 贮藏操作,如果与当前工作目录暂存区的修改冲突,也要先修改冲突才能再进行 stash 应用的相关操作。
我们知道,Git 的每次提交(commit)都会生成一个唯一的哈希值。git cherry-pick 命令可以将某个提交的更改应用到当前分支中(而无需将整个分支合并过来),并自动生成一个新的提交,这个新的提交包含了指定提交的更改,用法:
git cherry-pick <commit>
注意 git cherry-pick 会自动解决冲突。
我感觉将 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
在本文中,我们使用 GitHub 作为远程仓库服务器,先创建一个新的 Git 远程仓库,我们的 Git 之旅从这里开始。本文记录过程中,创建的远程仓库: MoeOffice/Git-Travel
Git 中的命令和参数繁多且灵活,不可能全部都记得,本文在 ChatGPT 的帮助下完成,作为方便自己日后查阅的 Cheat Sheet
在本地初始化一个 Git 仓库
mkdir Git-Travel
cd Git-Travel
git init
# 输出
Initialized empty Git repository in /Users/dejavu/MoeOffice/Git-Travel/.git/
git remote 命令用于管理本地 Git 仓库与远程仓库之间的连接
添加远程仓库,用法
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。
从本地 Git 仓库删除远程仓库
git remote remove <name>
# 比如
git remote remove origin
# 现在查看信息,应该输出为空
git remote -v
为了继续学习,我们将远程仓库添加回来
重命名远程仓库
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 <remote> <branch>
# 比如
git pull origin master
From github.com:MoeOffice/Git-Travel
* branch master -> FETCH_HEAD
我在本地仓库做了一些更改
# 先提交更改
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 <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
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 命令中的一个非常常用的命令,主要用于在不同的分支、标签和提交之间进行切换。常见的用法:
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 工作流中很重要的功能,基本操作
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 仓库中的某个提交打标签,可以理解为 Git 版本库的快照。通过给某个重要的提交打上一个有意义的标记,以便在后续查找和管理。
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 用于取消已经提交的变更、移动 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 <commit>
# 同时撤销多个提交
git revert <commit1> <commit2> ...
# 撤销最近的提交
git revert HEAD
# 撤销某一段提交
git revert <start>..<end>
将一个分支的更改合并到另一个分支,这是 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 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.
假如现在我们在某个分支做了一些工作(但是还没完成,暂时不能 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 popgit stash drop stash@{n}:放弃(删除)指定 stash 中的修改。例如 git stash drop stash@{1}git stash clear:清空所有的 stash注意 恢复 stash 贮藏操作,如果与当前工作目录暂存区的修改冲突,也要先修改冲突才能再进行 stash 应用的相关操作。
我们知道,Git 的每次提交(commit)都会生成一个唯一的哈希值。git cherry-pick 命令可以将某个提交的更改应用到当前分支中(而无需将整个分支合并过来),并自动生成一个新的提交,这个新的提交包含了指定提交的更改,用法:
git cherry-pick <commit>
注意 git cherry-pick 会自动解决冲突。
我感觉将 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