0x0 前言
没系统学过git,老是有一些问题就谷歌,解决完问题又没时间记,感觉不是个办法。正好看到一篇敲详细的文章(感谢作者整理),所以决定重头学习一遍并且把难点记录下来。第一次写文章🔰,如果文章有错或者有其它问题欢迎评论指正!
0x1 Git概述
Git的四个组成部分
详情见来源
// stash
区域应该算是一个独立的部分,不受这些命令干扰。
Git中的四类对象
Blob
(块)对象,Tree
(树)对象,Commit
(提交)对象,Tag
(标签)对象。 –来源
每一个对象都会有一个对应的 hash 值 (SHA-1)。1
2git hash-object <file> # 计算blob的hash
git cat-file [-p|-t] <hash> # -p查看对象内容 -t查看对象类型
blob
一听就知道是个二进制,它只存文件内容。文件名等信息的就在tree
里,很像操作系统的目录表结构。而树对象不仅可以引用blob
,还能引用tree
构成一个多层目录结构,这点像很像文件夹。
1 | git ls-tree <hash> # 查看树对象内容 |
一个commit
中只记录一个树,鉴于树的套娃结构,commit
里对应的是最根部的树对象。commit
会有一个很短的值,那其实不是完整的 hash 值,只是取前几位作为了缩写(只要唯一就行)。
0x2 Git配置
git config
system
: win 端好像都是C:/Program Files/Git/mingw64/etc/gitconfig
global
: win 端地址C:/Users/<username>/.gitconfig
local
: 项目中.git/config
三种级别配置文件,就近覆盖生效。local > global > system
一般都用 global
1 | # 配置代理: |
这样配置在文件中是这样:(我的.gitconfig
)1
2
3
4
5
6
7
8
9[github]
user = <github username>
token = <personal access token>
[http]
proxy = http://127.0.0.1:1080
[alias]
st = status -s -u
cm = commit -m
lg = log --graph --oneline --abbrev-commit
注意:设置了 2FA 两步验证则无法使用
[user]
密码登录,必须提供 token 作为凭据。
.gitignore
Github 官方有专门的一个 仓库 给出各语言的模板。下方匹配模式摘自 来源。
*
:零个或多个任意字符[abc]
:只匹配括号内中的任意一字符[0-9]
:范围内任一字符?
:任意一字符**
:任意的中间目录1
2
3
4# .gitignore
!index.d.ts # 除了这个文件
dir/ # 忽略该文件夹下所有
dir2/**/*.o # 所有dir2下子文件中的.o文件
已经被 tracked 的文件即使加入.gitignore
也无法解除追踪,StackOverflow:1
先 commit 你的当前更改再进行操作,否则更改会丢失!在项目根目录执行:
1 | git rm -r --cached . ; git add . |
0x3 Git提交
git add
1 | git add . # 所有更改文件放入暂存区 |
unstage 取消暂存:1
git reset -q <file> # unstage file
git commit
1 | git commit -m "<msg>" # 提交已暂存更改 |
git tag
tag
对像有两种,轻量标签直接引用 commit,附加标签创建 tag 对象,实质都是对commit
的引用。
对重要的提交打上 Tag,可以方便找到对应 commit ,如 release 新版本时。1
2
3
4
5
6
7# 创建 tag
git tag <version> # 轻量级标签(lightweight)
git tag -a <version> -m "<msg>" # 含附注标签(annotated)
git tag -a <version> <commit_id> # 追加标签 id 可为前7位简写
# 删除 tag
git tag -d <version> # 删除本地 tag
git push origin -d <version> # 删除远程 tag,命令中 tag 可以省略
如果想要使用 GPG 签名,将 -a 换成 -s 即可,验证签名用 -v 。加 -f 强制覆盖。
另外,git push 的时候默认不会把标签推送到远程仓库,如果想把标签页推送到远程仓库,可以:1
2git push origin <version> # 推送某标签远程
git push origin --tags # 推送所有 refs/tags 下的标签
0x4 Git分支
分支概述
分支并不是 Git 对象,和轻量级的 tag 对象类似,是对一个commit的引用。
当 branch 更新后会指向最新的 commit 。而 tag 则不会(除非--force
)。
- 本地分支:
.git/refs/heads/
- 远程分支:
.git/refs/remotes/
- HEAD:
.git/HEAD
指向当前工作区的本地分支
每个 commit 对象有一个 parent 属性值指向父 commit 的哈希值,由此可得到变化路径。
- 场景0:多人合作项目是私有项目,无法 fork,每个人对各自的任务创建分支。
- 场景1:master 要拿来持续部署/发行正式版,必须保证 master 的可靠稳定,创建 dev 分支专门接受其余各分支的 pr,经测试后再推送到 master。
- 场景2:大版本更新,备份一个之前版本到分支,因为要迁移所以旧版本会仍然有人在用。如果旧版本出现问题,可以直接到对应分支进行修复。
git branch
1 | git branch -a -v # 查看所有分支(远程+本地,显示每个分支最新 commit) |
1 | git checkout <branch> # 切换分支 |
恢复误删分支 StackOverflow:1
2git reflog # 检索出被删分支哈希
git checkout -b <branch> <hash>
git stash
切换分支时有未提交更改,而更改未完成不想直接提交,可以使用储藏来暂时保存更改。
储藏是一个栈结构,stash@{0}
指向最新的储藏,stash@{1}
则为上上个储藏,以此类推。1
2
3
4
5
6
7git stash (push) # 储藏当前未提交更改,含已暂存(push 可省略)
git stash list # 查看储藏栈
# 以下命令后都可接'<stash>',如'stash@{2}',未提供则默认使用最新'stash{0}'
git stash show # 查看存储更改
git stash apply # 只应用储藏
git stash drop # 丢弃对应储藏
git stash pop # 弹出,等同于 apply+drop
git merge
右侧为 快速合并(Fast-Forward-Merge),要求被提交分支无其它 commit。图源
1 | git merge -ff # 快速合并(默认) |
使用
--squash
将合并压缩为单个 commit。(见 merge reference –squash)
git rebase
看到 这篇文章 写的不错,可以去看看。下面是我从 Learn Git Branching 做的练习。
在 dev 分支上想合并到主分支,使用git rebase master
将 dev 分支变基到 master 上。此时还未做完,因为 master 还未指向最新 commit。需要切换到 master 再使用git rebase dev
。如果操作中有 conflict 则需手动解决冲突后 git rebase --continue
。
注意原先的 c2 commit 并不是丢失了,它仍存在历史中。
1 | git rebase A_branch B_branch # 在B分支上执行 git rebase A_branch |
交互模式只需更改左侧pick
为想要的命令然后保存退出,其后每一步都会有提示。如果搞砸了可以git reset --hard origin/<branch>
从远端覆盖变更。
0x5 Git远程
git remote
1 | # 本地初始化 git |
origin
只是一个远程仓库链接地址的别名,特别在它是默认的,还可添加其他远程地址。upstream
指向 fork 的原项目地址。
若远程还有其他分支,如origin/dev
,这种远程地址是不可更改的(只在同步的时候更改指向的 commit)。如果需要对该分支进行更改则需要建立本地的跟踪/关联分支:1
2git checkout -b dev origin/dev
git checkout -t origin/dev # 同上,全写 --track
git fetch
1 | git fetch origin # 拉取远程到本地 但是不会改变工作区 |
git push
1 | git push (origin) # 推送所有更改(默认)到 origin |
0x6 Git回退
HEAD一般指向当前分支的最顶端,但也可指向特定的 revision,此时称之为
detached HEAD。
HEAD^n
指向上n个提交记录,也可省略n,HEAD^^
表示上两个提交记录。HEAD~n
同上但不可使用多个~
。链式操作如HEAD^3~2^^
git reset
1 | # unstage file |
- soft:将回退的 commit 更改退回到暂存区中,工作区不变。
- mixed:默认,将已有的暂存区取消暂存,将回退的更改退回工作区。
- hard:丢弃回退的更改以及暂存区工作区更改。
git revert
若推送上远程后再用 reset 则产生冲突,revert 将撤销更改作为一个新的 commit。1
2
3
4git revert HEAD # 撤销最近的一个 commit(即当前指向)
git revert HEAD~n # 撤销前第 n 个提交
git revert <commit_id> # 只撤销该提交,后续提交不影响
git revert <oldest_commit_id>..<latest_commit_id> # 撤销范围提交
1 | 已经推送到远程的提交,是无法彻底撤回的(历史记录仍在)。 |
git checkout
1 | # 丢弃未暂存更改(无法找回) |
0x7 Git查询
git diff
1 | git diff # 工作区与暂存区差异 |
注:如果只想统计哪些文件被改动,多少行被改动,可以添加–stat参数。搬运自来源
git log
1 | git log --graph # 分支历史情况 |
git reflog
:Reference logs, or “reflogs”, record when the tips of branches and other references were updated in the local repository. —-git-scm
0x8 其它
1 | git cherry-pick <commit_id> # 取特定 commit 追加到当前分支 |
参考:
- coder-pig 《吐血整理》一篇文章教你学废Git版本管理
- Scott Chacon & Ben Straub Pro Git 中译版
- Git 官方文档(git-scm) reference
- Vincent Driessen A successful Git branching model
推荐:
- 腾讯IMWeb团队 十分钟了解git那些“不常用”命令 (
git rebase
) - 图形化 Git 分支练习 Learn Git Branching
- atlassian Git Workflows and Tutorials