Re: 从零开始的蕾姆级 git 指北笔记

310 阅读20分钟

米娜桑,好久不见~ 距离上次更新还是在上一次,不愧是鸽王的我,以后请叫我切图的鸽鸽~

今天给大家安利的是 git 常用的指令,这个时候可能会有小伙伴会有疑问,觉得 git add | commit | push 就已经够用啦~

达咩哟达咩达咩哟,格局小了,2022 年了怎么还可以这样呢,每多学点方便我们在女同事面前多炫会,这不比炫迈还炫!给我支棱起来呀!

嗯哼~,骚话先不能说太多,在阅读文章前我们可以先了解以下这几个问题

  • git 是什么?
  • git 可以为我们带来什么便利/好处?
  • 是否有与git类似功能的工具呢?
  • 为什么git相较其他同类别工具更为受欢迎呢?

Git 基本的工作流程

Git 基本的工作流程 图中主要涉及到的4个区域 工作区:被 git 管理的项目,比如某个文件夹; 暂存区(Index/Stage):临时存放被修改的文件; 本地仓库:用于存放提交代码快照的记录; 远程仓库:远端代码仓库。

Git 指令大全

git config

在使用 git 前我们需要配置一些必要的信息,使用 git config 即可对配置进行增删减查的操作。 Config 主要分为 system,global 和 local 级别,git 在取配置过程中优先取 local,没有找到对应的配置前会一直往外出冒泡查询 global 直到 system 为止知道找到或者报错(很向JS的作用域链)。

  • 配置用户信息
# 配置全局用户名
git config --global user.name 'qwz'
# 配置当前仓库用户名
git config --local user.name 'qwz'
#配置全局/当前用户邮箱
git config --global/--local user.email 'wenzhi.qiu@qdtech.ai'
  • 查询配置信息
# 查询配置信息
git config --list
# 查看 global 的配置列表
git config --global --list
# 查看 global 的用户名
git config --global user.name
  • 配置别名

配置别名可以让我们在操作过程中不必输入完整的指令,可以通过简短的别名来代表对于的指令。

# 设置全局 checkout 别名
git config --global alias.co checkout
# 设置全局 branch 别名
git config --global alias.br branch
# 设置全局 commit 别名
git config --global alias.co commit
# 设置全局 status 别名
git config --global alias.st status
# 设置全局 push 别名
git config --global alias.push ps 
# 设置全局 pull 别名
git config --global alias.pl pull
# 设置全局 pull origin 别名
git config --global alias.pr pull --rebase origin
# 设置全局 log 别名
git config --global alias.lg log
# 设置全局 cherry-pick 别名
git config --global alias.cp cherry-pick
# 等等,其余的可以根据个人习惯按需配置

git init 初始化仓库

该指令用于初始化 git 仓库,创建完成后仓库中会有一个隐形的文件夹.git, 并自动创建的 master 分支,并且将 HEAD 指针指向 master 分支。 .git 文件夹

.git目录 hooks:存放一些自定义 shell 脚本,可以在 git 命令前后做检查或做些自定义动作用于管控 git > 的工作提交流程; info:包含git仓库的一些信息,用于排除提交规则,与 .gitignore 类似,不过不会提交到版本库中; objects:存储 git 的对象,分别有 blob,tree 和 commit 对象

  • Blob 对象:用于解决数据存储的问题;
  • Tree 对象:用于解决文件名存储的问题;
  • Commit 对象:用于解决存储提交信息的问题; refs:存放分支或者标签的引用地址,有 heads,remotes 和 tags 文件夹;
  • heads:存储所有本地分支的对象;
  • remotes:存储所有远程分支的对象;
  • tags:存储所有标签的对象; config:存储当前仓库的配置信息; HEAD:HEAD指针,它指向了当前分支,这个文件记录了当前分支是哪个分支; index:前文提到的暂存区

git status 查看工作区和暂存区的文件状态

当我们在改动文件时,又或者摸鱼过程中时想看看自己多努力时就可以使用该指令查看文件改动状态。

红色表示工作区的文件,等待提交到暂存区

绿色表示暂存区的文件,等待提交快照

git add 提交文件至暂存区

该指令的操作有几种姿势

  1. 添加所有的文件到暂存区,可以使用 * 或者 . 来代表所有文件
git add *
  1. 添加指定文件到暂存区
git add index.js
  1. 添加文件夹至暂存区
git add ./lib
  1. 撤销暂存区的文件/文件夹
# 撤销暂存区的所有文件/文件夹
git reset HEAD
# 指定撤销暂存区的文件
git reset HEAD index.js
# 指定撤销暂存区的文件夹
git reset HEAD ./lib

git rm撤销/删除文件

在Coding过程中难免有需要将文件从暂存区退回工作区,又或者想直接从暂存区/工作区直接删除文件。

# 将文件从暂存区撤回工作区
git rm --cache [file]

# 删除工作区/暂存区的文件
git rm [file]

另外,关于撤销文件的姿势还有git reset 和 git checkout,先点到为止。

git stash 保存未提交的代码

git stash 我敢打包票,你会爱上它的,为什么这么说呢?

假如你在一个新分支中做开发,写到一半秃然后背一凉,原来是测试大哥送来了一个hight bug,刚好代码敲到一半提交也不行,撤销也不行,这个时候就是 git stash 的用武之地了,他的作用就是可以将当前未提交的文件暂时存放起来。

不过需要注意的是该指令可以将工作区和暂存区的改动全部保存起来,当时默认不会自动存放的文件主要包括 工作区中新增的文件和被忽略的文件。

当然,有时候文件一多难免会有一些意外发生,如果为了确保所有文件都可以保存起来可以使用 -u 或者 -a 用于保存新的文件或者目录下的所有修改。下面来看看怎么使用吧~

# 保存暂存区和工作区的改动
git stash

# 保存改动的同时为这个存放标明说明
git stash save 'description'

# 查看保存改动的列表,不仅可能在修bug的过程中又发现另外一个分支又有一个更紧急的bug
git stash list

# 取出最近或者指定一次改动的内容恢复至工作区后并删除掉对应的存档记录
git stash pop stash@{[index]}

# 取出最近或者指定一次改动的内容恢复至工作区后,继续保留原有的存档记录
git stash apply stash@{[index]}

# 查看保存的改动和当前的差异
# 查看最近一次保存和当前的差异
git stash show
# 指定对应一条记录于当前的差异
git stash show stash@{[index]}

# 删除最近的一条记录
git stash drop
# 删除指定的某条记录
git stash drop stash@{[index]}

# 删除所有保存的记录
git stash clear

git commit 提交操作

主要通过 git commit 将暂存区的文件提交到版本库中,其中主要可以携带以下几个参数

  • -m:描述此次提交的说明,如 git commit -m'fix(solution): 修复若干bug'
  • -a:该操作表示 add,可以直接将工作区的文件直接跳过暂存区存储直接提交到版本库中;
  • --amend:将更新并用新提交替换最近的提交,该提交会将任何阶段性更改与先前提交的内容相结合。
    • 场景1:急于下班的你一把梭子随便写了个 commit_desc 后被怼了,这个时候就可以用 --amend 对上一次的提交描述做调整 example
    • 场景2:还是急于到点下班的你在提交过程中代码虽然是 fix,但实际因为少打了个}导致 no fix,so bad,这个时候为了“掩盖”你的低级错误时就可以对代码调整后再次提交中使用该指令覆盖上一次的提交操作。 example

不过需要注意一点,对于 --amend 调整前就提交至远端分支情况下,如果想将调整后的commit提交上去需要使用 -f 强推,不过不建议这样操作。

git log 查看提交记录

git log 的操作比较简单,以下就直接列出常用的指令以及参数吧,另外这些参数可以任意搭配哦。

# --oneline 将每条日志以一行一条的形式展示出来,方便快速浏览
git log --oneline

# -[length] 指定显示多少条日志
git log -4

# --skip=[length] 跳过前 length 条
# 假如我想查看第4~8条日志,可以使用
git log --skip=3 -5

# --pretty=raw 查看每条 log 更为详细的信息
git log --pretty=raw -2

# -p 查看每条日志对应的改动内容
git log -p -1

# --graph 以可视化图形的方式呈现每条 log 的内容,可以直观的看到每条日志的演变过程
git log --graph -5

# --name-status 显示对应 log 中改动的文件有哪些
git log --name-status -6

# --author authorName 搜索某个开发者提交的 log
git log --author qwz

# --grep searchKey 通过含有对应搜索内容的 log
git log --grep balabala

# 查看指定多个分支的 commit 差异
# 查看 dev 分支中有当在 test 分支没有的 commit log
git log dev ^test

git branch 分支操作

branch

一个项目时有多个版本的,很多时候存在多个版本多个分支同时并行开发来减低代码维护和冲突的成本,所以当我们在开发过程中对于分支的操作是肯定少不了的,废话不多话,看看 git branch 的 n 种操作姿势吧~

# 查看本地所有分支
git branch

#查看所有分支
git branch -a

# 查看远端所有分支
git branch -r

# 查看本地全部分支
git branch -v

# 查看本地分支所关联的远端分支
git branch -vv

# 创建分支
git branch branchName # 该操作会创建 branchName 分支
git checkout -b branchName1 # 该操作会创建并切换到 branchName1 分支
# 基于远端分支创建可以自动跟踪远端变更的本地同名分支
git checkout --track origin/branchName

# 将本地分支与远端分支关联
git branch --set-upstream-to origin/***

# 更改分支没错
git branch -m oldBranchName newBranchName

# 删除分支
git branch -d branchName # 推送并合并到远端仓库时
git branch -D branchName # 未推送远端或者合并时强制取出

# 切换分支
git checkout -b branchName
# 或者
git switch branch branchName

# 查看有哪些分支合并到该分支
git branch --merged

# 查看哪些分支还未合并到该分支
git branch --no-merged

# 合并分支
git merge branch | origin/branchName

# 强制修改移动分支位置
git branch -f branch commitId

关于 branch 的操作比较简单,我们就挑几条有意思的来说说吧

  1. 在其上开发某个新功能或者修复bug,开发完成后使用git merge feature将其合并回主线。

branch

  1. 使用 -f 让分支指向另外一个提交

branch

git tag 打标签

git tag 主要是给特定的历史记录打上标记,标签不像分支任意更改。它们在创建后没有进一步的提交历史记录。大多数人使用此功能来标记一些版本发布点。同时在开发过程中我们也可以通过标签来快速切到对于的提交,so 是不是很Nice呢。

标签的类型主要有两种,分别是 带注释的标签 和 轻量级标签

  • 带注释的标签

带注释的标记将作为完整对象保存在 Git 的记录中。这些类型的标记存储一些额外的元数据信息,例如标记名称、标记邮件或者日期。

在栗子中我们可以使用 git 给 HEAD 打个标签。用 -a 为设置标签名为"Release_1_0",并且通过 -m 为标签前设置注释。

tag

  • 轻量级标签

可以通过 git tag <tag_name> 直接给某个提交打上标签即可,这里就偷懒不写啦。

git checkout

git checkout 是一个非常强大的指令,它操作的对象既可以是分支,也可以是某个提交或者文件。

  • 分支操作

提到分支操作,各位小伙伴第一个想起的应该就是上文敢热乎一会的 branch 指令吧,在后面我们提到了`checkout 也是可以实现分支的创建和切换的,而且还更强大!下面来看看具体怎么用吧。

# 创建并自动切换到新分支
git checkout -b branchName
# 等价于 git branch branchName && git switch branchName

# 切换到某个分支(已存在的)
git checkout branchName

# 切换至某个 tag
git checkout tagName

# 基于某个提交创建分支
git checkout -b newBranchName <commitID|tag>

是不是觉得很Nice呢,别急,这才只是开始,继续!!!

我们在实际开发过程中想基于某个远端分支创建新分支并创建关联关系时也可以使用`checkout 来操作。

# 方式一
# 上文提到的基于远端分支创建可以自动跟踪远端变更的本地同名分支
git checkout --track origin/branchName

# 方式二
git checkout -b branchName origin/branchName
  • 提交操作

另外,这些对于上面的这些操作只不过是前菜而已,还有强大的一点就是`checkout 可以将 HEAD 切换到某个提交中进行调整或者创建新的分支。

checkout

  • 文件操作

checkout 处理文件也是很方便,不过好用也得慎用23333

追求完美的你在coding过程中发现自己敲的很离谱,这个你还未提交想放弃所有更改时就可以使用 git checkout . 或者 git checkout -f 全部从来(当然你也可以使用 git reset,后面再提)。 又或者你想丢恢复某个文件至改动前时所以使用 git checkout -- file

对于 git checkout -- file 需要注意以下,他有两种情况

情况1:如果暂存区有该文件的改动,这会将工作区的文件变动恢复到暂存区的变动一致; 情况2:如果指定的文件未提交到暂存区时,也就是暂存区没有保存该文件的变更状态则会恢复至与最近一次提交的一致。

git diff

该指令主要用于区分代码差异。

  • git diff
    1. 当暂存区为空时比较的是工作区与最近一次提交的变更差异;
    2. 当暂存区不为空比较的则是暂存区与最近以及提交的变更差异。
  • git diff --cached | git diff --staged 对比工作区与最近一次提交的变更差异;
  • git diff <commit> 对比当前 HEAD 与指定某个提交的变更差异;
  • git diff <branch1> <branch2> 对比指定多个分支的变更差异。

git reset

git reset 也是一个强大的指令,主要用于回退代码,使工作区,暂存区或者各版本的文件提交回退重置。

reset

  • 场景1 取消某个文件的提交

母单手速的你不小心将多余的文件 add 到暂存区,可以使用 git reset -- file 将对于的文件移除不纳入此次提交的变更。

  • 场景2 临时中断任务处理

没错,曾几何时的那位测试小哥又过来搞事给你丢了一个 bug,并执意要在你旁边看你修完,这个时候被迫中断当前分支,这个时候那诗般优雅的代码还没有产品不好提交情况下如何保存呢?

没错,可以使用上文提到的 git stash 来存储,但其实还有一种方法,就是添加一个临时提交,等fix完成后再切回原本分支来回退临时提交来继续诗的续写。不过最好还是使用 git stash,不过方法多总比少好嘛,下面看下栗子吧。

# coding...,小哥还有 5s 到处你的工位
# 小哥已到工位,保护我方代码并 fix bug
git commit -am'WIP feat'
git checkout master
# fix 完工提交并继续原来的工作
git commit -am'hotfix'
git checkout feat
# 将工作区和暂存区恢复至提交前的状态
git reset --soft HEAD^
  • 场景3 回滚至最近一次提交

提交过程中发现仍需做些调整,当时这些调整不足以再次提交所以想从最近一次提交进行调整。

git commit -m'完工'
git reset --soft HEAD~
# coding...
git commit -am'完工'

这里可能有同学比较好奇 --soft 参数的作用,其实除了它还有另外2个参数分别是 --mixed--hard

reset

  1. --soft 当我们回退到A提交时,当前 HEAD 会指向A,将B提交的变动的文件放置到当前的暂存区中后B提交便会消失。
  2. --mixed 当我们回退到A提交时,当前 HEAD 会指向A,将B提交的变动的文件放置到当前的工作区中后B提交便会消失。
  3. --hard 当我们回退到A提交时,当前 HEAD 会指向A,将B提交的变动的文件不会放置到暂存区或者工作区而是直接消失了。
  • 场景4 对 merge | pull 做回滚处理 当我们在拉去远端代码或者合并另外一个分支的过程中发现变更的突出极其多,没时间处理的情况想放弃此次合并或者 pull 操作时可以使用 git reset --hard HEAD 来回退到合并前。

git merge

我们开发都是多分支并行开发的,假如你的分支需要合并其他分支的 feat 或者 fix 来支持你的开发就可以使用 git merge 将另外分支合并到我们开发分支进行支持。关于 merge 操作的指令较为简单,感兴趣的小伙伴可以前往了解更多

merge

git rebase

git rebase 是一个强大有危险的指令,我们也称之为“变基操作”,简单说就是从某个分支中取出一系列的提交记录,“复制”它们,然后在另外一个分支逐个放下去。它的优势就是可以创造出更线性的提交历史使得我们的版本库分支提交历史变得足够清晰明确。 举个简单的例子,当我执行 git rebase feat main 后将 feat分支 的提交记录移到 main分支 中使得两个分支的提交记录时按顺序提交一样。

rebase

看到这里可能还是比较疑惑,rebase 可以解决什么场景问题。

  • 线性合并分支,利于 codereview

如上图操作,可以使用 git rebase feat mainfeat 的代码合并到 main 中,并且提交历史使呈线性关系。比较比较细心的小伙伴可能会注意到 merge 也可以达到合并的效果,那它和 rebase 有什么区别呢?

通过下图可以很直观的看出它俩的区别,首先我们得知道 merge 是合并而 rebase 复位基底的意思,他们都是从一个分支合并到当前所在分支,区别在于 merge 合并后生成一个新的提交记录,且提交历史依然处于分叉状态而 rebase 合并后会自身分支的提交历史挪动到当前分支中且不会新增新的提交节点记录,呈线性记录让我们开起来分叉清晰。

rebase vs merge

  • 整理提交历史记录

很多时候我们的提交历史中可能会存在一个功能多个提交的场景,当时这些提交很多都是为了解决或者开发同一个需求,为了便于更好的 codereview 尽量可以避免这种提交历史,由此我们可以通过 git rebase --i ,通过这条指令你可以对你的提交历史做顺序/合并/编辑或者删除等操作,具体的操作可以点击了解更多

git revert

git revert 可以被认为是“撤消”类型的指令,但是它和 reset 的回退不一样,它不是从项目提交历史中直接删除提交,而 revert 提交一个新的版本,将需要 revert 的版本的内容再反向修改回去,这可以防止提交历史的丢失,对于的提交历史的完整性和可靠的协作很重要。

# 撤销前一次 commit
git revert HEAD

# 撤销前n此的 commit
git revert HEAD~n

# 撤销指定某个commit
git revert commitId

git cherry-pick

git cherry-pick 使用起来非常简单,它可以从一个分支中选择一个或者多个提交应用到其他分支中。例如你不小心在错误的分支中进行提交时可以切换到正确的分支选择对于的提交提交到属于它的位置。

cherry-pick

git reflog

Reflog 是一种记录分支尖端何时更新的机制。该命令用于管理其中记录的信息。

也就是说 git relog 可以显示我们在 git 中的所有操作记录,例如提交,合并以及回退等操作,通过这些记录我们可以及时的恢复本地错误操作(例如回退/变基等操作)带来的问题。

reflog

git reflog 算是我们最后的后悔药,给予了我们恢复因为错误操作导致的提交历史丢失,不过我们自身也得养成好的提交习惯来防止。

git remote

git remote 主要可以用于创建、查看和删除与其他存储库的连接。

# 查看git仓库的远端配置
git remote

# 查看远程仓库的链接
git remote -v

# 添加远端仓库链接
git remote add <name> <url>
# 例如
git remote origin ****

# 删除远端仓库链接
git remote rm <name>

# 重命名远端仓库
git remote rename <old-name> <new-name>

git clone

git clone 可以将远端仓库文件及提交记录克隆至本地。

# 克隆远端仓库到本地
git clone <remote-repo-url>

# 克隆远端仓库指定分支到本地
git clone -b <branchName> <remote-repo-url>

git fetch

git fetch 从远程仓库中拉取最新的变更数据,将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。但它不会改变你本地仓库的状态。它不会更新我们本地的任何分支以及文件,可以让我们在不影响本地分支的情况下对远端变动做一些了解。如果需要将远端仓库的变动同步到我们的本地分支可以使用 merge 操作。

# 获取远端仓库所有变更
git fetch origin

# 获取远端仓库指定分支的最新变更
git fetch origin dev

# 将远端分支的变动同步到本地分支中
git checkout dev
git merge origin/dev

# 拉取远端分支并同步本地分支
git fetch origin main:feat

git pull

git pullgit fetch 同样可以获取远端仓库的数据,当时不同点是 pull 会将远端仓库的变动同步到本地分支,可以看成 git pull = git fetch + git merge。 基本用法:git pull <远程主机名> <远程分支名>:<本地分支名>

git pull origin main:feat

# 如果是将远端分支与本地当前分支合并时冒号后面部分可以省略
git pull origin <branchName>

# 远端已删除的分支同步删除本地分支
git pull -p

git push

git push 用于将本地仓库变更提交到远程仓库。

# 删除远端分支
git push <remote> --delete <branchName>
# 等价于
git push <remote> :<branchName>

# 将本地分支推至远端仓库,如果远端不存在则创建对应的分支
git push <remote> <branchName>

# 将所有标签推送至远端仓库
git push <remote> --tags

# 将单个标签推送至远端
git push <remote> <tagName>

# 删除远端标签
git push -d <remote> <tagName>

# 强推本地分支/标签至远端
git push <remote> <branchName> -f 

参考文章

Git内部原理之Git对象

廖雪峰老师的Git教程

往期文章推荐

真香~,咱也撸一个redux

奥利给,从零打造一个 MiniVuex

玩转JS中的堆栈内存及函数底层处理机制