开发者社区> 问答> 正文

我用四个命令,总结了 Git 的所有套路 6月8日 【今日算法】

我先开一会儿吐槽大会,Git 这东西我用了两年,根本尼玛用不明白。

我搞不明白的一个重要原因就是,命令的功能太杂,有时候一个需求可以用好几种命令解决,而且有的命令还 tm 有别名。这导致什么问题呢,我在网上找到的答案五花八门,竟然都能达成目的,难以找到规律,毫无套路可言。对于我这种不喜欢动脑子,只喜欢玩套路的人来说,简直不能接受。

以前我用 Git,就知道add .,然后commit -m,最后push origin master一套带走,或者就是把 Git 作为下载器,去clone别人的项目。但是在工作中呢,和别人一起开发代码,就需要处理一些复杂情况,比如解决冲突,比如手残恢复,等等等实用场景,这些我在后文都会列举。

对于工具的学习,我认为应该多做减法,只捡最有用的学,那些奇技淫巧不学也罢,应该把时间投入更有价值的事情中。

所以本文不是一个大而全 Git 命令的使用手册,而是根据实际工作中最常见问题,提供小而美的解决方案,仅仅涉及四个命令:add,commit,reset,checkout

PS:旧文 一起刷题学习 Git/SQL/正则表达式 介绍了一个可视化学习 Git 的网站,非常优秀,值得一刷。

一、预备知识

首先,在进入 Git 的各种神仙操作之前,一定要明白 git 的三个「分区」是什么,否则的话你一定没办法真正理解 Git 的原理

本地 Git 的三个分区分别是:working directory,stage/index area,commit history。

1.jpg

working directory是「工作目录」,也就是我们肉眼能够看到的文件,后文我们称其为work dir区。

当我们在work dir执行git add相关命令后,就会把work dir中的修改添加到「暂存区」stage area(或者叫index area)中去,后文我们称暂存区为stage 区。

stage中存在修改时,我们使用git commit相关命令之后,就会把stage中的修改保存到「提交历史」commit history,也就是HEAD指针指向的位置。后文我们称「提交历史」为history区。

关于commit history我们多说几句,任何修改只要进入commit history,基本可以认为永远不会丢失了。每个commit都有一个唯一的 Hash 值,我们经常说的HEAD或者master分支,都可以理解为一个指向某个commit的指针。

work dirstage区域的状态,可以通过命令git status来查看,history区域的提交历史可以通过git log命令来查看。

好的,如果上面的内容你都能够理解,那么本文就完全围绕这三个概念展开,下面就是一个「状态转移图」:

2.jpg

二、本地 Git 极简教程

需求一,如何把work dir中的修改加入stage

3.jpg

这个是最简单,使用 git add 相关的命令就行了。顺便一提,add有个别名叫做stage,也就是说你可能见到git stage相关的命令,这个命令和git add命令是完全一样的。

风险等级:无风险。

理由:不会改变任或撤销任何已作出的修改,而且还会将work dir中未追踪的修改(Untracked file)添加到暂存区stage中进行追踪。

需求二,如何把stage中的修改还原到work dir

4.jpg

这个需求很常见,也很重要,比如我先将当前work dir中的修改添加到stage中,然后又对work dir中的文件进行了修改,但是又后悔了,如何把work dir中的全部或部分文件还原成stage中的样子呢?

来个实际场景,我先新建两个文件,然后把他们都加到stage

$ touch a.txt b.txt
$ git add .
$ git status
On branch master
Changes to be committed:
    new file:   a.txt
    new file:   b.txt

然后我又修改了a.txt文件:

$ echo hello world >> a.txt
$ git status
On branch master
Changes to be committed:
    new file:   a.txt
    new file:   b.txt

Changes not staged for commit:
    modified:   a.txt

现在,我后悔了,我认为不应该修改a.txt,我想把它还原成stage中的空文件,怎么办?

答案是,使用 checkout 命令:

$ git checkout a.txt
Updated 1 path from the index

$ git status
On branch master
Changes to be committed:
    new file:   a.txt
    new file:   b.txt

看到了么,输出显示从index区(也就是stage区)更新了一个文件,也就是把work dira.txt文件还原成了stage中的状态(一个空文件)。

当然,如果work dir中被修改的文件很多,可以使用通配符全部恢复成stage

$ git checkout .

有一点需要指出的是,checkout命令只会把被「修改」的文件恢复成stage的状态,如果work dir中新增了新文件,你使用git checkout .是不会删除新文件的。

风险等级:中风险。

理由:在work dir做出的「修改」会被stage覆盖,无法恢复。所以使用该命令你应该确定work dir中的修改可以抛弃。

需求三,将stage区的文件添加到history

5.jpg

很简单,就是 git commit 相关的命令,一般我们就是这样用的:

$ git commit -m '一些描述'

再简单提一些常见场景, 比如说commit完之后,突然发现一些错别字需要修改,又不想为改几个错别字而新开一个commithistory区,那么就可以使用下面这个命令:

$ git commit --amend

这样就是把错别字的修改和之前的那个commit中的修改合并,作为一个commit提交到history区。

风险等级:无风险。

理由:不会改变任或撤销任何已作出的修改,而且还会将stage区的修改加入history区并分配一个 Hash 值。只要不乱动本地的.git文件夹,进入history的修改就永远不会丢失。

需求四,将history区的文件还原到stage

6.jpg

这个需求很常见,比如说我用了一个git add .一股脑把所有修改加入stage,但是突然想起来文件a.txt中的代码我还没写完,不应该把它commithistory区,所以我得把它从stage中撤销,等后面我写完了再提交。

$ echo aaa >> a.txt; echo bbb >> b.txt;
$ git add .
$ git status
On branch master
Changes to be committed:
    modified:   a.txt
    modified:   b.txt

如何把a.txtstage区还原出来呢?可以使用 git reset 命令:

$ git reset a.txt

$ git status
On branch master
Changes to be committed:
    modified:   b.txt

Changes not staged for commit:
    modified:   a.txt

你看,这样就可以把a.txt文件从stage区移出,这时候进行git commit相关的操作就不会把这个文件一起提交到history区了。

上面的这个命令是一个简写,实际上reset命令的完整写法如下:

$ git reset --mixed HEAD a.txt

其中,mixed是一个模式(mode)参数,如果reset省略这个选项的话默认是mixed模式;HEAD指定了一个历史提交的 hash 值;a.txt指定了一个或者多个文件。

该命令的自然语言描述是:不改变work dir中的任何数据,将stage区域中的a.txt文件还原成HEAD指向的commit history中的样子。就相当于把对a.txt的修改从stage区撤销,但依然保存在work dir中,变为unstage的状态。

风险等级:低风险。

理由:不会改变work dir中的数据,会改变stage区的数据,所以应确保stage中被改动数据是可以抛弃的。

需求五,将work dir的修改提交到history

7.jpg

这个需求很简单啦,先git add然后git commit就行了,或者一个快捷方法是使用命令git commit -a

风险等级:无风险。

理由:显而易见。

需求六,将history区的历史提交还原到work dir

8.jpg

这个场景,我说一个极端一点的例子:比如我从 GitHub 上clone了一个项目,然后乱改了一通代码,结果发现我写的代码根本跑不通,于是后悔了,干脆不改了,我想恢复成最初的模样,怎么办?

依然是使用checkout命令,但是和之前的使用方式有一些不同:

$ git checkout HEAD .
Updated 12 paths from d480c4f

这样,work dirstage中所有的「修改」都会被撤销,恢复成HEAD指向的那个history commit

注意,类似之前通过stage恢复work dircheckout命令,这里撤销的也只是修改,新增的文件不会被撤销。

当然,只要找到任意一个commit的 HASH 值,checkout命令可就以将文件恢复成任一个history commit中的样子:

$ git checkout 2bdf04a some_test.go
Updated 1 path from 2bdf04a
# 前文的用法显示 update from index

比如,我改了某个测试文件,结果发现测试跑不过了,所以就把该文件恢复到了它能跑过的那个历史版本……

风险等级:高风险。

理由:这个操作会将指定文件在work dir的数据恢复成指定commit的样子,且会删除该文件在stage中的数据,都无法恢复,所以应该慎重使用。

三、其他技巧

需求一,合并多个commit

比如说我本地从17bd20cHEAD有多个commit,但我希望把他们合并成一个commit推到远程仓库,这时候就可以使用reset命令:

$ git reset 17bd20c
$ git add .
$ git commit -m 'balabala'

回顾一下刚才说的reset命令的作用,相当于把 HEAD 移到了17bd20c这个commit,而且不会修改work dir中的数据,所以只要addcommit,就相当于把中间的多个commit合并到一个了。

需求二,由于HEAD指针的回退,导致有的commitgit log命令中无法看到,怎么得到它们的 Hash 值呢

再重复一遍,只要你不乱动本地的.git文件夹,任何修改只要提交到commit history中,都永远不会丢失,看不到某些commit只是因为它们不是我们当前HEAD位置的「历史」提交,我们可以使用如下命令查看操作记录:

$ git reflog

比如resetcheckout等等关键操作都会在这里留下记录,所有commit的 Hash 值都能在这里找到,所以如果你发现有哪个commit突然找不到了,一定都可以在这里找到。

需求三,怎么解决冲突

记住,Git 虽然高大上,但也不要迷恋,一定要懂得借助先进的工具。

比较流行的代码编辑器或者 IDE 都会集成方便的可视化 Git 工具,至于解决冲突,可视化的表现方式不是比你在命令行里git diff看半天要清晰明了得多?只需要点点点就行了。

所以说,只要明白本文讲的这些基本操作,够你用的了,平时能用图形化工具就多用图形化工具,毕竟工具都是为人服务的。

别忘了看看 一起刷题学习 Git/SQL/正则表达式。

来源 | github

作者 | labuladong

展开
收起
游客ih62co2qqq5ww 2020-06-09 15:16:23 1993 0
1 条回答
写回答
取消 提交回答
  • 收藏

    2020-06-11 14:12:18
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
新一代高效Git协同模型 立即下载
AGit-Flow:新一代高效Git协同模型 立即下载
AGit-flow:新一代高效Git协同模型 立即下载