Git是一个开源的版本管理系统,和Linux系统是同一作者——Linus Torvalds,用于管理Linux内核开发而开发的,作者给它起名Git(饭桶),介绍是The stupid content tracker, 傻瓜内容跟踪器……
为什么要认真了解它?因为它是个主流团队协作工具,协作工具用不好会影响到别人——《因代码不规范,国外程序员枪击4个同事》,据说是因为同事每次都git push -f
,也不知道真实情况如何……
最近使用比较多,Git命令的各种参数每次都是查完就忘,除了一些常用的外,遇到复杂点的场景都得重新查,其实也有一些图形界面工具(类似Sourcetree1)可以简化Git的使用,不过相对于命令行来说,图形界面的一个动作可能会执行一套组合命令,有时感觉不太可控。Git的官方文档2已经很详细,但太全面了查起某个具体功能来也有点大海捞针的感觉,为了摆脱查找的耗费,把常用命令和常见场景总结一下,毕竟查自己的要比查搜索引擎的要更方便。
有关Git相关的基础知识可以参考Git教程-廖雪峰的官方网站3,讲得挺清晰。
Git工作涉及的区域主要有四个——工作树、、暂存区、本地分支、远端分支。
图中的命令标示的只是常规使用方式,Git的命令通过附带参数的不同可以达到不同的效果,同一个效果也可以使用不同的命令来完成,并不是绝对的。
工作树就是当前作业正在进行的地方,所有对文件的改动都是在工作树上进行的。
官方文档里叫“Working Tree”,也有的资料里叫“Working Directory”。
修改完成的文件可以放入暂存区,此时的文件状态变为Staged,暂存区是工作区和分支之间的过渡区域。
官方文档里叫“Index”,也有的资料里叫“Stage”。
分支是由多个commit构成的历史记录的链路,可以存在多条不同的分支。
每个分支的瞬时状态其实是指向某个特定commit的指针;
本地目录中的“.git/refs/heads/”目录以普通的文本形式保存了本地仓库下的所有分支。
保存在服务器端的Git仓库内的分支。
本地目录中的“.git/refs/remotes/”目录以普通的文本形式保存了远端仓库下的所有分支,也包含远端Head指针当前的位置。
Git管理的文件有四种状态——Untracked、Unmodify、Modified、Staged。
图中的命令同样只是常规使用方式,命令通过附带参数可以达到不同效果,同一效果也可以使用不同命令来完成,并不绝对。
文件未跟踪。此文件在文件夹中, 但没有加入版本跟踪, 不参与版本控制。
可通过
git add
命令变为Staged状态。
文件已跟踪, 但未修改。文件与版本库中完全一致。
可直接修改文件, 变为Modified状态。
使用git rm
命令可将文件移出版本跟踪, 则成为Untracked状态。
文件已修改。文件与版本库中不一致
可通过
git add
命令变为Staged状态。
使用git restore
命令可丢弃修改,返回Unmodify状态。
文件已暂存。文件放入暂存区。
执行
git commit
可将修改同步到本地库中, 执行后版本库中的文件和本地文件又变为一致, 文件为Unmodify状态。
提交是从暂存区向分支进行上传的操作,每一次提交就会生成一个对应的commit。
每次进行提交时,Git通过SHA-1算法生成一个40位的十六进制Hash值,该SHA1哈希值就是commit-id,这个commit-id使每次commit提交都是独一无二的;
每个commit提交还会包含它的上一次commit提交的哈希值,Git以此来构建历史记录的提交链路;
每个commit-id是由其内容决定的,数值不能被改变,要修改commit-id只能重新创建一个全新的提交。
commit内部包含tree、blob,需要进一步了解的可以参考Git中三大对象commit、tree和blob之间的关系4
一个指向你正在工作的当前分支的指针。
Head指针一般都是指的本地Head指针,远端其实也有这个Head指针,但是正常情况下不会手动去更改远端Head指针的位置。
本地目录中的“.git/HEAD”文件以普通的文本形式保存了本地仓库的Head指针。
通过git init
对文件夹进行初始化,Git会在对应的文件夹构建管理空间,包括配置信息,commit提交信息,以及分支状态等。
本地仓库包含了暂存区和本地分支;
本地目录中的“.git/”隐藏目录保存了Git在当前文件夹的管理信息。
服务器端的Git仓库,可以向远端服务器推送代码用于开源或者团队协作。
远端仓库名默认为“origin”;
本地目录中的“.git/ORIG_HEAD”文件以普通的文本形式保存了远端仓库的Head指针对应的commit-id。
某个本地分支与某个远端分支建立了追踪关系,则该远端分支成为该本地分支的上游分支;该本地分支则是该远端分支的下游分支。
upstream常作为命令中的参数被使用,例如:–set-upstream-to、–set-upstream等
在某个具有特定意义的commit上做的标记,一般用来标识具有里程碑意义的各个commit,比如项目发布的各个历史版本都可以打上对应的tag来进行标记。
refs
是采用更加亲切的名称来指定某个commit,而不必使用Hash值来指定对应的commit,分支名就是一种引用。
其实引用直接或者间接指向了某个commit提交;
分支名是一种引用,比如分支“master”,但它的引用全称是“refs/heads/master”;
引用的具体内容以普通的文本形式被放在“.git/refs/”路径下;
“refs/heads/”下描述了本地仓库的分支;
“refs/remotes/”下是远端仓库以及对应的分支;
“refs/tags/”描述了在commit上打上的本地仓库的标签。
可以使用相对引用来指定某个commit,相对引用的使用涉及到两个符号~
与^
,两个符号的意义如下:
~
在第一父级上追踪,可用多个~
表示向前追踪的级数,也可在~后接数字表示向前追踪的级数;
^
在父级上追踪,通过在^
后缀数字1或2可以指定追踪的父级是第一父级还是第二父级,可省略后缀数字,表示默认追踪第一父级,可用多个^
表示向前追踪的级数。
第一父级是执行合并命令时所在的分支;
第二父级是执行合并命令时被合并的分支;
~
和^
可以在同一个表达式里配合使用。
示例:
以下分支图展示了在master分支上通过git marge hotfix
命令将hotfix分支合并到master分支之上的情况,通过~
和^
可以引用到不同分支的commit。
- 对于
commit节点8
来说,它是当前的HEAD节点,而且它只有一个父级,即节点7
,同时也是它的第一父级,所以节点7
可以表示成HEAD~1
或者HEAD^1
(也可省略数字1表示为HEAD~
或HEAD^
);- 对于
commit节点7
来说,它是一个合并节点,它的第一父级是节点6
(因为是从hotfix向master合并),第二父级是节点4,所以在节点7
引用的基础上,以下形式都可以是节点6
的表示方式:HEAD~1~1
、HEAD~1^1
、HEAD^1~1
、HEAD^1^1
、HEAD~~
、HEAD^^
、HEAD~^
、HEAD^~
、HEAD~2
;- 其他commit节点以此类推……
Git通过“具体引用”来表示本地仓库分支到远程仓库分支的映射,refspec
本质上是一种格式。
refspec
被表示为[+]
的形式,: 表示本地仓库的源分支,
dst
表示远程仓库的目标分支,可选的+
表示是否执行non-fast-forward(一种快速合并方式);
refspec
可用在对远端的推送、删除等操作上。
".git/"
——根目录
".git/hooks/"
——存放一些shell脚本
".git/info/exclude"
——仓库的一些注释信息
".git/logs/"
——存放所有更新的引用记录
".git/objects/"
——存放所有的Git对象,对象的SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。
".git/refs/"
——存放了所有引用内容
".git/refs/heads/"
——本地引用
".git/refs/remotes/"
——远端引用
".git/refs/tags/"
——标签引用
".git/CHERRY_PICK_HEAD"
——使用cherry-pick命令会更新此commit
".git/COMMIT_EDITMSG"
——最新一次commit所附带的描述
".git/config"
——仓库的配置信息
".git/description"
——仓库的描述信息
".git/FETCH_HEAD"
——使用fetch命令后会更新此commit,用于组合操作中的引用暂存,例如pull会先fetch再merge
".git/HEAD"
——当前检出的commit
".git/index"
——暂存区(二进制文件)
".git/MERGE_HEAD"
——使用merge命令会更新此commit,对应合并进当前分支的commit
".git/ORIG_HEAD"
——指向操作前的Head,用于某些命令的回退
".git/packed-refs"
——存放git运行垃圾回收机制后的一些引用,用于提高性能,垃圾回收不影响正常的Git功能,refs/文件夹下的一些内容有可能会被压缩到该文件内
".git/REBASE_HEAD"
——使用rebase命令会更新此commit
要更改主分支的位置就是修改refs/heads/master指向的内容。同样地,创建一个新的分支就是把commit哈希写入新文件这样简单。这也是为何Gi相比SVN是更加轻量的重要原因。
常用形式:
// 用得最多的一个命令了
git status
常用形式:
// 将“工作树”所有改动添加至“暂存区”
git add .
附加参数:
// 干跑,演示执行效果,并不实际运行(这个参数在很多命令里都可以附带,很有用)
--dry-run | -n
// 只跟踪tracked状态的文件,无视新增的文件
--update | -u
扩展:
// 针对所有改动
git add .
git add *
// 针对某个文件
git add myFile.cs
// 针对多个文件(用空格间隔)
git add myFile.cs myFile2.cs
// 针对某个文件夹(递归)
git add myFolder/
// 针对某类型文件
git add *.cs
常用形式:
// 将“暂存区”所有改动提交至“当前分支”
git commit -m
配置了git的默认编辑器之后,不带-m可自动打开编辑器处理描述文字
附加参数:
// 先自动执行add操作,再commit
--all | -a
// 修改上一次的commit,会重新生成一个commitId(可以只修改message,也可以附带add到暂存区的改动)
--amend
// 演示执行效果
--dry-run
// 添加描述文字
--message= | -m
// 修改指定的commit内容,会生成一个以“fixup!”开头的描述文字的commit提交
// fixup功能需要配合附带--autosquash参数的rebase命令执行
--fixup=
// 修改指定的commit内容和描述文字,会生成一个以“fixup!”开头的描述文字的commit提交
--fixup=amend:
// 修改指定的commit描述文字,会生成一个以“fixup!”开头的描述文字的commit提交
--fixup=reword:
常用形式:
// 显示所有提交的历史记录
git log
附加参数:
// 将信息压缩在一行显示
--oneline
// 以分支图的形式显示历史信息
--graph
// 限制显示的历史记录数目
--max-count= | -n | -
// 使用作者来过滤显示的历史记录
--author= | --committer=
常用形式:
// 删除文件,并记录这次的删除操作(删除操作会记入暂存区,用于commit的时候对分支中的文件进行处理)
git rm myFile.cs
附加参数:
// 强制删除,如果文件被改动过(不管是否add到暂存区),使用普通删除都会报错,需要使用强制删除
--force | -f
// 删除暂存区文件,但保留工作区文件
--cached
常用形式:(这个功能其实用得不多,因为在VisualStudio或VSCode等IDE环境中在合并的时候有更完善的diff功能)
// 对比文件。当工作树有改动,暂存区为空,对比工作树与最后一次commit的共同文件差异;当工作树与暂存区都有改动,对比的是工作树与暂存区的共同文件差异
git diff
附加参数:
// 对比暂存区与最后一次commit间的差异文件
--cached | --staged
可以从“暂存区”向“工作树”还原;
也可以从“当前分支”向“暂存区”还原;
也可以从“当前分支”向“工作树”还原
常用形式:(restore
命令只修改工作树和暂存区,不会进行提交)
// 还原工作树文件,即撤销更改
git restore myFile.cs
附加参数:
// 还原工作树文件(这也是默认参数),优先以暂存区为源,如果暂存区没有,则以Head为源
--worktree | -W
// 还原暂存区文件
--staged | -S
// 指定被还原文件的来源,默认使用Head
--source= | -s
扩展:
// 针对当前目录下的所有内容
git restore .
// 针对顶层目录下的所有内容
git restore :/
// 针对某个文件
git restore myFile.cs
// 针对某类文件
git restore '*.cs'
常用形式:(该命令通过移动本地Head指针至指定的提交上,以此进行回退)
// 默认还原工作树文件(使用相对引用,也可直接使用commit-id)
git reset --hard HEAD^
附加参数:
// 重置工作树和暂存区的改动
--hard
// 保留工作树的改动,重置暂存区的改动(默认)
--mixed
// 保留工作树和暂存区的改动
--soft
常用形式:(该命令不会更改之前的提交,而是进行新的提交,以此还原其它提交所做的变动,常用于已经push到远端的提交进行功能回撤)
// 回滚某个指定的commit,会自动进行新的提交,生成新的commit-id
git revert
附加参数:
// 回滚的改动不自动进行提交,而是放入暂存区
--no-commit | -n
// 回滚范围内所有的commit,并按照顺序依次生成多个新的提交
// 范围:从(不包含)到(包含)
// 新提交的顺序:先生成,最后生成(并不包含)
// 想要只生成单个commit,可以使用-n参数,再手动提交一次即可
git revert ..
// 针对merge合并的分支进行回滚,相当于抛弃某个分支内的所有修改,并生成新的提交
// 是1或者2,表示需要保留的分支
// 可以用show命令查看合并点的信息,在显示的“Merge行”可以看到commit顺序,从左到右分别为1、2
--mainline | -m <合并点的commit-id>
// 继续回滚操作(回滚过程中,如果有冲突,会产生暂停,需要解决完冲突)
--continue
// 跳过当前commit,继续余下的回滚操作(回滚过程中)
--skip
// 放弃回滚操作,回到操作前的状态(回滚过程中)
--abort
// 退出回滚操作,保持当前状态,不回到操作前的状态(回滚过程中)
--quit
这里要提一下在合并的分支上进行回滚操作的一些注意事项:
假设当前的情况是在feature分支(临时功能分支)上开发了一个新功能,并合并到了dev分支(主要开发分支)上,之后又在dev分支上提交了几个commit,在某一天突然发现该新功能存在Bug,需要revert掉,于是执行git revert -m 1 xxxx
生成了回滚commit提交W,如下:
回到feature分支后,进行了两次提交C和D之后,修复了新功能中的Bug,之后又向dev分支合并,如下:
此时会出现一个问题,就是A提交和B提交中的改动不会被合并到dev分支中,因为它们已经在W提交中被回滚了。
此时想要把A提交和B提交的改动一起合并到dev分支,则需要在合并前在dev分支上再做一些操作,把回滚M提交
而生成的W提交
再做一次回滚,如下:(M’是回滚W而生成的提交)
这样之前的A提交和B提交的改动就能正确地合并进dev分支了。参考revert-a-faulty-merge5
常用形式:
// 查看之前的改动记录,可以得到检索列表,在其它命令中可使用检索到的commit-id进行操作,也可以使用改动记录标识`HEAD@{n}`作为commit-id进行操作
git reflog
在Git的相关图形界面上对分支进行的操作也同样能在reflog命令里被查找到。
常用形式:(该命令用于将一个分支上的某些commit改动应用到目标分支上,而不必将整个分支的改动合并到目标分支)
// 拣选特定的commit到当前分支,会产生新的commit提交
git cherry-pick
附加参数:
// 将改动放入工作区,不产生新的提交
--no-commit | -n
// 针对merge合并的节点也可以拣选,但需要指定拣选的改动来自哪个父级的代码变动
// 是1或者2,表示需要保留的分支
// 可以用show命令查看合并点的信息,在显示的“Merge行”可以看到commit顺序,从左到右分别为1、2
--mainline | -m <合并点的commit-id>
// 继续拣选操作(拣选过程中,如果有冲突,会产生暂停,需要解决完冲突)
--continue
// 跳过当前commit,继续余下的拣选操作(拣选过程中)
--skip
// 放弃拣选操作,回到操作前的状态(拣选过程中)
--abort
// 退出拣选操作,保持当前状态,不回到操作前的状态(拣选过程中)
--quit
扩展:
// 打开外部编辑器,编辑描述
--edit | -e
// 拣选多个commit到当前分支
git cherry-pick
// 拣选多个连续的commit到当前分支,不包含`commit-id1`,如果需要包含,可用`^`
git cherry-pick ..
常用形式:(以某个commit为基点,重构之后的commit(一般针对分叉的情况),在分支图上形成一条直线,强迫症患者之友)
// 以指定的上游分支与当前分支的分叉点为基,进行重构,当前分支的所有改动会链接在上游分支的最后一个commit之后
git rebase
附加参数:
// 在衍合操作时使用交互模式,可以在编辑器内手动对每个commit指定相应的操作(pick、drop、squash等)来完成整个衍合
--interactive | -i
// 自动按照需要的方式完成衍合操作,一般需要之前的commit中附带了--fixup、--squash、--amend等待定操作来指示自动衍合的行为,即commit中会出现以“fixup!”或“amend!”或"squash!"为开头的描述文字
--autosquash
// 将当前分支在新的基点处进行衍合
--onto
// 继续衍合操作(衍合过程中,如果有冲突,会产生暂停,需要解决完冲突)
--continue
// 跳过当前commit,继续余下的衍合操作(衍合过程中)
--skip
// 放弃衍合操作,回到操作前的状态(衍合过程中)
--abort
// 退出衍合操作,保持当前状态,不回到操作前的状态(衍合过程中)
--quit
关于rebase的理解:
其实rebase的本质就是把当前产生变化的commit在需要的地方重新执行一遍,以相同的信息重构基点之后的commit,重构会导致大部分commit-id的变化,以master为基进行衍合不会修改master,这意味着你的子分支的commit将在master的最后一个commit之后执行,即变成一条直线。
假设当前的情况是在feature分支(功能分支)上开发了新功能,并合并到了dev分支(主要开发分支)上,如果我们在dev分支上使用git merge feature
进行合并(merge
是合并指定分支到当前分支的操作),会产生如下分支图:
merge
可以正常地工作,但这只是一个分支的合并,试想在一个分支众多、存在交叉提交的环境下,分支图会像没有整理好的电缆线箱一样,错综复杂,很快失去可读性。但是如果使用rebase
的方式来处理,在feature分支中使用git rebase dev
,会产生如下分支图:
分支图里没有分叉,featrue分支的所有commit以dev分支头(commit 4)为基点,进行了重构,这期间feature分支上的所有commit对应的commit-id都会重新生成,目前分支的合并还未完成,还需要切换到dev分支,在dev分支上使用git merge feature
,此时的合并会自动使用fast-forward
快进合并(直接移动dev的分支头指针到feature的分支头指针位置)来完成合并,分支图最终形成一条直线,如下:
常用形式:
// 添加远端仓库,Remote一般约定为origin,也可以自己起名,RemoteURL有以下两种形式
// ssh协议形式:[email protected]:myName/myProject.git
// http协议形式:https://github.com/myName/myProject.git
git remote add
常用形式:
// 显示所有远端仓库的信息
git remote -v
常用形式:
// 修改远端仓库名在本地的简称
git remote rename
常用形式:
// 移除简称对应的远端仓库
git remote rm
因为git版本的不断发展,在新关键字的加入的同时为了兼容老版本,同一个操作会有不同的命令能够完成。
常用形式:
// 查看所有分支
git branch -a
附加参数:
// 查看远端分支
--remotes | -r
// 查看本地分支对应的远程分支
-vv
常用形式:
// 从当前Head创建本地分支
git branch
// 从当前Head创建本地分支,并切换到新分支
git checkout -b
git switch -c
// 从远程分支创建本地分支
git branch --track /
git checkout -b /
git switch -c /
附加参数:
// 创建分支
(git checkout) -b
(git switch) --create | -c
// 强制创建分支,分支名被占用的情况下会覆盖旧分支
(git branch) --force | -f
(git checkout) -B
(git switch)--force-create | -C
// 从远程分支创建本地分支的同时附带追踪关系
(git branch)(git checkout)(git switch) --track | -t
// 从远程分支创建本地分支的同时不附带追踪关系
(git branch)(git checkout)(git switch) --no-track
扩展:
// 从某个commit或者tag创建分支
git branch
常用形式:
git checkout
git switch
常用形式:
// 删除本地分支,删除分支时不能停留在当前分支上,否则会报错
git branch -d
// 删除远端分支
git push -delete origin
附加参数:
// 创建分支
(git checkout) -b
(git switch) --create | -c
// 强制创建分支,分支名被占用的情况下会进行覆盖
(git branch) --force | -f
(git checkout) -B
(git switch)--force-create | -C
常用形式:
// 移动分支会附带分支上的配置和操作记录(reflog)
git branch -m
附加参数:
// 强制移动分支,分支名被占用的情况下会进行覆盖
-M
// 复制分支会附带旧分支上的配置和操作记录(reflog)到新分支
git branch -c
附加参数:
// 强制复制分支,分支名被占用的情况下会进行覆盖
-C
// 合并指定分支到当前分支
git merge
附加参数:
// 附带描述文字
-m
// 是否执行fast-forward快进合并
--ff
--no-ff
--ff-only
// 合并时执行squash挤压操作,并将结果暂停commit提交,执行完成需要手动提交。squash会在合并时将指定分支的所有commit放入一个新生成的commit之中,新commit的作者为当前的操作者,指定分支上的历史提交记录将不会转移到合并分支,意味着历史提交记录的丢失
--squash
// 继续合并操作(合并过程中,如果有冲突,会产生暂停,需要解决完冲突)
--continue
// 放弃合并操作,回到操作前的状态(合并过程中)
--abort
// 退出合并操作,保持当前状态,不回到操作前的状态(合并过程中)
--quit
常用形式:
// 建立分支与远端分支的追踪关系
git branch -u /
附加参数:
// 创建追踪关系,未指定则默认当前分支
--set-upstream-to | -u
// 解除追踪关系,未指定则默认当前分支
--unset-upstream
常用形式:
// 从建立追踪关系的远端分支拉取改动到本地分支,如果当前分支只有一个追踪分支,则本地分支名可省;如果当前分支只与一个主机存在追踪关系,则远端主机名可省
git pull :
是具体引用
: ,完整形式是
[+]
,可以写成: ,远端->本地,与push的方向相反。
:
关于无参数的git pull
行为,可以参考Git push与pull的默认行为6
附加参数:
// 是否执行fast-forward快进合并
--ff
--no-ff
--ff-only
// 以rebase的方式执行pull合并
--rebase | -r
常用形式:
// 推送当前分支到建立追踪关系的远端分支,如果当前分支只有一个追踪分支,则远端分支名可省;如果当前分支只与一个主机存在追踪关系,则远端主机名可省
git push :
是具体引用
: ,完整形式是
[+]
,可以写成: ,本地->远端。
:
关于无参数的git push
行为,可以参考Git push与pull的默认行为6
附加参数:
// 强制推送,使用本地分支覆盖远端分支
--force | -f
// 推送所有标签到远端
--tags
扩展:
// 直接推送(假设远端主机名origin,远端同名分支dev)
git push origin dev:refs/heads/dev
// 如果远端开始了code review,使用上面的push会产生“remote rejected”错误,需要使用`refs/for/`的形式
git push origin dev:refs/for/dev
// 推送单个标签,第二行是完整形式
git push origin
git push origin refs/tags/tagname:refs/tags/tagname
// 删除远程标签,推送空标签到远端相当于删除标签
git push origin :refs/tags/tagname
git push origin --delete
常用形式:
// 储藏当前工作树和暂存区的改动到储藏栈
git stash -m
附加参数:
// 查看储藏栈内的内容
git stash list
// 显示某个储藏的详细内容
git stash show []
// 带push允许指定具体的文件
git stash push
// 从储藏栈内弹出储藏的场景,恢复至工作树,不带储藏记录标识[]的话,默认指定为储藏栈顶元素
git stash pop []
// 应用储藏的场景,恢复至工作树,但不从储藏栈内弹出
git stash apply []
// pop和apply命令中使用,用于将储藏的内容按照原有的形式分别放回工作树和暂存区,默认情况下储藏将全部放回工作树
--index
// 从储藏栈内丢弃某个储藏,不带储藏记录标识[]的话,默认指定为储藏栈顶元素
git stash drop []
// 清空储藏栈
git stash clear
stash支持把当前工作区和暂存区保存起来然后在切换到其它分支上再pop或者apply出来
常用形式:(标签相当于一个指向对应commit的不可移动的指针,对某个commit进行标记)
// 对指定的commit进行标签标记
git tag
附加参数:
// 生成附注标签,附注标签会包含日期、作者、描述文字等相关信息,并且有自己的commit-id,附注标签可以看作commit的别名,一般用于推送到远端,而普通标签一般只在本地使用
--annotate | -a
// 删除指定标签
--delete | -d
// 查看标签列表
--list | -l
// 为附注标签添加描述文字,普通标签不能带该参数
--message= | -m
扩展:
// 查看远程标签
git ls-remote --tags origin
查看之前的改动记录,得到检索列表,可使用检索到的commit-id进行操作,也可以使用改动记录标识HEAD@{n}
作为commit-id进行操作
git reflog
操作对应的commit-id或者改动标识
git reset --hard HEAD@{5}
查看所有分支
git branch -a
查看远端分支情况
git remote show origin
同步已删除的远程分支在本地的索引,支持–dry-run
git remote prune origin
移动(相当于重命名)本地分支,带配置信息和操作记录(reflog)
git branch -m
删除远端旧分支
git push --delete origin
推送修改后的本地分支
git push origin :
建立本地分支和新远端分支的追踪关系
git branch --set-upsteam-to /
场景一:在某个分支上(例如dev分支)直接开发,拉取远端分支(远端dev分支)代码的时候,在pull命令中附带–rebase参数进行衍合。
git pull --rebase origin dev:refs/heads/dev
场景二:在某个功能分支上(例如feature)开发,开发完成后需要向主分支(dev分支)合并,此时需要先从远端向本地更新dev分支,再对本地的feature分支进行以dev分支为基的衍合。
切换到dev分支上:
git switch dev
更新远端dev分支到本地dev分支(也使用衍合方式):
git pull --rebase origin dev:refs/heads/dev
切换回feature分支:
git switch feature
对feature分支以dev为基进行衍合:
> `git rebase dev`
再次切换到dev分支上:
git switch dev
将feature分支合并入dev分支(自动fast-forward):
git merge feature
突然觉得之前的某次提交不够完美,需要重新修正一下,但是在那次提交之后又进行了很多次提交,需要这样操作:
查找需要修正的提交的commit-id:
git log --oneline --graph
在工作树上进行修正并将改动放入暂存区:
git add .
进行fixup方式的提交,指定需要被修正的commit的commit-id,执行完成后本地会生成一个commit,它的描述文字会以“fixup!”开头:
git commit --fixup=
进行对需要修正的commit的rebase
衍合操作,此处指定的commit-id是作为基点的commit,所以不能是被修正的commit本身,而应该比它更早某个commit即可,这里我们用相对引用“^”符号来指定被修正的commit的父级:
git rebase -i --autosquash ^
待补充
待补充
待补充
它们其实是一个东西,都是合并请求,只是因为不同的git站点的方式不同
细节待补充
Git的本质是针对commit的管理,Git的上手难度不难,只使用基本功能也能正常地进行工作,但是一些高级的功能能够更好地帮助维护者进行版本管理,需要在实践中不断熟练,以后有更深入的理解再来添加内容,下面列出一些使用时候的注意事项:
Git对大小写敏感
虽然Git对大小写敏感,但会在仓库克隆或初始化时,根据当前系统来设置是否忽略大小写,在Windows下会设置为忽略大小写。
少量多次进行commit提交
将任务划分成比较细的粒度,多次commit提交,在解决合并冲突的时候会更加轻松,cherry-pick等操作也会更加清晰。
尽量少用checkout命令,防止误操作
对分支的操作不推荐使用checkout命令,使用switch、branch可以替代分支操作方面的功能,因为checkout除了可以操作分支,它还可以操作文件,可能会因为误操作而修改工作区。
切换分支时,保证当前分支的工作区和缓存区是干净的
如果当前分支工作树和缓存区是干净的(即你在上次commit之后再没做任何改动,可用git status查看),切换到别的分支跳不会有影响。但是如果当前分支下有未完成的工作,切换到别的分支会把改动带过去,可以stash后再切换。
避免revert -m
对合并点的回滚
针对合并的commit节点进行revert回滚操作,不会在未来的分支有提示,如果未来需要把回滚的分支重新合并到主分支上,需要revert之前执行revert的那个commit节点(非合并节点),第一容易遗忘,第二还需要艰难地沿着分支树寻找到执行revert的节点。(文中有详细提到)
慎用git push -f
操作
这个操作实际上只是把问题丢给了别人(即同在一个远端仓库提交代码的小伙伴),而别人并不清楚你把毛线打了什么结,要解开这个结,花费的成本往往会成倍增长,还记得开头提到的新闻吗……
使用rebase时要注意对base的选择
如果试图通过以子分支(例如feature功能分支)为基来衍合主分支(master分支),就是在自找苦吃,除非你的仓库只有你自己在使用,否则你会花费更多的时间去修复你的错误,或者
git push -f
把问题丢给别人……
对squash功能要特别注意
带有squash参数的命令,可能会涉及到使用rebase来完成最后的操作,会导致重构,使得某些commit-id发生变化,在协作的仓库中使用需要特别注意
如果不完全明白某个操作的含义,不要随便使用
查看相关文档,正确地使用命令和参数,可以先使用–dry-run(很多命令附带这个参数)来演示执行一下,否则当你试图操作,但是并不知道自己在做什么的时候,就是恶梦的开始,不正确的使用有可能会丢失大量工作,
Sourcetree:https://www.sourcetreeapp.com ↩︎
git官方文档:https://git-scm.com/docs ↩︎
Git教程-廖雪峰的官方网站:https://www.liaoxuefeng.com/wiki/896043488029600 ↩︎
Git中三大对象commit、tree和blob之间的关系:https://blog.csdn.net/yao_94/article/details/88648468 ↩︎
revert-a-faulty-merge:https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt ↩︎
Git push与pull的默认行为:https://segmentfault.com/a/1190000002783245 ↩︎ ↩︎