1 Git基础

本机测试环境:macOS 11

01 | 课程综述

VSC:版本控制系统

  • 集中式——客户端必须时刻和服务器相连
    • SVM
  • 分布式——服务端和客户端都有完整的版本库
    • Git,Linus开发
    • 基于Git的开源社区——背景:DevOps时代,开发运维
      • GitHub——全球最大
      • GitLab——公司常用;免费、提供了持续集成-CI;如阿里云、点评

02 | 安装Git

03 | 使用Git之前需要做的最小配置

## 添加name和email配置
git config [--local | --global | --system] user.name 'Your name'
git config [--local | --global | --system] user.email 'Your email'
## 查看现有配置
git config --list [--local | --global | --system]
## 区别
##  --local:本仓库
##  --global: 当前用户的所有仓库
##  --system: 本系统的所有用户
  • 作用:记录人员信息,CR(Code Review,代码审查)时也可以对应发邮件提醒

04 | 创建第一个仓库并配置local用户信息

  • 创建仓库的两种方式,均会生成.git文件夹
## 在已有项目文件夹里创建git仓库
git init
## 新建一个名为[Project Name]的git仓库
git init [Project Name]
  • 如果需要单独配置local用户信息,使用--local配置即可,该种配置优先级最高,见上一节
  • 进行一次简单的commit❗️
## 添加文件到该git项目中,略
## 添加要commit的文件到
git add [File Name]
### PS:添加当前路径下的所有文件—— git add .
## 附带信息进行commit操作
git commit -m'Some Message'
### 查看git状态,如:哪些文件待commit,哪些未被跟踪
git status
### 查看git记录,如:commit的记录
git log

05 | 通过几次commit来认识工作区和暂存区

  • 暂存区❗️——Git的精髓之一
    • 使用场景:开发两套方案,第一套方案做好后先暂存,然后做第二套方案,如果第二套方案没有第一套好,可以再回到第一套
    • PS:
      • 如果要把工作区的某个 file 替换为暂存区,则 git checkout -- file
      • 如果要把工作区变更的所有文件都恢复成和暂存区的一样,则 git checkout *
  • git add -ugit add .
    • 前者:只操作已经被跟踪的文件,即将文件的修改和删除,添加到暂存区
    • 后者:还会操作文件的新建,即将文件的创建、修改和删除,添加到暂存区
    • PS: git add all/-A 针对整个仓库,不只是当前路径
  • 具体操作类似上一节——一次简单的commit

06 | 给文件重命名的简便方法

  • 细化步骤
## 重命名
mv readme readme.md
## 添加新文件readme.md
git add readme.md
## 删除被跟踪的旧文件readme
git rm readme
## 提交
git commit -m"rename"
  • 使用 git reset --hard 回退到最新的一次提交,即清空暂存区
  • 再展示精简步骤
## 一条命令顶上面三条
git mv readme readme.md
## 提交
git commit -m"rename"
  • 实操展示

通过git status实时观察暂存区状态。

PS:第一种方式git status也能检测出是renamed

07 | 通过git log查看版本演变历史

## 查看简洁的单行历史
git log --oneline
## 查看最近的4条历史
git log -n4
git log -4 
## 查看所有分支的历史
git log --all
## 查看指定分支(名为BRANCH_NAME)的历史
git log BRANCH_NAME
## 查看图形化的版本历史
git log --graph
## 组合使用:查看所有分支最近4条图形化的单行历史
git log --all -n4 --graph --oneline
## 跳转到网页版的git log帮助文档
git help --web log
  • git log --all -n4 --graph --oneline 效果图

附加命令

  • git branch 查看本地有哪些分支, -a 会同时查看本地和远程的分支
  • git checkout -b <new_branch> []
    • 创建名为new_brach(以commit hash值为start point的commit为基)的新分支
    • 为了简便,start point可以选取hash值的一个片段,只要能够唯一标识commit即可
  • git commit -am'msg'
    • 直接将工作区和暂存区中的文件添加到版本库中,相当于使用了add
    • ⚠️:只能提交已经被跟踪的文件,新建的文件还是要先通过 git add . 添加到暂存区

PS

  • 可以在~/.zshrc里,通过别名设置更友好的log显示方式,设置好后使用 lg ... 即可
alias lg="git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
  • 一般地,单字母的选项对应 - ,非单字母的选项是 --

08 | gitk:通过图形界面工具来查看版本历史

  • Mac下安装: brew install git-gui
  • 使用: gitk
  • 界面简单展示
  1. 选择View-New View后,选中All refs后,即可展示所有分支、Tag信息
  2. 右键某一个commit有很多操作,如Create tag

PS

  • 为什么一次commit既有作者Author,又有提交人Committer
    • 尊重版权
    • 比如 git cherry-pick 命令:将指定的commit应用于其他分支
    • 具体使用参考git cherry-pick 教程——阮一峰
  • 另推荐一款Git图形界面工具:Sourcetree,其实工具之间大同小异,看个人习惯

09 | 探密.git目录

Git具有优秀的存储能力,不需要网络,本地也能做版本管理

  • 🌟4个关键文件:HEAD、config、refs、objects,下面一一剖析
  • ❗️3个重要对象:commit、tree、blob,PS:tag
  1. 关系图如上,参考GIT对象模型——Git Community Book 中文版
  2. blob:全称Binary Large Object,即二进制大型对象,可理解为文件对象
  3. 下一节还会深入讲解,先来看看4个关键文件~
  • HEAD:指向当前正在工作的分支

手动修改HEAD里的分支,效果同 git checkout

  • config:本地仓库(local)配置信息

主要关注user信息

手动修改config文件,效果同 git config --local

  • refs:包含heads和tags文件夹
  • heads里每个分支存放的是一个commit的hash值
  • tags里每个里程碑存放的也是一个commit的hash值
    • 也可能是一个tag的hash值,其又指向commit
  • git cat-file :显示版本库对象的信息
    • -t :类型
    • -p :内容(直接用cat命令可能会显示乱码,因为这些对象需要解析)
    • 后者接唯一的hash值或文件名即可

PS:细心的你会发现一个commit里存储了一个tree的hash值,是不是和一开始的关系图串起来了,再试试查看tree的内容

  • objects:存放所有对象,包括tree / blob / commit
  • 1)大部分文件夹📁都是以hash值的前2位字符命名,里面存放38位字符命名的文件
    • 由这40位字符组成唯一的hash值,指向一个对象
  • 2)tree对象里存储了blob对象信息,blob对象的内容就是某个文件的内容,如css.x
    • tree对象里还可以存储另一个tree对象信息
    • 只要文件的内容相同,在Git眼里它就是唯一的一个blob
  • 3)blob对象内容与其指向的文件内容一致
    • 一定会一致吗?不一定,和版本有关
  • PS:还有pack文件夹和info文件夹
    • 前者用于存放打包的松散的文件夹,后者与前者相配合

10 | commit、tree和blob三个对象之间的关系

上一节提到了它们仨,这一节再具体看一个commit,加深理解

  • 这是它们三者的关系图,可以关注箭头的走向,参考GIT对象模型——Git Community Book 中文版
  • 解析
    • 一个commit里的内容由一棵tree来管理,有且只有一棵
      • 存放当前commit里所有文件夹和文件的一个快照
    • 一棵tree里可以有多个子tree和多个blob
    • 一个blob里存储的就是文件对象的具体信息
      • 文件内容一样则唯一,与文件名完全无关
  • 示例

commit👉tree👉blob + tree

PS: lg 是在第7节配置的 git log 友好显示版命令

11 | 小练习:数一数tree的个数

新建的Git仓库,有且仅有1个commit,仅仅包含/doc/readme
请问内含多少个tree、多少个blob?

下面开始试验🧪

## 首先新建Git仓库
git init watch_git_object
## 进入仓库,创建doc文件夹,并添加readme
cd watch_git_object
mkdir doc
echo "hello, world" > doc/readme
## 此时观察.git/objects里有没有文件类型的文件:空
find .git/objects -type f
## 把doc添加到暂存区
git add doc/
## 再次观察:Git生成了一个blob对象,里面记录了readme的内容
find .git/objects -type f
  • 添加到暂存区时,就会产生blob对象
  • ❗️此时还没有commit、tree对象,下面进行commit
## 进行commit
git commit -m'Add readme'
## 观察.git/objects里的文件:已经有4个了
find .git/objects -type f
## 逐个检查它们的类型和内容,如下图所示
git cat-file -t ***
git cat-file -p ***
  • 可以看出新生成了:1个commit、2个tree
  • 它们的关系是:
    • commit👉tree(commit对应一个tree)👉tree(doc文件夹)👉blob(readme文件)

12 | 分离头指针情况下的注意事项

detached HEAD

  • 产生:切换HEAD到某一个commit上(即使是某分支最新的commit)

git checkout <HASH_OF_COMMIT> ,而非某一个分支

此时就会出现detached HEAD情况,你可以做一些试验性操作,并进行commit

1)如果不想保留这些commit:可以直接切换回分支,不做其它额外的操作

2)如果想保留这些commit:则需要在此创建一个新的分支,从而与分支挂钩

  • 接着对readme做一些修改,并进行提交,当切换回某个分支时

Git友好提示:如果想要保留刚才的提交,可以通过commit的hash值创建一个新的分支

借助gitk查看此时分支的情况,可以看到,看不到刚才commit的影子

执行上述友好提示中的 git branch fix_readme 34976f8

前面做的提交已经与fix_readme分支联系起来了

  • 小结
    • 使用场景:做一些试验性尝试
    • ⚠️:当想做保留所做的提交时,要与某个分支挂钩

13 | 进一步理解HEAD和branch

  • 带着2个问题学习:
  1. 新建分支的时候,HEAD会发生什么变化?(HEAD指向)
  2. HEAD相关的使用技巧是什么?(指代commit,特殊标记 ^~

下面一一进行探讨:

  • 1

HEAD可以指向具体的分支,也可以指向具体的commit

通过 git checkout -b <NEW_BRANCH_NAME> <BASE_BRANCH_NAME/HASH_OF_COMMIT> 命令,基于某个分支或提交,创建新的分支,并切换到新分支上

可以发现:HEAD从指向分离头指针,转变到,指向fix_new分支上

再观察HEAD文件的信息

其实,HEAD指向某个分支,而该分支指向某个特定的commit

  • 2

既然HEAD最终指向一个commit,那也可以用它来指代commit的hash值啦~

比如在使用 git diff <A> <B> 比较两个commit时

上面3条命令是等价的,即:

git diff 34976f8 a60a3d5git diff HEAD HEAD~git diff HEAD 'HEAD^'

⚠️:

HEAD的 ^~ 标记都可以获取HEAD的祖先commit,它们的区别可参考下图:

这个分支图的方向和 git log --graph 生成的图方向相反,即A是最小的;且结合了有merge分支的情况

参考What's the difference between HEAD^ and HEAD~ in Git?——StackOverflow

PS:在zsh下, ^ 有特殊含义,所以需要用引号包起来

学到这里,是不是更新了自己对Git的认识呢~是否期待Next Part呢?!