一些概念:
commit:提交
repository,repo:仓库
working directory:工作区
checkout:检出
staging area、staging index、index:暂存区、索引区
working area、working tree:工作区
SHA:每个 commit 的编码
branch:分支
首先要知道查询文档的方法:在 bash 输入 git help [keyword]
,例如 git help add
,就可以看到这个命令的使用说明。另外,查询配置信息需要这样:git config --list --show-origin
。
推荐两篇学习文章:
以下是我的学习笔记。
HEAD、branch、tag 都是 reference,HEAD 指向 branch 或 tag 或 sha,如果指向 tag 和 sha,那就是 detached head,.git/HEAD
这个文件记录了当前的 head 是哪个。从 .git/refs/heads/
这个文件夹来看,每个 branch 都有一个 HEAD,你 checkout 到哪个 branch,那这个 branch 的 HEAD 就生效。如果是 detached head,那所有 branch 的 head 都不生效。
这篇文章对理解 HEAD、branch、tag 很有帮助:Git Internals 。
可以通过 echo [SHA] > .git/HEAD
修改 HEAD 指向哪里,效果和 git reset
类似,所以 git reset
主要改变 HEAD 这个文件。
repo 里面的任一文件有 4 种状态:untracked、unmodified、modified、staged。除了 git add
,所有 git 命令对 untracked 文件无效。
git 就是对比,在 git 的世界中,万物皆对比。用对比的思维理解 git,而不要用文件的流动性(变化性)去理解。例如 working dir 和 staged area 对比,如果有差异,git status
就会用红色表示出来,git diff
看的是红色的细节。staged area 和 commit area 对比,如果有差异,git status
就会用绿色表示,绿色表示 staged area 和 working dir 一样,但和 commit area 不一样,git diff --staged
看的是绿色的细节。
git reset
也是对比的过程,默认的 --mixed
重置 commit area 和 staged area,这时候 git status
,git 会把 working dir 和 staged 进行对比,差异显示为红色。--soft
只重置 commit area,所以 staged area 和 commit area 有差异,用绿色表示。--hard
会同时恢复 3 个 area。详情可看 git reset -h
。
搭配 github 中使用 git:
git remote:查看本地 repo 在 github 的所有远程 repo,即这个本地 repo 被上传到哪些 github repo;
git remote show origin:可以看到 origin 这个 repo 的详细信息;
git remote rename [旧名称] [新名称]:重命名,例如本来是 origin 的,改为 newOrigin;
git remote add [名称] [URL]:创建远程 repo 的方法,通常叫 origin,url 就是远程 repo 的 url;
git remote -v:查看远程 repo 的 url 是什么,包括 fetch 也就是下载的 url,push 也就是上传的 url,-v
是 --verbose
的缩写;
git push origin main:把本地的 main 分支,上传至远程的 origin,这里用 origin 指代 url,origin
可省略,因为这是默认值;
git push origin [tagName]:默认情况下,git push 不会把 tag 上传到 github,需要主动 push,如果要 push 所有 tag ,可以用 git push origin --tags
,origin
可省略,因为这是默认值;
git pull origin main:从远程 origin 这里,把 main 分支最新的内容下载到本地,更新本地的分支,origin
可省略,因为这是默认值;
github 的 fork:相当于在 github clone 别人的 repo,如果 fork 之后从自己的账户 clone 到本地,那这个 remote 就是自己的 remote,而不是原始 remote,而且你通常对原始的 remote 无写入权;
upstream/main:如果你 fork 了一个项目,然后从你的 repo 把这个项目 clone 到本地,那这个本地项目的 remote origin 就是你自己的 repo,如果你想从原始 repo 更新你的本地项目,需要手动 git remote add [名称] [url]
,其中名称习惯叫 upstream
而不是 origin
;
注意 main、origin/main 两个分支的区别,当有 remote 参与时,本地 commit 可能会出现 2 个 branch,main、origin/main 不一致时就会出现,他们的关系可能有四种:ahead、behind、up-to-date、out of sync;
git rebase:变基,比较少用;
github 的 pull request:相当于远程 merge,以 branch 的方式进行 request,所以最好的做法是,这个 branch 的名称可以反映这个 request 的 topic。当然,在提交 request 的时候,你可以起标题,说明等。本地 merge,如果是快进合并,不会产生新的 commit,但是在 github 接受 pull request 产生的 merge,肯定会产生新的 commit;
开源项目大概是这样进行的,以 deno 为例:ry 发布 deno,其他人可以 fork 它的项目,fork 了之后再 clone 到自己的电脑,发现 bug 或者加入新功能等,需要更改代码,那就建一个新 branch,改代码再通过这个 branch push 到自己的 repo,然后在自己的 repo 那里发起 pull request。可以向原作者 pull request,在创建 pull request 时,github 会提醒你的代码是否和最新的原作代码有冲突,如果有,你可以 add 原始的 remote,也就是 upstream/main,把代码 fetch 下来 diff,根据需要把 main、upstream/main merge 起来,再 push 更新你的 main、origin/main,然后重新提交 pull request
branch 在实际应用中往往可以分为两类:long running branch 、topic branch。pull request 通常是 topic branch。
一些 git 命令:
git init:初始化 git 仓库,自动生成 .git 文件夹;
git clone:下载仓库;
git status:状态;
git log:默认显示 SHA、作者、日期、消息,只能看到 branch 的 commit,不在 branch 的 commit 看不到,例如 detached head;
git log --oneline:只显示 sha;
git log -2、git log --since=2,weeks:显示哪些 log;
git log --stat:显示每条 commit 那些文件发生了改变;
git log [branch 名称]:查看某个 branch 的 log;
git log -p:显示这些改变都是什么,也可以写作 --patch;
git log [SHA]:可以从该 SHA 开始,倒翻去看;
git log --follow [文件名称]:看这个文件的历史变化,包括重命名;
git show:显示最新 commit 的详细变化。它和 git diff 的重要区别是,git show 只会显示该 commit 相对其父 commit 的变化,例如两个分支 merge 之后,有点难区分某个 commit 的父 commit 是什么,这时用 git show [SHA]
就可以默认比较该 commit 相对其父 commit 的变化;
git show [SHA]:显示该 SHA 的详细变化,可以和 -p
、--stat
、-w
(空格) 等一起使用;
git add:把变化从工作目录转移到暂存区,后面可以跟具体文件名称,也可以加 .
;
git add --patch:如果一个文件有多个改变,你希望把这些改变分拆开多个 commit 提交,可以用这个命令;
git rm --cached [文件]:可以把文件移出 staging area,可接受多个文件,也就是变成 untracked,文件继续保留在硬盘;
git rm [文件]:把文件从 working dir 删除,同时从 staging area 删除,也就是从硬盘上删除,rm
不会把文件从 staging area 中删除;
git mv [名称 1] [名称 2]:在 repo 中对被 tracked 的文件用 mv
重命名要小心,因为 git 会把重命名理解为 3 个步骤,删了原来那个,新建文件,这个文件没有被 tracked。所以可以用 git rm 这个命令重命名,一次性完成三个步骤,相关笔记:Git 如何重命名文件和文件夹 ;
git diff:和 git log -p
一样。后面可以加两个 SHA,这样就是比较两个 commit 的变化,如果不加任何 SHA,就是比较 staging area 和 working dir 两者之间的区别。如果要比较 staging area 和 commit 之间的变化,需要 --staged
;
git diff --staged:参考上一条;
git commit:提交;
git commit -a:跳过 add 到 staged,直接从 working dir 到 commit;
git commit -m:提交说明不要超过 60 个字符;
git tag -a v1.0:表示创建一个带注释的 tag,如果没有 -a 就不带注释,git show v1.0 可以查看这个 tag 的内容。tag 不会移动。不带注释的 tag 类似 branch,带注释的 tag 类似 commit,因为带注释的 tag 会生成一个新的 sha 值,只不过 log 不会显示这个 sha 值。可以在 .git/refs/tags/ 查看;
git tag:列出所有标签
git tag -d v1.0:删除标签
git tag -a v1.0 [SHA]:如果后面加 SHA,表示给指定 commit 加标签,如果没加 SHA 就表示给最新的 commit 加标签;
git branch:显示所有分支,--merge
可以显示那些已经 merge 的分支 --no-merge
反之;
git branch -v :可以查看所有 branch 最新一个 commit,-v
是 --verbose
的缩写;
git branch [名称]:创建分支。注意分支的可读性,是指这个分支顺藤摸瓜可以 log 到哪些 commit,不是所有 commit 都可以通过这个分支 log 到,有些 commit 可能所有分支都 log 不到;
git checkout [分支名称]:切换分支;
git checkout [SHA]:切换 sha;
git checkout [tag]:切换 tag;
git checkout [文件名称]:撤销该文件 staged 之后的修改,相当于用 staged 的状态覆盖未 staged 的状态
git reset HEAD [文件名]:用该文件在 commit 的状态覆盖 staged 的状态,覆盖后相当于该文件在 staged 中和其在 commit 中无变化。又因为 reset 默认不覆盖 working dir,所以该文件在 working dir 中无影响,所以表现出该文件未 staged;
git branch [名称] [SHA]:表示创建分支,并指向 sha 这个 commit;
git branch -d [名称]:删除分支,注意删除分支是指删除分支这个标志,这个分支的 commit 至少短时间内并不会被删除。如果分支删除之前 merge 入了另一个分支,那这个被删除的分支的所有 commit 都可以通过另一个分支的 log 查看,并且不会被删除。如果没有 merge 就删除分支,那这个分支的所有 commit 不能通过 log 查看,但在一定时间之后(默认 30 还是 90 天)会被删除,删除前只能通过 git reflog
查看。当然就可以 checkout SHA 回到这个 commit;
git merge [分支名称]:把分支合并过来,合并分为快进合并、普通合并。checkout 在 a 分支,再 merge,那 merge 之后的新 commit 所在的分支也叫 a。快进合并不会产生新的 commit,只不过 HEAD 的位置变了,具体来说就是往前移动了,普通合并很定会产生新的 commit;
git mergetool:要先执行 git merge ,如果有冲突才可以用 mergetool,要先设置:git config --global merge.tool vimdiff3
;
git commit --amend:更新最近的一个 commit;
git revert [SHA]:还原 commit,会创建一个新 commit;
git reflog:可以查看被 reset 后、30 天内的 commit;
其他:
HEAD 当前 commit,HEAD^ 上一个 commit,也可以用 HEAD~ HEAD~ 表示;
HEAD^^、HEAD~2 表示上上个 commit;
HEAD^^^、HEAD~3 表示上上上个 commit;
^、~ 的区别是,合并分支后的 commit 有两个父 commit,HEAD^ 表示当前所在分支的父 commit,HEAD^1 表示被合并过来的分支的父 commit,~ 没有这个功能。
关于 git checkout
、git reset
、git restore
、git revert
的区别,可以看 stackoverflow 的这个问答 ,它们都有后悔药的作用。如果要恢复一个文件,建议用 git restore
,它是在 git 2.23 新加的功能,功能上是 git checkout
的子集,更安全。