Git科普文,Git基本原理&各种骚操作
https://mp.weixin.qq.com/s/csEgAjJwH75_IvAnFBIuvw
Git简单介绍**
Git是一个分布式版本控制软件,最初由Linus Torvalds创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计。
\Git工作流程以及各个区域**

Workspace:工作区
Staging/Index:暂存区
Local Repository:本地仓库(可修改)
/refs/remotes:远程仓库的引用(不可修改)
Remote:远程仓库
\Git文件状态变化**

\Git各种命令**
Git简单命令
Git常用命令
git clone
git stash
git config
git remote
git add
git commit
git branch
git checkout
git tag
git push
git reset
git diff
git show
git log
git rebase
git restore
git revert
\Git骚操作**
Git命令不能自动补全?(Mac版)
❝我见过有的人使用Git别名,反正因为有自动补全的存在,我从来没用过Git别名。不过我的确将我的rm -rf命令替换成了别的脚本了...
安装bash-completion
添加 bash-completion 到~/.bash_profile:
❝shell有不同种类,我这里使用的是bash。
代码没写完,突然要切换到别的分支怎么办?
暂存未提交的代码
还原暂存的代码
怎么合并其他分支的指定Commit?
使用cherry-pick命令
本地临时代码不想提交,怎么一次性清空?
还原未commit的本地更改的代码
还原包含commit的代码,到跟远程分支相同
已经提交的代码,不需要了,怎么当做没提交过?
还原到上次commit
还原到当前之前的几次commit
强制推送到远程分支,确保没有其他人在push,不然可能会丢失代码
历史commit作者邮箱写错了,怎么一次性改过来?
使用git filter-branch命令。
复制下面的脚本,替换相关变量
OLD_EMAILCORRECT_NAMECORRECT_EMAIL
脚本如下:
强制推送替换
不小心把不该提交的文件commit了,怎么永久删除?
也是使用git filter-branch命令。
强制推送覆盖远程分支。
强制推送覆盖远程tag。
怎么保证团队成员提交的代码都是可运行的?
这里想说的是使用git hooks,一般在项目目录.git/hooks,客户端可以使用hooks,控制团队commit提交规范,或者push之前,自动编译项目校验项目可运行。服务端可以使用hooks,控制push之后自动构建项目,merge等自动触发单元测试等。
git reset --hard命令,执行错了,能恢复吗?
git reset --hard命令,执行错了,能恢复吗?查看当前commit log

误操作git reset --hard 8529cb7

执行git reflog

还原到之前的样子

公司使用GitLab,平时还用GitHub,多账号SSH,如何配置?
编辑 ~/.ssh/config文件 没有就创建
Git commits历史如何变得清爽起来?
多用git rebase。
比如,开发分支是feature,主干分支是master。我们在进行代码合并的时候,可以执行下面的命令。
然后我们再切换到master分支,执行git merge feature,就可以进行快进式合并了,commmits历史就不会有交叉了。后文我们会详细讲解。
❝git rebase会更改commit历史,请谨慎使用。
下面的图是Guava项目的commit记录。

如何修改已经提交的commit信息?
原始Git提交记录是这样的

执行git rebase -i 070943d,对指定commitId之前的提交,进行修改

修改后Git提交记录变成了这样

❝git rebase -i非常实用,还可以将多个commit合并成一个等很多事情,务必要记下。
不小心执行了git stash clear怎么办?
git stash clear怎么办?执行之后,可以找到相关丢失的commit-id,然后merge一下即可。
该命令上可以找回git add之后被弄丢的文件。
❝啥?你没执行过git add代码就丢了?别怕,一般编译器有Local History赶紧去试试吧。
详解git merge
我们执行git merge命令的时候,经常会看到Fast-forward字样,Fast-forward到底是个什么东西?
其实,git merge一般有三种场景。
快进式合并
举个栗子,假如初始存在master和hotfix分支是这样的。

然后我们在hotfix分支加了些代码,分支变成这样了。

这个时候,我们将hotfix分支,merge到master,即执行git merge hotfix。

由于的分支hotfix所指向的提交C3是C2的直接后继, 因此Git会直接将指针向前移动。换句话说,如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 快进(fast-forward)。
三方合并
再举个栗子,假如初始存在feature和master分支情况是这样的。

然后我们在feature分支加了些代码,而master分支也有人加了代码,现在分支变成这样了。

这个时候,我们将feature分支,merge到master,即执行git merge feature。

和之前将分支指针向前推进所不同的是,Git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
❝所以我们也知道了,为什么有的时候merge之后会产生新的commit,而有的时候没有。
遇到冲突时的合并
如果在两个分支分别对同一个文件做了改动,Git就没法直接合并他们。当遇到冲突的时候,Git会自动停下来,等待我们解决冲突。就像这样
我们可以在合并冲突后的任意时刻使用git status命令来查看那些因包含合并冲突而处于未合并unmerged状态的文件。
待解决冲突的文件Git会以未合并的状态标识出来,出现冲突的文件会出现一些特殊的区段,看起来像下面的样子。
<<<<<<<后面的是当前分支的引用,我们的例子中,就代表master分支。>>>>>>>后面表示的是要合并到当前分支的分支,即dev分支。=======的上半部分,表示当前分支的代码。下半部分表示dev分支的代码。
我们可以把上面的测试内容改成下面的样子来解决冲突
在解决了所有文件里的冲突之后,对每个文件使用git add命令来将其标记为冲突已解决。
解决冲突的过程中,每一步都可以执行git status查看当前状态,Git也会给出相应提示,进行下一步操作。当我们所有的文件都暂存之后时,执行git status时,Git会给我们看起来像下面的这种提示
然后,我们根据提示执行git commit。
然后,我们保存这次提交就完成了这次冲突合并。
\详解git rebase**
rebase做了什么
举个栗子。我们同样用刚才merge的场景。
如果不用rebase,使用merge是下面这样的,合并分支的时候会产生一个合并提交,而且会有分支交叉的情况。

使用rebase是下面这样的。

然后,切换到master分支,进行一次快进式合并。

❝变基实际上就是基于其他分支重塑当前分支。变基之后,当前分支就相当于是基于最新的其他分支新加了一些commit,这样的话就可以进行快进式合并了。
rebase原理
它的原理是首先找到这两个分支(即当前分支 dev、变基操作的目标基底分支master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底C3, 最后以此将之前另存为临时文件的修改依序应用,也就是在C3后面添加C4'、C5'。
\Git对象与快照**
提到Git,总有人会说快照,快照是个什么鬼?
实际上,Git是一个内容寻址文件系统,其核心部分是一个简单的键值对数据库。将Git中的对象,存储在.git/objects目录下。
Git对象主要分为,数据对象(blob object)、树对象(tree object)、提交对象(commit object)、标签对象(tag object)。
数据对象
我们新建一个目录,然后在该目录下执行git init初始化一个Git项目。
然后,查看.git/objects目录下都有什么。
接着,我们写一个文件echo '1111' > 111.txt,并执行git add之后,再查看。
我们发现.git/objects目录下,多了个文件和目录。实际上,Git会将我们的文件数据外加一个头部信息header一起做SHA-1校验运算而得到校验和。然后,校验和的前2个字符用于命名子目录,余下的38个字符则用作文件名。
我们可以使用下面的命令,显示在Git对象中存储的内容。
这就是我们在上文写入的文件内容。
上述类型的对象称之为数据对象(blob object)。数据对象,仅保存了文件内容,而文件名字没有被保存。
树对象
数据对象大致对应UNIX中的inodes或文件内容,树对象则对应了UNIX中的目录项。一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的SHA-1指针,以及相应的模式、类型、文件名信息。
通常,Git根据某一时刻暂存区(即index区域)所表示的状态创建并记录一个对应的树对象。
当我们执行过git add之后,暂存区就有内容了,我们可以通过Git底层命令,生成树对象。
查看该树对象的内容。
提交对象
数据对象保存了数据的内容,树对象可以表示当前目录的快照。但是,若想重用这些快照,必须记住树对象的SHA-1哈希值。而且,我们也不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。而以上这些,正是提交对象(commit object)能保存的基本信息。
我们对当前暂存区进行一次提交,git commit -m "first commit"。
然后查看一下log找到该次提交的commit哈希值。
接着,我们查看一下该提交对象的内容。
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交);之后是作者/提交者信息(依据你的user.name和user.email配置来设定,外加一个时间戳);留空一行,最后是提交注释。
标签对象
标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。
❝实际上Git中的各种对象都是类似的,只不过因为各种对象自身功能不同,存储结构不同而已。
Git引用-我从远程拉的代码不是最新的?
Git引用相当于是Git中特定哈希值的别名。一长串的哈希值不是很友好,但是起个别名,我们就可以像这样git show master、git log master的去使用他们。
Git中的引用存储在.git/refs目录下。我们可以执行find .git/refs/查看当前Git项目中都存在哪些引用。
HEAD引用
在.git目录下有一个名字叫做HEAD的文件,HEAD文件通常是一个符号引用(symbolic reference)指向目前所在的分支。所谓符号引用,表示它是一个指向其他引用的指针。
如果我们在工作区checkout一个SHA-1值,HEAD引用也会指向这个包含Git对象的SHA-1值。
标签引用
Git标签分为,附注标签和轻量标签。轻量标签,使用git tag v1.0即可创建。附注标签需要使用-a选项,即git tag -a v1.0 -m "my version 1.0"这种。
轻量标签就是一个固定的引用。附注标签需要创建标签对象,并记录一个引用来指向该标签对象。
远程引用
不熟悉Git的同学,可能会犯这样一个错误。其他同学让他拉取一下远程最新的master分支代码,他可能直接用IDE找到本地的远程分支的引用,也就是origin/master,直接checkout一个本地分支。
其实,origin/master只是远程分支的一个引用,不一定跟远程分支代码同步,我们可以用git fetch或者git pull来让origin/master和远程分支同步。
参考文献:
[1]: https://git-scm.com/
Last updated
Was this helpful?