Git 时光机,一只调皮的猫
前言
本文主要讲Git
的相关知识,特别是关于rebase、revert指令的一些个人理解,欢迎大家指正。articles/2023-03-14-GIt时光机(简单小例子).md at main · slshsl/articles (github.com)
Git 与其他版本控制系统的区别
Git
直接记录快照,而非差异比较。
Git 完整性
Git
中所有的数据在存储前都计算校验和,然后以校验和来引用。Git
用以计算校验和的机制叫做 SHA-1 散列(hash
,哈希)。
Git 如何存储数据
Git存储数据有点像 key-value store
,key
可以认为是data
的sha1
哈希,value
认为是数据本身。
💡value是经过ZLib压缩过的
Git 基础概念
Working Space / Working Directory
: 工作区/工作目录,就是你平时存放项目代码的地方,本文统一使用工作区
Index / Stage
: 暂存区/索引区,用于临时存放你的改动,本文统一使用暂存区
Local Repository
: 本地仓库(本地版本库)本文统一使用本地仓库
Stash
:贮藏区
Remote Repository
: 远程仓库(远程版本库)本文统一使用远程仓库
<remote_name>/<branch_name>
:本地远程分支引用/本地远程跟踪分支,本文统一使用本地远程跟踪分支
常用指令
git xxx -h
查看某个指令的帮助信息。
git config
# 查看git配置
$ git config -l`
# 查看系统配置
$ git config --system --list
# 查看用户配置
$ git config --global --list
# 查看项目配置
$ git config --local --list
# 设置用户名称
$ git config --global user.name xxx
# 设置用户邮箱
$ git config --global user.email xxx
# 启动的编辑器一般为 vim。 当然也可以命令设置你喜欢的编辑器。
# 我一般习惯设置成vscode
$ git config --global core.editor xxx
# 项目启用rerere
$ git config rerere.enabled true
# 设置log -1 HEAD的别名为last
$ git config --global alias.last 'log -1 HEAD'
# 使用last别名
$ git last
💡每一个级别会覆盖上一级别的配置
git init
初始化一个仓库。
git clone
# 克隆一个仓库
$ git clone <url>
💡
git clone
命令做了什么?
- 添加一个跟踪的远程仓库
remote
,自动将其命名为origin
,拉取它的所有数据 - 创建一个
origin/master
(本地远程跟踪分支) - 创建一个
master
(本地分支)
# 克隆一个仓库
$ git clone —depth=1 <url>
通常我们直接使用 git clone
即可克隆项目,如果只是使用一个仓库项目,这是加入 –depth=1参数可以使我们只下载当前的最新提交即可。
git add
添加工作区的内容至索引区。
git status
显示当前的状态。
$ git status
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: a.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: a.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
c.txt
- 显示当前分支与其本地远程跟踪分支(如果有)的关系
可以通过
git push
推到远程的commit - 显示暂存区和本地仓库有差异的文件
通过运行
git commit
会添加到本地仓库的文件 - 显示工作区和暂存区有差异的文件
通过运行
git add
可以添加到暂存区的文件 - 显示工作区中不被
git
追踪的文件(也不被.gitignore
忽略) 通过运行git add
可以添加到暂存区的文件
git commit
# 提交变更到本地仓库
$ git commit -m "xxx"
# 修补提交:修补最后的提交
$ git commit --amend
# 修补提交:修补最后的提交,不修改提交信息
$ git commit --amend --no-edit
💡
git commit --amend
:修补最后的提交时,是完全用一个新的提交替换旧的提交。修补提交最明显的价值是可以稍微改进你最后的提交,例如一些小的修补,笔误等等;可以像下面这样操作:
# 第一次提交
$ git commit -m 'initial commit'
# 修改笔误等
$ git add forgotten_file
# 第二次提交
$ git commit --amend
💡最终你只会有一个提交;第二次提交将代替第一次提交的结果。
💡假如你的代码已经
push
了的话,git commit --amend
要慎用,因为会修改提交历史。
# 带签名的提交
git commit --signoff -m 'xxxx'
git log
# 基本用法
$ git log
# 仅显示最近的 n 条提交
$ git log -<n>
# 按补丁格式显示每个提交引入的差异
$ git log -p
# 显示每次提交的文件修改统计信息
$ git log --stat
# 在日志旁以 ASCII 图形显示分支与合并历史
$ git log --graph
# 每条日志一行显示
$ git log --pretty=oneline
# 仅显示 SHA-1 校验和所有 40 个字符中的前几个字符
$ git log --abbrev-commit
# --pretty=oneline --abbrev-commit 合用的简写。
$ git log --oneline
# 仅显示作者匹配指定字符串的提交。
$ git log --author='xxx'
#
$ git --no-pager log
# Log files that have been moved or renamed
$ git log --name-status --follow -- file
git diff
# 比较工作区与暂存区之间的差别
$ git diff
# 比较暂存区与本地仓库之间的差别
$ git diff —cached(git diff —staged)
# 比较工作区和暂存区(即所有未提交到本地仓库的修改)与本地仓库当前的HEAD之间的差别
$ git diff HEAD
# 比较工作区和暂存区(即所有未提交到本地仓库的修改)与本地仓库dev分支最新commit之间的差别
$ git diff dev
# 比较工作区和暂存区(即所有未提交到本地仓库的修改)与本地远程跟踪dev分支最新commit之间的差别
$ git diff origin/master
# 比较两次提交之间的差别
$ git diff [commit_sha1_value] [commit_sha1_value]
git tag
有两种tag,一种是lightweight,轻量标签;一种是annotated tag,附注标签
# 显示所有
$ git tag
# 创建lightweight tag(轻量标签)
# 如果没有写commit_sha1_value,则会自动把tag放到目前的这个commit上
$ git tag <tag_name> [commit_sha1_value]
# 创建annotated tag(附注标签)
# 如果没有写commit_sha1_value,则会自动把tag放到目前的这个commit上
$ git tag -a <tag_name> -m <tag_message> [commit_sha1_value]
# 删除tag
$ git tag -d <tag_name>
# 查看某个commit上的标签
$ git tag --points-at <commit>
💡轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。
💡附注标签是存储在
Git
数据库中的一个完整git的tag类型的objects对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息。
git show
# 显示tag对应的commit所提交的内容,如果是附注标签,还是显示附注标签的信息
$ git show <tag_name>
# 显示某次commit提交的内容
$ git show <commit_sha1_value>
git branch
# 查看本地仓库分支
$ git branch
# 查看远程仓库分支
$ git branch -r
# 查看本地仓库和远程仓库分支
$ git branch -a
# 创建本地仓库分支
$ git branch <branch_name>
# 强制删除本地仓库分支
$ git branch -D <branch_name>
# 删除本地仓库分支
$ git branch -d <branch_name>
# 查看分支详细信息,包括分支最后一个commit的hash、message等
$ git branch -v
# 同git branch -v
$ git branch -vv
# 修改本地仓库分支名称,如果省略old_branch_name,则更改的是当前分支的名称
$ git branch -m <old_branch_name> <new_branch_name>
# 强制修改本地仓库分支名称
$ git branch -M <old_branch_name> <new_branch_name>
# 查看哪些分支已经合并到当前分支
$ git branch --merged
# 查看哪些分支未合并到当前分支
$ git branch --no-merged
💡
git branch -d <branch_name>
:如果该分支还未合入master或者main,会提示报错,不让删除
💡
git branch -vv
:同时还会显示每一个分支正在跟踪哪个远程分支
git checkout
# 切换到新分支
$ git checkout <branch_name>
# 创建并切换到新分支
$ git checkout -b <branch_name>
# 重置分支,删除已存在的分支且重新创建,分支不存在也不会报错称
$ git checkout -B <branch_name>
# 将head指向一个commit
$ git checkout <commit_sha1_value>
# 从某个本地远程跟踪分支中检出一个新分支(并设置该分支的上游是远程跟踪分支)
$ git checkout -b <new_branch_name> <remote_name>/<old_branch_name>
# 是 git checkout -b <new_branch_name> <remote_name>/<old_branch_name>的简写
$ git checkout --track <remote_name>/<branch_name>
# 让工作区中的所有文件撤销更改
$ git checkout -- .
# 让工作区中的某些文件撤销更改
$ git checkout -- <file_name> <file_name>
💡
git checkout -b <new_branch_name> <remote_name>/<old_branch_nameh>
💡
git checkout --track <remote_name>/<branch_name>
💡
git checkout <branch_name>
git checkout -b <new_branch_name> <remote_name>/<old_branch_nameh>
的意思是从某个本地远程跟踪分支中检出一个新分支,并设置该新分支的上游是该本地远程跟踪分支; 因为git checkout -b <new_branch_name> <remote_name>/<old_branch_nameh>
经常用,且检出的新分支名字和其上游分支的名字一般相同,不太会取不同的名字;所以出了一个简写命令git checkout --track <remote_name>/<branch_name>
;git checkout --track <remote_name>/<branch_name>
该命令从某个本地远程跟踪分支中检出一个新分支,并设置该新分支的上游是该本地远程跟踪分支,同时该新分支的名字不能指定,只能是该本地远程跟踪分支的名字;git checkout --track <remote_name>/<branch_name>
还是太长;所以git checkout branch_name
切换分支时,先从本地库查找分支,在本地库没找到时,会去本地远程跟踪分支中查找,如果本地远程跟踪分支中有相同名称的分支,则也会检出分支并设置其上游为同名的本地远程跟踪分支;如果本地远程跟踪分支里也没有找到就会报错;
💡
git checkout <commit_sha1_value>
:这样会发生detached head,即head
不再指向一个分支,而是指向一个commit
; 应用场景是比如从之前的commit
或者误删的commit
(通过给git reflog
查看);拉出一个新分支;
# 查看所有操作的日志
$ git reflog
# 检出需要检出的commit
$ git checkout <ommit_sha1_value>
# 检出一个新的分支
$ git checkout -b <new_branch_name>
💡
git checkout --
的撤销功能:git checkout
撤销的是工作区的内容,即清除工作区 场景一、本地库中已有user.txt
,在工作区修改了该文件,但是没有提交到暂存区,此时撤销更改是从本地仓库中恢复内容 场景二、本地库中已有user.txt
,在工作区修改了该文件,并且之前按已提交到暂存区,此时撤销更改是从暂存区中恢复内容
git restore
替代git checkout
的撤销功能。
# 放弃在工作区的修改
$ git restore <file_name> <file_name>
# 放弃所有文件在工作区的修改
$ git restore .
# 将暂存区的内容,移动工作区中,即是git add的反向操作
$ git restore —staged <file_name> <filen_ame>
💡
git restore <file_name> <file_name>
替代git checkout -- <file_name> <file_name>
💡
git restore .
替代git checkout -- .
💡
it restore —staged <file_name> <filen_ame>
:git add
的反向操作
git switch
替代git checkout
的切换分支功能。
# 如果本地仓库或者本地远程跟踪分支有这个分支,则切成功,否则失败
$ git switch <branch_name>
# 创建并切换到新分支
$ git switch -c <branch_name>
# 重置分支,删除已存在的分支且重新创建,分支不存在也不会报错称
$ git switch -C <branch_name>
💡
git switch <branch_name>
替代git checkout <branch_name*>
💡
git switch -c <branch_name>
替代git checkout -b <branch_name>
💡
git switch -C <branch_name>
替代git checkout -B <branch_name>
💡
git switch -
可以快速切换上一个分支,来回切换,与cd -
一样
git remote
# 显示远程仓库的名字
$ git remote
# 显示远程仓库的名字及url
$ git remote -v
# 查看本地远程跟踪分支与远程仓库中分支的同步情况
$ git remote show <remote_name>
# 可以删除(在远程仓库中被删除的分支)对应的本地远程跟踪分支
$ git remote prune <remote_name>
💡
git remote show <remote_name>
:一般应用场景就是在git fetch
之前查看一下分支同步情况
💡
git fetch —prune
与git remote prune
的作用一样,实际上删除(远程仓库中被删除的分支)对应的本地远程跟踪分支。
git rebase
变基
$ git rebase <upstream_name>
# 当前分支为dev
$ git rebase main
# 如果有冲突,解决冲突后继续编辑
$ git rebase —continue
# 当前分支为main
$ git merge dev
git rebase <upstream_name>
做了什么?git rebase main
(dev
)把dev
分支的提交重放(重新应用)到main
分支的顶部。
git rebase --onto main server client
做了什么? 对于这个指令,每次看到都头大,需要自己用文字描述一下,以方便理解
# newbase_name\upstream_name\branch_name既可以是分支名,也可以是commit_sha1_value
$ git rebase [--onto <newbase_name>] [<upstream_name> | [branch_name]]
💡 如何理解上面这条指令各个参数是啥意思?列出具体指令来尝试讲解
-
场景及命令一: 场景:假设
dev
是从master
检出的分支,要在dev
分支上变基,当前所在分支不是dev
分支 命令:git rebase <upstream_name>
:git rebase master
# 切换到dev分支 git checkout dev # rebase master,这个master是对应upstream_name;怎么理解这里的上游分支 # 实际上就是dev是从master检出的,是dev的上游分支,依此来找到两个分支的交叉点 # 把在dev分支上以这个交叉点为起点,以当前HEAD为终点(dev最新commit),把这两点之间的 # commit在master分支顶部(最新commit),重新来一遍,得到一个新的dev分支。 git rebase master
-
场景及命令二: 场景:假设
dev
是从master
检出的分支,要在dev
分支上变基,当前所在分支不是dev
分支 命令:git rebase <upstream_name> [branch_name]
:git rebase master dev
# git rebase master dev 是 git checkout dev + git rebase master 这两条命令的简写 # 就是我先切到dev分支,再rebase master分支 git rebase master dev
-
场景及命令三: 场景:假设当前所在分支是
dev
分支,其本地远程跟踪分支是origin/dev
命令:git rebase
# git rebase 是 git rebase origin/dev 命令的简写 git rebase
💡 简写的前提是
- 当前不是detached headhead状态
- 当前分支有对应的本地远程跟踪分支(即上游分支)
-
场景及命令四: 场景:
命令:git rebase --onto main server client
# 第一步:git checkout client # 第二步:找到client与其上游分支server的交叉点的之后的所有commit # 第三步:把这些commit基于--onto参数的值main顶部(最新的commit),重放这些commit # 理解分为两部分,第一部分找server client交叉之后的提交, # 第二部分,以--onto参数的值为基,重放这些提交 git rebase --onto main server client
-
场景及命令五: 场景:
master
分支,共6个提交,每个提交都创建一个txt
文件 命令:git rebase --onto HEAD~5 HEAD~3 HEAD
# 第一步:git checkout HEAD,处于detached headhead状态 # 第二步:找到HEAD~3与HEAD之间所有的commit # 第三步:把这些commit基于HEAD~5,重放这些commit # 实际效果就是删除第2次与第3次提交 # 可以从当前游离的head检出一个分支替代master,或者直接git branch -D master git rebase --onto HEAD~5 HEAD~3 HEAD
- 应用场景
- 在合并分支前,不想分叉,可以先
rebase
目标分支,再合入目标分支 - 同步远程分支时,不使用
git pull
,使用git fetch
,再使用git rebase
- 在合并分支前,不想分叉,可以先
💡 共享分支:当一个分支会被
push
到远程仓库,且有可能其他人会进行pull
操作时,这就是一个共享分支
💡 Do not rebase commits that exist outside your repository and that people may have based work on.
💡 永远、永远不要对一个共享分支进行变基。
💡 原因就是:变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
💡 基于变基的本质,如果是多人协作对共享分支允许变基;假如A在本地变基操作后推到了远程共享的分支,同时也丢弃了一些现有的提交;
💡 而B在拉去远程共享分支时,由于依赖了之前A丢弃的提交,所以如果再merge后推送到远程,就会造成重复的提交。
💡 解决方式:那就是永远不要对共享分支进行变基;
交互式变基
# start_commit_sha1_value不包含,end_commit_sha1_value包含
# end_commit_sha1_value可以没有,则默认为当前分支的HEAD
$ git rebase -i [start_commit_sha1_value] [end_commit_sha1_value]
# 如果你异常退出了变基选择操作的窗口,使用以下命令重新打开
$ git rebase --edit-todo
# 放弃变基
$ git rebase --abort
# 保留,啥也不动
**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
# 删除该提交
**d, drop <commit> = remove commit**
建议使用vscode
中的GitLens
插件来进行交互式变基,如下图
git fetch
会拉去数据,同时会同步所有远程仓库分支和本地远程跟踪分支,对于本地远程跟踪分支,实际上就是为了区分本地仓库分支,前面加了remote_name
的分支引用
💡git fetch
命令做了什么?
- 拉取它的所有数据
- 更新你的远程跟踪分支
- 对与远程仓库中有而本地没有的分支,只会创建远程跟踪分支,不会创建本地分支
💡 当抓取到新的远程跟踪分支时(这句话的意思是比如远程仓库新建了一个分支dev,当git fetch
时就会拉取新远程分支到本地的远程跟踪分支origin/dev
),但不会创建一个本地dev
分支。
- 如何只是想合并到你的分支,可以执行
git merge origin/dev
- 如果想在本地分支的工作,可以从
origin/dev
检出一个本地的dev
分支,执行git checkout -b dev origin/dev
# 从远程仓库中拉去所有分支数据到本地,同步所有远程仓库分支和本地远程跟踪分支,如果缺省远程仓库名,默认为origin
$ git fetch <remote_name>
# 从远程仓库中只拉取一个分支数据到本地
$ git fetch <remote_name> <remote_branch_name>
# 从远程仓库中只拉取一个分支数据到本地,并在本地仓库中创建一个分支
$ git fetch <remote_name> <remote_branch_name>:<local_branch_name>
# 首先会同步远程仓库分支与本地远程跟踪分支,同时会将本地远程跟踪分支中存在而远程仓库分支没有的分支删除。
$ git fetch —prune
# 显示fetch的详细信息
$ git fetch -v
💡
git fetch —prune
:比如初始状态时远程仓库分支和本地远程跟踪分支已经同步; 此时,在远程仓库中删除某个分支a
,再使用git fetch
时,本地远程跟踪分支中的分支a
不会删除; 此场景下可以使用git fetch —prune
,也可以使用git remote prune <remote_name>
- 应用
- 应用一
git fetch origin master
//从远程origin
仓库拉取master
分支的数据,同时同步本地远程跟踪分支master
git log -p master origin/master
//比较本地仓库的master
分支和本地远程跟踪分支master
的区别git merge origin/master
//将本地远程跟踪分支中的master
分支合入本地仓库的master
分支 - 应用二
git fetch origin master:temp
//从远程的origin
仓库拉取master
分支的数据,并在本地仓库中新建一个分支temp
git diff temp
//比较本地仓库当前分支和本地仓库temp
分支的差别git merge temp
//合并本地仓库temp
分支到本地仓库master
分支git branch -d temp
//删除本地仓库temp
分支
- 应用一
git merge
# 默认采用fast forward
$ git merge
# 采用fast forward
$ git merge -ff
# 强行关闭fast forward
# 个人觉得应用场景是:拉取远程分支,检出自己的分支上开发,然后push前rebase一下远程分支,提个pr,采用git merge --no--ff的方式merge到远程分支。
$ git merge --no-ff
fast forward
:这时候bugfix
合入master
是一次fast forward
3 way merge
💡 如果已经分叉了,还想实现
fast-forward
的merge
,可以使用git rebase
git pull
git fetch
+ git merge
# git fetch + git merge
$ git pull
# git pull的简写
$ git pull --merge
# git fetch + git rebase
$ git pull --rebase
# 显示详细信息
$ git pull -v
git push
将本地仓库当前分支推出送远程分支
# 完整命令,对于没有设置上游的本地分支推送至远程采用的方法
$ git push <remote_name> <local_branch_name>:<remote_branch_name>
# 如果本地分支名和远程分支名一样的情况下,可以省略:<remote_branch_name>
$ git push <remote_name> <local_branch_name>>
# 设置当前的分支的上游分支是
$ git push —set-upstream <remote_name> <remote_branch_name>
# git push -u origin是git push —set-upstream origin的简写
$ git push -u <remote_name> <remote_branch_name>
# 删除远程分支
$ git push <remote_name> -d <remote_branch_name>
# 删除远程分支
$ git push <remote_name> :<remote_branch_name>
# 推送某个标签
$ git push <remote_name> <tag_name>
# 一次性推送很多标签
$ git push origin —tags
# 删除远程标签的方式
$ git push origin --delete <tag_name>
💡
git push
简写命令使用前提条件是:
- 远程仓库有这个分支
- 并且通过
git branch -vv
查看,本地仓库当前分支与本地远程跟踪分支是关联的- 并且当前只有一个
origin
的remote
💡
git push <remote_name> <local_branch_name>
:将本地仓库分支推送到远程仓库;这样操作,虽然会同时更新本地远程跟踪分支;并没有将本地的该分支与对应的本地远程跟踪分支进行关联(通过git branch -vv
查看)
💡新建本地分支后
push
到远程仓库,但并没有将本地分支与对应的本地远程跟踪分支相关联,下次本地分支有新的commit
后,再push
到远程,依然要git push <remote_name> <local_branch_name>
,不能直接用git push
这样的简写命令
💡
git push -u <remote_name> <remote_branch_name>
:将新建的本地分支推送到远程分支,并将该分支与对应的本地远程跟踪分支相关联,下回再推送时就可以使用git push
这样的简写
git revert
git revert <commit_sha1_value>
git revert HEAD
git revert HEAD^
git revert HEAD~
下图是在C5
为当前HEAD
,分别对C5
、C4
、C3
、C2
、C1
、C0
进行revert
💡区别:
HEAD^
主要是控制merge
之后回退的方向;HEAD~
才是回退的步数 个人理解:如果某个节点只有一个父节点,那就用~
,不要用^
,因为不太直观,容易混乱;只针对有多个父节点的回退操作采用^
。
git revert -m <commit_sha1_value>
git revert -m HEAD
git revert -m HEAD^
git revert -m HEAD~
💡revert主要分两类,一类是针对只有一个父节点的commit,一类是针对有两个父节点的commit(merge分支产生的)
💡什么时候加-m,当该节点是merge产生的节点的时候,它会有2个父节点(之前老是记不清,一个commit节点是不可能有多于2个父节点的,通过这个-m的参数才想起来,所以这个-m后面跟的数字只能是1和2,1代表我保留自己的分支,2代表保留合入的分支)
💡
revert
可能带来的问题如下图描述
对于此问题官网给出的解决方案是在master
上revert
掉之前的revert
,然后再merge
git reset
# 更改HEAD和当前分支顶部指向哪个commit,并覆盖暂存区
git reset —mixed <commit_sha1_value>
# 只更改HEAD和当前分支顶部指向哪个commit
git reset —soft <commit_sha1_value>
# 更改HEAD和当前分支顶部指向哪个commit,并覆盖暂存区和工作区
git reset —hard <commit_sha1_value>
# git reset —mixed <commit_sha1_value>简写
git reset <commit_sha1_value>
git reset —soft

git reset —mixed

git reset —hard

下图是在C5
为当前HEAD
,分别reset
到C4
、C3
、C2
、C1
、C0
可以使用的命令
💡
revert
与reset
的区别是什么git revert
是用一次新的commit
来回滚之前的commit
,此次提交之前的commit
都会被保留;git reset
是回退到某次提交,提交及之前的commit
都会被保留,但是此之后的commit
都会被删除;所以可以说revert
是以新增commit
的方式回滚某个commit
;而reset
是回退到某个commit
💡 单从用法上来讲,应用场景可以参考以下: 找出有问题的
commit
,如果从HEAD一直到有问题的commit
之间都不要,那就用reset
;如果从HEAD
一直到有问题的commit
之间,只想回滚有问题的commit
,中间其他的commit
还要保留,那就用revert
💡 以上过于啰嗦了,简单点,就是回滚某个
commit
用revert
,回退到某个commit
用reset
;所以对于git reset HEAD
这个命令是无意义的,当前就在这个commit
,要回退到这个commit
岂不是无用功;对于git revert HEAD
这个命令是有意义的,如果你确实想回滚当前的commit
git stash
# 保存工作区暂存区中的内容
git stash
# 保存工作区暂存区中的内容,并添加注释,不推荐使用
git stash save 'message'
# 保存工作区暂存区中的内容,并添加注释,推荐使用
git stash push -m 'message'
# 恢复后同时弹出 stash
git stash pop
# 恢复后stash内容并不删除
git stash apply
# 删除stash
git stash drop
# 清空`stash`
git stash clear
# 显示所有的stash
git stash list
# 显示最新缓存修改的统计信息
git stash show
💡 在使用
git stash pop
代码时,经常会碰到有冲突的情况,一旦出现冲突的话,系统会认为你的stash
没有结束。导致的结果是git stash list
中的列表依然存在,实际上代码已经pop
出来了。
- 应用场景
- 正常dev分支上开发,紧急bug或者功能来时,可以先将在dev分支做的工作用
git stash save
存下,然后切换到紧急分支,修复或者完成commit
之后,在切回dev分支,用git stash pop
将之前的工作内容回复
- 正常dev分支上开发,紧急bug或者功能来时,可以先将在dev分支做的工作用
Git中的其他概念及指令
本地远程跟踪分支 上游分支
从一个远程跟踪分支检出一个本地分支会自动创建所谓的跟踪分支(它跟踪的分支叫做上游分支,即跟踪的是远程仓库的分支); 跟踪分支是与远程分支有直接关系的本地分支; 如果在一个跟踪分支上输入 git pull
,Git
能自动地识别去哪个服务器上抓取、合并到哪个分支。
ORIG_HEAD
ORIG_HEAD
记录了reset
或者merge
操作之前的HEAD
应用场景:
- 利用
ORIG_HEAD
撤销reset
:git reset ORIG_HEAD
- 利用
ORIG_HEAD
撤销merge
:git reset --merge ORIG_HEAD
use --merge flag to preserve any uncommitted changes
FETCH_HEAD
记录了远程所有分支对应的最新的commit
git ls-files -s
查看索引区内容
git cat-file
# 查看git object内容
git cat-file -p <sha1_value>
# 查看git object类型
git cat-file -t <sha1_value>
# 查看git object大小
git cat-file -s <sha1_value>
git objects
有四种类型:blob
、commit
、tree
、tag
通用数据结构为:
blob
# echo -n输出的内容不换行
echo -n 'Hello, World!' | git hash-object --stdin
b45ef6fec89518d314f546fd6c3025367b721684
# echo -e转义
echo -e -n 'blob 13\0Hello, World!' | openssl sha1
(stdin)= b45ef6fec89518d314f546fd6c3025367b721684
git add .
会生成blob
对象
blob
对象存储文件内容信息,包含sha1
、字节大小、文件内容数据
tree
git commit
会生成tree
对象(可能有多个)
tree
对象中嵌套tree
以来表示文件的嵌套
💡
blob
对象不存储文件的名称,文件的名称存储在tree
对象中
commit
git commit
会生成一个commit
对象(包含)、tree
对象(可有有多个),不会生成blob
对象
tag
git tag -a tagname
会创建一个tag
对象,对象中的object
会指向某一个commit
;tag
对象会包含一些作者和时间的信息等等
💡
git tag <tag_name>
不会创建一个tag
类型的git object
git references
git中的引用实际上是指向某个commit的指针
Tags
Lightweight tags
的引用是指向某个commit
类型的git object
对象的指针
ANNOTATED tags
的引用时指向某个tag
类型的git object
对象的指针
Branches
Branches
的引用是指向某个commit
类型的git object
对象的指针
HEAD
通常是指向当前分支的引用,但是它也可以指向某个commit
类型的git object
对象(detached HEAD
)
git show-ref
# 查看当前所有的tag,约等同与 git tag
git show-ref --tags
# 查看当前所有的分支,约等同与 git branch
git show-ref --heads
git gc
由于git
是全量快照,每一次commit
都会对应一个版本的全部数据,这样会造成仓库很大,为了解决该问题,引入了压缩算法,举例,git clone
在拉取远程仓库时,就会压缩成pack
后再传递;在本地执行gc
后,也会把.git/objects
文件夹下的对象压缩到pack
文件夹下。
参考文献
本文发布内容来自个人对于Git-scm网站关于Pro Git book的阅读及实践后的理解,同时参考了Git In-depth视频,文章未经授权禁止任何形式的转载。
转载自:https://juejin.cn/post/7212536164057972792