# 一、基础必知

# 1、什么是 git?

  官方描述:

Git 是一个免费和开源的 分布式版本控制系统,旨在以速度和效率处理从小型到大型项目的所有内容。

Git 易于学习, 占用空间小,性能快如闪电。具有廉价的本地分支、方便的暂存区域和 多个工作流等功能。

  相关链接推荐:

git 官方文档 (opens new window)

git 操作流程

git 提交(commit)对象

文章参考 (opens new window)分支简介 (opens new window)git 对象 (opens new window)

  具体位置: .git / objects /

  每一次 add、commit 都会自动产生 SHA-1 校验和文件

git cat-file -p 00eb5c2b

  根据 cat-file,我们可以 git 内部实现原理如下:

  上面我们已经知道了,每一次提交的 commit obj 会作为一次快照,供我们进行其他操作

git log --pretty=oneline

  当然,我们在 git 官网上也能找到一些不错的原理图:

# 2、统一声明

  后面描述的 git 命令: []中的内容为可选内容,|表示或者的意思,<>中的内容为必写内容

  默认有 Linux 命令基础、git 基础。

Git Bash 就是一个 shell(基于 CMD),是 Widow 下的命令行工具,可以执行 Linux 命令

# 例如:登录远程服务器
ssh root@8.34.138.11

# 3、git 常用命令

  • clone 旧项目开发
git clone "url"

git status -s
git add .
git commit [-a] -m"message"

git pull
git push
  • init 新项目开发

push.default (opens new window)

# ======= 项目本地搭建 =======
git init
git status -s
git add .
git commit [-a] -m"message"


# ======= 项目远程推送 =======

git remote add origin "url" # 仓库关联
git push -u origin master:main # 初次push的同时将 origin/main 设置为上游分支


# ======= 项目后续开发 =======

# push.default的默认值为 simple
git push # 推送主分支
git push origin owner_dev # 推送其他分支

git pull
git pull origin zhangsan_dev
详细解读
# 1、查看文件状态
git status [-s]
# 【注意:-s表示以简写的形式展示结果🚩】

# 2、提交至index区
git add [<fileName> | . | -u | -A]
# 【注意: . 只对当前目录有效,但不包括D状态的文件。
        # -u 对整个git目录有效,但不包括??状态的文件。
        # -A 提交所有。】

git commit [-a] [<fileName>] -m"<对应commit的message信息>"
# 【注意:信息内容使用双引号。
#        -a表示执行commit的同时也执行了add操作】


git pull [<远程仓库名> <远程分支名>:<本地分支名>]
# 【示例: git pull origin dev:localDev】

git push [-u <远程仓库名> <本地分支名>:<远程分支名>]
# 【示例:-u origin localBugFix:bugFix】

# 其他
# git branch --set-upstream-to=origin/main # 上游分支
# git config push.default upstream  # 🤔(没有加--global,仅当前仓库生)

# git fetch
# git merge --allow-unrelated-histories

# 二、git 个人配置

直接查看与编辑配置文件:

手动配置:C:\Users\<电脑用户名>\.gitconfig

# 查看
git config --list [--global] # 默认为local

# 编辑(以默认的编辑器打开)
git config --edit [--global]

# 1、全局配置

# ① 署名/邮箱

git config --global user.name "<署名>"
git config --global user.email "<邮箱>"

# ② 大小写

  有兴趣的话,还可以看看git mv (opens new window)

git 注意事项

  git 两个小的注意事项:

  • 空文件夹是不会被 git 捕捉到的
  • 文件名的大小写变更 git 也是不到捕捉

  也就是说你在本地新增的空文件夹和修改文件名的大小写是不会被记录的,简单的说是无效的。

如果我们修改了文件名的大小写并且将其名称应用到了项目中的某段代码中,在本地是不会有什么问题,但在远程由于文件名的修改 git 并没有 push 到远程,在部署时就会发生找不到文件的情况。

git config core.ignorecase false # 不忽略大小写(local中才会生效)

# ③ 默认编辑器

# 默认为vim编辑器

# 1、设置vscode为默认编辑器
git config --global core.editor "code --wait"

# 2、还原vim为默认编辑器
git config --global --unset core.editor

# ④ main 分支配置

  自 2020 年 10 月 1 日开始,GitHub 新建仓库的默认分支名由“master 分支”改为了“main”。

# git init时的默认分支配置
git config --global init.defaultBranch main

  对创建但未git push的原 master 分支项目:

# 当前master分支改名为main
git branch -M main

# ⑤ 解除 SSL 验证

  如果你使用了一些工具进行 github 加速(例如:Watt Toolkit 等),但发现使用 git clone 等命令时出现报错信息:SSL certificate problem: unable to get local issuer certificate

  这个时候可以进行如下操作:

git config --global http.sslverify false

# ⑥ 提示换行符转换

  不同操作系统使用不同的换行符格式(lf | cr | crlf),设置为 false 可以确保代码在版本控制系统中保持不变,不受操作系统的影响。

比如:拉取 nestjs 的示例项目typescript-starter (opens new window)不设置就会报错

问题思考?

  既然在团队开发中会遇到这样的问题,那如何根治呢?

我第一个想到的就是 .editorconfig,通过其中的 end_of_line 配置项,让项目的整个开发流程中,保持换行符风格一致,不受系统和 IDE 编辑器的影响。

git config --global core.autocrlf false

# 2、日志配置

按空格查看下一条,按 Q 键退出

# 查看commit记录(当前分支)
git log --oneline

# 查看commit记录(所有分支)
git log --pretty=oneline --graph --decorate --all

# 查看操作记录
git reflog
git 的校验和

  git 校验和 有人也叫 Commit ID

git 用 SHA-1 散列(Hash)生成校验和(由 40 个十六进制字符组成),是基于 git 中文件的内容和目录结构计算出来的

  配置个性化的 git 命令别名来查看 git 分支结构:

git config --global alias.slog "log --oneline --decorate --graph --all"

git config --global alias.olog "log --pretty=format:'%h | %an | %ar | %s'  --decorate --graph --all"

git config --global alias.plog "log --pretty=format:'%C(yellow)[%h] %C(magenta)%an %C(cyan)%ad  %C(auto)- %s' --date=format:'%Y-%m-%d %H:%M:%S %A' --decorate --graph --all"

# 3、忽略文件配置

官方文档 (opens new window)

文件 .gitignore 的格式规范如下:

  • # 开头的是注释
  • / 结尾的是目录
  • / 开头防止递归
  • ! 开头表示取反

glob 模式是指 shell 所使用的简化了的正则表达式:

  • * :匹配零个或多个任意字符
  • [abc] :匹配任何一个列在方括号中的字符
  • ? :只匹配一个任意字符
  • ** :匹配任意中间目录

示例:

  • .gitignore

通常情况下,前端脚手架会自动生成并配置

忽略文件查询 (opens new window) (通常是 java、C++等开发者)

# 忽略所有的 .a 文件
*.a

# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a

# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO

# 忽略任何目录下名为 build 的文件夹
build/

# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt

# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf

# 三、本地仓库

# 文件状态 ✨:

git status -s

通过简写的形式查看文件状态,红色表示在文件在workspace工作区,绿色表示文件在index暂存区。字母A表示首次出现的文件,M表示 commit 过的文件,D表示已经执行删除操作的文件

MM:左侧表示暂存区状态,右侧表示工作区状态

符号解析

工作区文件的几种状态:

  未跟踪(??)、未修改、已修改(M)

暂存区文件的几种状态:

  已暂存(M)

图示:

# 1、提交

# ① 工作区提交到暂存区
git add [<fileName> | . | -u | -A]
# 【注意: . 只对当前目录有效,但不包括D状态的文件。
        # -u 对整个git目录有效,但不包括??状态的文件。
        # -A 提交所有。】

# ② 暂存区提交到本地仓库
git commit [-a] [<fileName>] -m"<对应commit的message信息>"
# 【注意:信息内容使用双引号。
#        -a表示执行commit的同时也执行了add操作】

# 2、撤销

# ① 已暂存 撤销到 已修改
git reset [--mixed] [HEAD] [<fileName> | .]
# ② 已修改 撤销到 未修改
git checkout <fileName> | .

# ③ 已暂存 撤销到 未修改
git checkout HEAD [<fileName> | .]

# ④ 清除未跟踪
git clean -f <fileName> | .

# 3、比较

# ① 已暂存 和 已修改
git diff <fileName>
# ② 已修改 和 未修改
git diff HEAD <fileName>

# ③ 已暂存 和 未修改
git diff --cached <fileName>

# 4、删除

# ① 删除 未修改
git rm <fileName>

# ② 删除 已暂存 和 已修改
git rm < -f | --cached > <fileName>

# 四、版本管理

# HEAD 指针 ✨:

提示

  关于 HEAD 指针,其指向就是 .git / logs / refs / heads / master 文件中存储的 SHA-1 校验和

  其中,HEAD 默认指向的是当前分支最后一次 commit

  下面是指定 commit 版本 的特殊表示方法:

  • HEAD :最后一次 commit 版本
  • HEAD^ :前几次 commit 版本(几个^代表前几个)
  • HEAD~n : 前 n 次 commit 版本
  • <hash 值> :指定的 commit 版本(前 7/8 位即可)

# 1、reset 版本回退

相同点

  reset 和 revert 都是回到了指定的 commit 版本。

不同点

  reset抛弃了指定版本后面提交的 commit 版本,直接回到指定的 commit 版本。

  revert相当于复制了一份指定的 commit 版本,并进行了提交。

①reset 版本查看 命令 🚩:

# 1、保留 workspace工作区和index缓存区的文件
git reset --soft <commit版本>

# 2、保留 index缓存区的文件(默认)
git reset [--mixed] <commit版本>

# 3、完全回到指定的commit版本✨
git reset --hard <commit版本>

【注意:

  使用 reset 命令时,最好是针对未git push过的分支或 commit 版本,不然会因为本地仓库版本落后与远程仓库版本而git push不成功】

取消上一次提交的 commit
git reset HEAD^

这个命令会将 HEAD 指向上一次的 commit,并且保留缓存区的修改。如果你想彻底取消上一次的 commit,并且丢弃你的修改,可以使用以下命令:

git reset --hard HEAD^

这个命令会将 HEAD 指向上一次的 commit,并且彻底丢弃你的修改。注意,这个命令会永久删除你的修改,所以请谨慎使用。

②revert 版本回退 命令

git revert <commit版本>

【使用 revert 就不会有使用 reset 后git push不成功的问题】

# 3、tag 标签

常见的版本修饰词

  • beta:测试版
  • release:发行版
  • lts:长期维护版本
  • rc:即将作为正式版发布
  • alpha:内部版本
# 1、创建
git tag v1.0.0
# git tag -a v1.0.0 -m"<对应标签的message信息>"

# 2、查看tags
git tag
# git show v1.0.0
# git tag -l "v3.3.*"
# git checkout v1.0.0

# 3、删除
git tag -d v1.0.0
# git push origin -d v1.0.0

# 4、上传tags
git push orgin v1.0.0
# git push orgin --tags

# 3、rebase 重写

官方文档:Git 工具 - 重写历史 (opens new window)

# 修改最近一次commit的message
git commit --amend

  对于 git 的重写历史操作,最好是发生未git push过的分支或 commit 版本,不然会与远程发生冲突

当然,如果该分支是由你一人单独开发,大不了使用 git push -f,反正对其他人由没什么影响

  关于 rebase,它可以做更多事情:

  • 重写的区间
# 列出待重写的commit区间
git rebase -i HEAD~n
# git rebase -i [startpoint]  [endpoint]

# 选择要执行操作
# ……
  • 可执行操作
与前几次 commit 合并

  如果采用的是fixup,那么默认 commit 合并的 message 为:删除 bbbb

  • 示例 1
git rebase -i HEAD~5

# 修改如下(弹窗1):
pick 6b3a892 新增aaaa
pick 1a91eaf 新增bbbb
pick c08eb92 新增cccc
pick 93aa96d 删除bbbb
squash f8a99f0 新增dddd  # 与前一个commit合并

# 修改如下(弹窗2):
删除了bbbb并新增dddd # 删除用于提示的message,然后编写 commit合并的message
  • 示例 2
git rebase -i HEAD~5
# 修改如下(弹窗1):
pick 6b3a892 新增aaaa
pick 1a91eaf 新增bbbb
squash c08eb92 新增cccc # 与前一个commit合并
squash 93aa96d 删除bbbb # 与前一个commit合并
pick f8a99f0 新增dddd
# 修改如下(弹窗2):
新增cccc # 删除用于提示的message,然后编写 commit合并的message
为某次 commit 追加文件

  其实想想也知道,如果你为前面的任何一个 commit 添加内容,肯定会发生冲突(后面的版本没有相关记录,冲突虽然可以解决,但后面与该文件相关的 commit 区间会合并为一个 commit)。

  所以,对于 rebase 中的 edit,通常可以 ✍ 应用于:

  • 上次的项目大版本发布时,忘记追加 CHANGELOG.md 或者 README.md 内容
  • 在项目创立的初期,忘记添加 版权文件 LICENSE
git rebase -i HEAD~n

# 在编辑器中将 "pick" 改为 "edit" 并保存文件

# 1、修改message
git commit --amend
git rebase --continue
# 结束

# 2、修改内容(一般新增文件🚩:例如:README.md)
git add .
git commit --amend
git rebase --continue # 更改的message
# 如有冲突,解决冲突
git commit --amend
git rebase --continue # 合并的message
# 结束

# 其他
git rebase --abort # 取消rebase(不想要此次rebase时)
# 默认值
pick <commit> # use commit

# 修改message
reword <commit> # use commit, but edit the commit message

# 修改指定commit版本中的内容和message
edit <commit> # use commit, but stop for amending

# 与前一个commit版本合并
squash <commit> # use commit, but meld into previous commit
fixup <commit> # like "squash", but discard this commit's log message

# 4、stash 存储 *

注意:

  ① 经过git stash存储后的文件,在git apply|pop 恢复时,文件全部显示为工作区文件状态。

  ② 暂存区的文件经过stash会变成工作区文件( MM状态的文件在stash时,会选择 M状态的文件进行存储 )。

应用场景:

修复紧急 bug/需求时,但又不想 commit 当前工作区正在编写的文件,可以先stash存储未提交的文件。然后 git pull 先处理的紧急 bug/需求,解决完需求后再apply恢复即可。

git stash

git stash list

git stash apply stash@{n}
git stash pop stash@{n}

git stash drop stash@{n}
git stash clear

# 五、分支

# master 分支 ✨:

  Git 的分支,其实本质上仅仅是指向提交(commit)对象的可变指针。

  其中,Git 的默认分支名字是 master。

为什么需要分支
  • bug 修复

当我们切换到 master 分支,对 hotfix 分支进行 merge 合并时,可能会遇到合并冲突

解决办法:解决冲突后单独作为一次提交

git flow 工作流

  常见的工作流如下(工作流为 development 分支):

# 1、本地操作

# 1、创建、切换
git branch <分支名>
git checkout <分支名>
# git checkout -b <分支名>

# 2、查看
git branch -v
git branch -a
# git branch --merged
# git branch --no-merged

# 3、删除
git branch <-d | -D > <分支名>
# 【-d 和 -D的区别为:-D可以强制删除未merge的分支】

# 4、合并 🎉
git checkout main
git merge fix_branch # 将fix分支合并到main分支(这样冲突时main优先级高)

# 5、重命名
git branch -m current_latest_Name # 重命名当前分支
git branch -m old_branch new_branch # 重命名其他分支

思考?

如何更改当前项目的主分支?

# 2、远程分支

  其实我们可以使用-u设置主分支的推送方式。而其他子分支则使用标准的 push 方式。

# 推送本地分支
git push -u origin <本地分支名>

# 拉取远程分支
git pull origin <远程分支名>

# 删除远程分支
git push origin -d <远程分支名>

# 3、rebase 变基

  整合分支的方法除了merge还有rebase,与 merge 不同的是,rebase 会将 commit 链变为线性结构。

rebase 与 merge 的区别

官方:变基 (opens new window)

分支整合目标:

  • merge 方式
  • rebase 方式

提示

  rebase 虽然可以简化历史记录,但也要注意使用的时机(比如:永远不要在主分支 main 上进行 rebase 操作

git checkout feature
git rebase master

git log --pretty=oneline

git checkout master
git merge feature

# 4、cherry-pick 遴选 *

参考链接 (opens new window)

应用场景

当前你在负责开发一个分支实现功能 A(完成,且为一个 commit 版本)、功能 B(未完成)。由于上线测试需求,要先功能 A 合并到 master 分支上,此时就可以使用cherry-pick将已实现功能 A 的 commit 版本单独复制并合并到 master 分支上。

git cherry-pick [ -e | -n ] <commit版本>
# 【注意:-e 表示会打开编辑器编辑合并的commit信息
#        -n 表示只是更新工作区、缓存区,不产生commit版本】

若发生合并冲突:

# 继续合并
git add.
git cherry-pick --continue

# 放弃合并
# 1、回归原始状态
git cherry-pick --abort
# 2、保留冲突状态在工作区
git cherry-pick --quit

# 六、远程仓库

# 1、基础命令

# 1、拉取到工作区
# 方式1 🎈
git pull [<远程仓库名> <远程分支名>:<本地分支名>]
# 【示例: git pull origin master:master】
# 强制拉取
git fetch --all && git reset --hard origin/master && git pull
# 【说明:先获取远程更新的数据 和 回退工作区/暂存区/版本库,最后再pull】

# 方式2
git clone <url> [.]
# 【克隆下来的远程仓库名默认为origin】
# 等效于:
git fetch # 先拉取远程仓库中新的commit(得到🚩远程仓库中所有分支的引用:参考.git/FETCH_HEAD文件)
git merge # 再合并远程仓库中所有分支的引用

# 2、推送到远程仓库 🎈
# -u:表示设置为默认推送方式
git push [-u <远程仓库名> <本地分支名>:<远程分支名>]
# 【示例:-u origin master】
# 强制推送
git push -f

# 2、关联远程仓库

通常建立的 GitHub 仓库会有提示信息:

git remote add origin <url>

git push -u origin master

# 1、关联
git remote add <远程仓库名> <url>

# 2、查看
git remote [-v]

# 3、移除关联
git remote remove <远程仓库名>

方式一:

git init
git remote add origin <url>
git push -u origin master

方式二:

git clone <url>
git remote add origin <url>
git push -u origin master

# 3、解决冲突

  防止发生冲突的关键是:潜意识里要提高远程仓库的地位(即 进入开发状态前,先 pull ),并且不要直接在仓库里修改代码。

① pull 冲突

  • 未 commit 过,pull 时发生冲突 (可以先 ✨stash 工作区内容,然后 pull)

完全可以避免,进入开发状态前先 pull

git reset --soft HEAD
git stash .
git pull
git apply stash@{n}
# 【此时就可以在工作区进行版本比较了】
  • 已 commit 未 push,pull 时发生冲突

一般不存在,进入开发状态前已经 pull 过了

# 此时进行pull操作毫无意义,直接push代码即可
git push

② push 冲突

  冲突产生原因:有人已经 push 的文件和你将要 push 的文件发生冲突

git pull
# ……然后直接修改冲突内容
git add.
git commit -m"<对应commit的message信息>"
git push

② 注意事项

  若直接手动解决冲突文件,默认采取的是 merge 操作,并且会产生一次 commit。

  在解决 push 冲突时,我们可以发现了一个 后来者居上原则(当前解决冲突的人可以覆盖先前的人写的代码)。所以发生冲突时,一般要进行沟通协调;不然前者就得花时间去看日志了。

更新于 : 7/8/2024, 10:21:14 AM