一、git简介
Git 是一个免费的开源分布式版本控制系统,旨在快速高效地处理从小到大的所有项目。Git 易于学习,占用空间小,性能快如闪电。 它优于 Subversion、CVS、Perforce 和 ClearCase 等 SCM 工具,具有便宜的本地分支、方便的暂存区和多个工作流等功能。 — https://git-scm.com/
1.1 git仓库四大区域
图/git四大关键区域关系
掌握了这张图里的内容,git的实用技能就掌握80%了:
- 工作区:用来编辑保存项目文件的地方,也是用户能直接操作到的地方。例如:在任意编辑器中输入"hello git",点击保存后,内容就存于工作区中。
- 暂存区:用来保存下次即将提交到版本库的文件列表信息,一般在git仓库中,是一个叫index的文件。例如:输入指令
git add . 后,工作区中的变更内容就记录到暂存区。 - 版本库:也称为本地仓库,也就是你电脑中的它和其它无数个开发伙伴电脑中的它,组成了git的分布式版本控制系统。例如:输入指令
git commit 后,暂存区中记录的内容就被推送到版本库中。 - 远程仓库:托管在因特网或其它网络中的项目的版本库,你可以理解为是版本库(本地仓库)在云端的克隆版。有了远程仓库,所有开发小伙伴就可以基于它这个桥梁,实现本地仓库的版本协同。例如:输入
git push origin master 则可以将版本库中的master分支内容更新至远程仓库;输入git pull origin master 就可以将远程仓库中的内容更新至本地(包括工作区、暂存区、版本库)。
1.2 存储原理
注意:本节内容可以忽略(适当了解更好)。 💲 只要记住这一点就好:初始化仓库后,.git文件中的内容不要删除。
- 初始git仓库
mkdir storage
cd storage
# 初始化git仓库
git init
# 配置user.name
git config --local user.name "Pioneer4"
# 配置user.email
git config --local user.email "electricalqzhang@gmail.com"
# 查看.git下的目录结构
tree .git/
/*
.git/
├── branches 不这么重要,暂不用管
├── config git配置信息,包括用户名,email,remote repository的地址,本地branch
| 和remote branch
├── description 该git库的描述信息,如果使用了GitWeb的话,该描述信息将会被显示在该repo的页面上
├── HEAD 工作目录当前状态对应的commit,一般来说是当前branch的head,HEAD也可以通过git
| checkout命令被直接设置到一个特定的commit上,这种情况被称之为 detached HEAD
├── hooks 钩子程序,可以被用于在执行git命令时自动执行一些特定操作,例如加入changeid
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ └── update.sample
├── info 不这么重要,暂不用管
│ └── exclude
├── objects 保存git对象的目录,包括三类对象commit,tree和blob
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
*/
- 进行一次提交
# 进行一次提交
echo "init project" >> readme.md
mkdir src
echo "hello git" >> src/db.log
git add .
git commit -m "init prj"
# 查看.git中objects的目录结构
tree .git/objects/
/*
.git/objects/
├── 05 commit_id的前两位
│ └── 3baa2271d8de75b8621f08ae6fc6694bf31593 文本内容的SHA-1哈希值作为校验和(取38字符)
├── 69
│ └── b0bd61b54155b5ff6ed45847e4e01767ca14db
├── 8d
│ └── 0e41234f24b6da002d962a26c2495ea16a425f
├── a1
│ └── 0a826fc98d5263841dacf5c66014bb1f7f4acd
├── d6
│ └── c639c45454a483b6f0e5fa6828d45f3ca5c7f3
├── info
└── pack
*/
说明:Git Object目录中存储了三种对象:Commit, tree和blob。Git为对象生成一个文件,并根据文件信息生成一个 SHA-1 哈希值作为文件内容的校验和,创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 ,将该文件保存至子目录下。
- 查看Git Object的内容
cat .git/HEAD
> ref: refs/heads/maste
cat .git/refs/heads/master
> 69b0bd61b54155b5ff6ed45847e4e01767ca14db
# 查看文件类型
git cat-file -t 69b0bd
> commit
# 查看文件内容
git cat-file -p 69b0bd
> tree d6c639c45454a483b6f0e5fa6828d45f3ca5c7f3
> author Pioneer4 <electricalqzhang@gmail.com> 1636025068 +0800
> committer Pioneer4 <electricalqzhang@gmail.com> 1636025068 +0800
>
> init prj
# 查看tree中的内容
git cat-file -p d6c639
> 100644 blob 053baa2271d8de75b8621f08ae6fc6694bf31593 readme.md
> 040000 tree a10a826fc98d5263841dacf5c66014bb1f7f4acd src
# 查看readme.md中的内容
git cat-file -p 053baa
> init project
由此可以分析,Git Object中存储的对象和关系如下:
HEAD---> refs/heads/master--> 69b0bd(commit)
+
|
v
d6c639(tree)
+
|
+---------+----------+
| |
v v
053baa(blob) a10a82(tree)
readme.md src
+
|
v
8d0e41(blob)
db.log
- 创建分支dev,并在dev上提交内容
# 基于master分支创建dev分支
git branch dev
# 切换至dev分支
git checkout dev
# 查看此时.git/refs的目录结构
tree .git/refs
>
.git/refs/
├── heads
│ ├── dev
│ └── master
└── tags
# 查看commit对象的id,由于dev分支刚从master分支copy出来,所以commit_id相等
cat .git/refs/heads/master .git/refs/heads/dev
> 69b0bd61b54155b5ff6ed45847e4e01767ca14db
> 69b0bd61b54155b5ff6ed45847e4e01767ca14db
# 在dev分支添加内容
echo "electrical" >> readme.md
echo "gcc -o main main.c" >> Makefile
git add .
git commit -m "add Makefile"
cat .git/refs/heads/master .git/refs/heads/dev
> 69b0bd61b54155b5ff6ed45847e4e01767ca14db
> 3d64d5b0bf15a2e59a68854011ac9ee1771f164f
此时,Git Object中存储的对象和关系如下:
(parent)
HEAD--> refs/heads/work--> 3d64d5(commit) +------> 69b0bd(commit)<--refs/heads/master
+ +
| |
v v
ecf0a4(tree) d6c639(tree)
+ +
| |
+-----------------------------+ +--------+-----------+
| | | | |
v v v v v
501a93(blob) 98c9be(blob) a10a82(tree) 053baa(blob)
readme.md (version 2) Makefile src readme.md (version 1)
+
|
v
8d0e41(blob)
db.log
二、本地仓库
2.1 必会操作
2.1.1 初始化
如果要管理文件demo,则通过命令行进入demo文件里,输入
git init
2.1.2 基础配置
2.1.2.1 用户信息
--system 、--global 、--local 三者的区别:
# 1、系统层面的配置,配置信息写入git安装目录下的etc/gitconfig
git config --system
# 2、全局层面(当前用户层面)的配置,配置信息写入~/.config
git config --global
# 3、仓库层面的配置,配置信息写入当前仓库下的.git/config,是默认的配置方式
git config --local
配置生效的优先级:仓库层面(local) > 用户层面(global) > 系统层面(system)
因此,只要通过git config --local 对仓库A进行了配置,仓库A就会使用该配置,而忽略用户层面、和系统层面的配置。
最佳实践:每次git init 初始化完本地仓库后,通过git config --local 配置用户名和邮箱信息,例如:
git config --local user.name 'Pioneer4'
git config --local user.email 'electricalqzhang@gmail.com'
2.1.2.2 资源范围管理(.gitignore)
.gitignore 是一个文本文件,用于管理仓库的资源范围,它告诉Git仓库要忽略项目中的哪些文件或文件夹。换句话说,如果xxx被记录到.gitignore ,那仓库就不会管xxx,把你当做不存在。
下面是go语言项目开发时,.gitignore 的基础配置示例:
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
2.1.2.3 密码设置
git config --system --unset credential.helper 重置
git config --global credential.helper store 设置
2.1.3 内容提交
git status 查看工作区状态。也就是说,工作区中任何的内容变更,通过此指令都会被查询出来
git add . 将工作区变更的内容添加到index(暂存区)
git commit -m "优化质检事件ES查询性能" 将暂存区中的内容提交到本地仓库
2.1.4 临时贮藏工作区
# 将工作区的内容贮藏起来
git stash
# 查看贮藏的内容
git stash list
# 之前的内容覆盖工作区 stash中的内容还在
git stash apply
# stash中的内容不见了
git stash pop
2.1.5 回退
2.1.5.1 指令
soft、mixed、hard区别
指令 | 本地仓库 | 暂存区 | 工作区 |
---|
git reset --soft | √ | × | × | git reset --mixed | √ | √ | × | git reset --hard | √ | √ | √ |
git reset --soft :仅在本地仓库移动指针git reset --mixed :本地库移动指针,并重置暂存区git reset --hard :本地库移动指针,重置暂存区,重置工作区
基于索引值commit_id git reset --hard commit_id 基于符号^ git reset --hard HEAD^ 回退一步 基于符号~ git reset --hard HEAD~3 回退3步
checkout指令:
git checkout [文件名] :恢复工作区中某一个文件的内容(删除某一个文件的改动),让该文件内容变为和上次提交时一样。git checkout . :恢复工作区的内容(删除所有文件的改动)
注意:比如现在有一个项目,你对项目当前版本已有的文件的内容(1)进行了修改或(2)删除,并且未添加至暂存区,则可以通过git checkout . 删除这些改动。但是,一旦提交到暂存区,或者是在项目里创建了新的文件,使用git checkout . 无法删除这些改动。所以最佳方式还是需要使用下面提到的两步法(先add再reset)
2.1.5.2 典型场景
git reset HEAD readme.md 将暂存区中的readme.md文件移除,相当于git add readme.md 的逆操作
git reset HEAD . 将暂存区中所有内容移除
- 由于错误操作,在工作区中添加了一些错误的内容,希望将工作区重置
git checkout .
- 由于错误操作,在工作区中不仅添加了内容,而且还创建了新的文件,此时希望将工作区重置
# 第1步:将工作区的内容添加到暂存区
git add .
# 第2步:使用--hard的方式回退版本,同时修正暂存区和工作区
git reset --hard HEAD
说明:虽然说git reset --hard 可以同时修正本地仓库、暂存区、工作区的指针,但目前来说,在工作区创建新的文件后,如果仅仅使用该条指令,发现新建的文件还在工作区,不能达到重置工作区的目的。所以,对于要重置工作区的最佳实践就是两步法(先add再reset):先通过git add . 将所有变更内容加入暂存区,再通过git reset --hard HEAD 进行版本回退。
2.1.6 版本信息
2.1.6.1 查询提交日志
-
git log 最完整的形式 -
git log --oneline 显示commit_id前7位和注释 -
git log --pretty=oneline 显示commit_id和注释 -
git log --all --graph 显示版本图 -
git reflog 显示commit_id前几位和注释以及到当前版本的步数。
git reflog 的重要使用场景:
因为reflog会保存commit、checkout、reset的记录,所以有一个重要应用场景:如果由于误操作,使用git reset --hard xxx 将仓库从current版本回退到xxx版本后,此时,通过git log --oneline 只能查询到xxx及其之前的版本。如果想再次回到current版本,则需要通过git reflog 来查询记录,找到current版本的commit_id,最终通过git reset --hard current版本的commit_id 返回到current版本。
2.1.6.2 内容比较
# 比较当前版本(HEAD)与临近的第1个版本(HEAD~1)的差异
git diff HEAD~1 HEAD
# 比较当前版本(HEAD)与临近的第2个版本(HEAD~2)的差异
git diff HEAD~2 HEAD
# 比较临近的第一个版本(HEAD~1)与临近的第两个版本(HEAD~2)的差异
git diff HEAD~2 HEAD~1
# 工作区与当前版本的差异比较
git diff HEAD
# 工作区与上一版本的差异比较
git diff HEAD~1
# 工作区与暂存区的比较
git diff
# 暂存区与当前版本的比较
git diff --cached
# 暂存区与上一个版本的比较
git diff HEAD~1 --cached
# 比较master分支和dev分支的差异
git diff dev master
语法小提示:在比较当前版本(HEAD)与临近的第1个版本(HEAD~1)的差异时,指令为git diff HEAD~1 HEAD 。HEAD~1 在前,HEAD 在后,的意思就是以HEAD~1 为基准,查看HEAD 这个版本发生的内容变化。
2.1.6.3 图形化工具
gitk --all
图/fastjson的gitk界面
2.1.7 commit message
2.1.7.1 message修改
git commit --amend
该操作更进一层的理解是通过创建新提交来替换当前版本的提交。所以使用该指令后,当前版本的message和commit_id都将发生变化。
git rebase -i base_commit_id
例如:
git log --oneline
> 3ff36f6 (HEAD -> temp) v3.0
> 78d7e97 v2
> 4c21794 v1
> 6d6314a 添加教程
> fcb8826 add .gitignore
> 459f866 delete log
# 第1步:若想修改4c21794的内容,则需要rebase到它的父commit,也就是6d6314a
git rebase -i 6d6314a
# 第2步:在弹出的commit message信息框(vi文本)中,将要修改版本前的pick修改为r;然后会继续弹出信息框,此时修改message,最后wq保存退出
# 修改完成后,可以看出6d6314a之后的commit的id和message都发生了变化
> ca31d40 (HEAD -> temp) v3.0
> d7ffc44 v2.0
> 3fd7822 v1.0
> 6d6314a 添加教程
> fcb8826 add .gitignore
> 459f866 delete log
2.1.7.2 commit 合并
git rebase -i base_commit_id 和修改commit message使用的指令相同,只是在提示框中输入的信息不同
# 操作前
git log --oneline
> ca31d40 (HEAD -> temp) v3.0
> d7ffc44 v2.0
> 3fd7822 v1.0
> 6d6314a 添加教程
> fcb8826 add .gitignore
> 459f866 delete log
第1步:如果想合并v1.0、v2.0、v3.0这三个请求,需要选取 6d6314a 这个版本作为基
git rebase -i 6d6314a
得到以下界面:
第2步:根据显示的Commands提示信息可以知道,如果要融合(meld)多个版本提交,需要使用squash命令(也可简写为s)替换pick,替换后如下:
注意 v1.0作为基础 它的pick不能替换
第3步:将pick改为s后,输入:wq ,则会进入以下这个页面
第4步:在v1.0那个位置添加合并后的commit message信息
第5步:输入:wq 保存后,得到如下反馈信息
[detached HEAD 89cccf0] feature-search complete
Date: Fri Nov 5 15:44:46 2021 +0800
1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/temp.
再次通过git log --oneline 查询提交日志,可看出v1.0、v2.0、v3.0成功的合并为了 “feature-search complete”
2.2 分支
2.2.1 查看、创建、切换和删除
-
查看
git branch -a 查看当前所在分支,以及所有分支的namegit banch -v 查看当前所在分支,以及所有本地分支的name、commit_id、message -
创建 git branch xxx 以当前分支为基,创建新的分支xxx。如:git branch dev 表示以当前分支为基础,创建dev分支; -
切换 git checkout dev 切换至dev分支 -
删除
git branch -d fix 如果fix已经与当前分支merge,则可以成功删除git branch -D fix 如果fix没有与当前分支merge,则使用"-D"强制删除 提示:如果有一个fix分支,已经与dev分支合并,目前项目处于master分支,如果使用git branch -d fix 是无法删除fix分支的,因为fix并未merge至master分支;如果当前处于dev分支,则可以使用git branch -d fix 删除fix分支。
2.2.2 分支合并
# 如果希望将dev分支合并至master分支,则分两步走:
# 1、切换至master分支
git checkout master
# 2、合并dev分支
git merge dev
# (1)如果没有冲突,则会弹出vi信息框(第1行就是默认的message),可输入":wq"直接commit;
# (2)有冲突,则手动解决冲突后再commit;
2.3 标签
-
查看 git tag -l 查看本地仓库tag列表 git show-ref --tags 查看本地仓库tag列表 git ls-remote --tags 查看远程仓库tag列表 -
创建 git tag -a [tag_name] -m "release version v1.2" [commit_id] 加上-a 参数来创建一个带备注的tag,备注信息由-m 指定。如果你未传入-m 则创建过程系统会自动为你打开vi编辑器让你填写备注信息 git push origin [tag_name] 将标签名为[tag_name] 的tag同步到远程仓库 -
删除 git tag -d [tag_name] 删除本地仓库中标签名为[tag_name] 的tag git push origin :refs/tags/[tagName] 删除远程仓库中标签名为[tag_name] 的tag
三、远程仓库
3.1 与本地仓库协同
3.1.1 创建
3.1.1.1 本地仓库未创建
第1步:在远程仓库中创建项目
第2步:将远程仓库中的项目clone到本地
3.1.1.2 本地仓库已创建
第1步:在远程仓库中创建项目,(注意:远程仓库中的项目名最好与本地仓库的项目名一致)
第2步:git push [origin] --all -f 强制推送本地仓库所有的分支到远程仓库[origin]中,由于为了保证远程仓库的安全性,通常会设置禁止强制推送选项,也就是禁止使用-f 参数。此时,可以通过一下步骤达到同样的目的,以main分支为例:
- 首先需要了解到远程仓库的分支有“default”的标识,通常就是main分支,所以,需要将default标识设置到其它分支;
- 在远程仓库web端中删除
main 分支; - 本地仓库中,使用
git push origin main ,将本地仓库main 分支推送到远程仓库; - 为了
main 分支的安全,重新将"default"标识设置为main 分支;
3.1.2 remote增删改查
git remote -v 查看保存的远程仓库地址
git remote show <远程仓库名> 查看该远程仓库的详细信息
git remote rm <远程仓库名> 用于删除远程仓库名
git remote add test https://github.com/Pioneer4/test.git 添加远程仓库名和远程仓库地址
git remote rename <原远程仓库名> <新远程仓库名> 用于远程仓库的改名
git remote prune <远程仓库名> 清理远程仓库中已经不存在的分支
3.1.3 push & pull & fetch
语法特征:分支推送或拉取顺序的写法是git push/pull/fetch [remote_repo_name] <source>:<dsetination> ,所以拉取是git pull [remote_repo_name] <远程分支>:<本地分支> ,而推送git push [remote_repo_name] <本地分支>:<远程分支> 。
-
push git push origin main 推送main分支 git push <远程仓库名> <本地分支名>:<远程分支名> 命令用于将本地仓库分支的更新,推送到远程仓库
- 如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。
git push origin master 上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。 - 如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支
# 删除远程仓库origin的man分支。注意:这里只是做演示,通常远程仓库默认是不允许用户删除main分支的,况且用户也不应该删除main分支!
$ git push origin :man
# 等同于
$ git push origin --delete man
-
pull git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库某个分支的更新,更新本地仓库,暂存区、工作区。 提示:git pull origin main 这条命令等用于fetch 和merge 两条命令的组合
git fetch origin main - git merge origin/main
-
fetch git fetch <远程仓库名> 将某个远程仓库中的所有分支拉回本地仓库。更新本地仓库中<远程仓库名>/ 路径下的所有分支,不更新本地main、dev等分支。 git fetch <远程仓库名> <分支名> 将某个远程仓库的某个分支拉回,相当于 <远程仓库名>/<分支名>更新,但本地仓库的<分支名>不更新。
场景:git pull 和 git fetch 的区别演示
fetch & merge
# 1、在用户A的工作区中添加内容,然后add、commit、push。
touch battery.log && git add . && git commit -m "Add battery log" && git push rcxn main:main
# 2、在用户B的工作区中fetch更新
git fetch rcxn main
# 3、查看本地main分支提交日志,发现如任何更新
git log --oneline
# 4、切换至分支rcxn/main,查看更新日志,可发现已经收到更新日志"Add battery log"
git checkout rcxn/main && git log --oneline
# 5、切换至本地main分支,和并分支rcxn/main,最后查看日志可知,main中也更新了"Add battery log"
git checkout main && git merge rcxn/main && git log --oneline
pull
# 使用pull,就会直接拉取远程仓库分支并合并
git pull rcxn main
3.1.4 commit message修改
message修改首先在本地仓库中进行,方式前面已经介绍过。修改完成后,使用-f 参数强制推送至远程仓库即可
git push rcxn dev:dev -f 将修改message后的dev分支强制推送至远程仓库rcxn
3.2 规范流程
- 找到问题:提出Issue;
- Issue讨论OK,提交PR(Pull Request)或者MR(Merge Request);
- CodeReview后合并;
四、参考
git 维基百科
git 官网
Pro Git (Second Edition)
git内存储管理
Git for Teams
|