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分支的数据,同时同步本地远程跟踪分支mastergit log -p master origin/master//比较本地仓库的master分支和本地远程跟踪分支master的区别git merge origin/master//将本地远程跟踪分支中的master分支合入本地仓库的master分支 - 应用二
git fetch origin master:temp//从远程的origin仓库拉取master分支的数据,并在本地仓库中新建一个分支tempgit 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 forward3 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