从本节开始,我们就开始讲解Git的核心概念以及用法了。
创建版本库=git init
什么是版本库
版本库又名仓库,英文名repository。你可以把它简单理解成一个目录,Git能够管理目录下的所有文件,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。示例
把文件添加到版本库=git add/ git commit
能添加什么文件?
- 所有的版本控制系统,只能跟踪文本文件的改动。比如TXT文件,网页,所有的程序代码等等,Git也不例外。我们不仅能够知道哪些文件发生了变化,同时也知道每个文件内部哪一行发生的什么变化。(如:增加了什么内容,删除了什么内容)
- 图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化。也就是说,知道文件大小发生了改变,但是却不知道到底哪里发生了变化。注意:Microsoft下的word是二进制编码所以无法使用版本控制系统。
示例
- cat > readme表示将键盘输入内容输出到文件readme中
- git add readme 表示将文件readme添加到暂存区中
- git commit -m “write a readme file” 表示一次性把暂存区的所有修改提交到分支。其中 -m 参数表示添加注释,自定义记录该次改动信息,便于以后查阅。技巧:commit可以一次提交多个文件。可以多次add文件,一次commit。
版本库状态及改动查询=git status/git diff
我们此时有一个文件readme。内容为:
Git is a version control system.
Git is free software.
修改文件为:
Git is a distributed version control system.
Git is free software.执行命令git status查看版本库状态:
上述命令的输出结果告诉我们:readme文件被修改了,但是该修改没有提交。但是这个时候有一个问题,我们知道readme文件被修改了,但是具体是哪些内容被修改了呢?
这个时候就需要命令git diff了。
从上图中我们可以看出,Git is a version control system.这一行被删除,Git is a distributed version control system.这行被添加。这告诉我们,修改命令实际上包含两个命令,先删除,后添加。将修改过的文件添加到暂存区,然后再查看status
我们发现将要被committed的文件包含readme,我们可以对比add前的status看看两者有什么区别。commit readme,然后再看看status。
版本跳转=git log/ git reflog/ git reset
复习一下刚才的内容,我们继续修改文件为:
Git is a distributed version control system.
Git is free software distributed under the GPL.
然后add,commit提交如果你玩过大型RPG游戏的话,一定对存档这个概念很熟悉。我们往往在得到一些“史诗”级武器之后都存一下档,以免得到的宝物消失了。在Git中,commit就相当于这个存档的功能,我们能够通过commit记录下来的信息来进行不同历史版本之间的“跳跃”。
这个时候问题来了,我们现在到底有几个历史版本了?
如果你记忆力好的话,当然知道我们现在有3个版本。但是当我们开发大型软件的时候,一天commit几十个版本,你还能记得哪个版本修改了什么吗?
这个时候就需要一个git命令:git log
git log 命令输出从最近到最远的commit信息,如果希望更简洁的查看,可以使用参数--pretty=oneline。
此时仅输出两个信息:commit id和提交时记录的注释(git commit -m “注释”)
我们可以看到git的commit id不是按照SVN那样按数字1,2,3,4依次增加的,而是通过SHA1计算出来的一个非常大的数字。目的就是避免提交冲突。有了commit id我们就可以进行版本回退了。
进行版本回退,首先要知道当前版本和以往版本的表示方法。
在Git中,用HEAD表示当前版本,也就是最新的提交3628164…882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。版本跳转的命令为git reset –hard commit_id,这里的commit_id可以用HEAD来替换。
我们现在回到了最初的版本,输入命令git log,看看版本历史有什么变化。
发现我们之后创建的版本历史都消失了。也就是说,当我们穿越回”古代”,之后,未来发生的事情我们都是看不到的。那么问题来了,我想要回到”现代社会”,这个时候该怎么办呢?
答案还是:git reset命令。
这个时候,还有一个问题没有解决!
此时git log 已经找不到第三次提交的committed_id了,没有committed_id就像没有目的地一样,我们怎么才能找到我们的”终点站”呢?
使用git reflog命令!git reflog记录了每一次的命令。在这里,我们可以找到所有出现过的历史版本的committed_id号。
我们找到了append GPL,也就是第三个版本的committed_id号1366f6c(这个committed_id并不是完整的id,后面省略了很多字符,但是足够用来区分其他版本的id了)。
**补充:**Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL,改为指向add distributed。
可以通过一个视频来更好的体会这一点:git reset工作区和暂存区
工作区(Working Directory)
你在电脑里能看到的目录,比如上文创建的learngit版本库(Repository)
- 工作区有一个隐藏目录.git,这个不属于工作区,而是Git的版本库。
- Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。如下图所示:
现在我们来演示一下功能:
- 修改readme,增加一行内容
- 增加文件LICENSE
- 执行命令git status查看当前版本库状态:
我们可以看到,新建的LICENSE文件状态为untrack,readme文件为modified。这是git通过对比工作区和当前分支得到的结果。 - 执行命令git add readme LICENSE,查看当前版本库状态
- 执行命令git commit,查看当前版本库状态
管理修改
Git跟踪并管理的是修改,而非文件。这是git与众不同的地方。
我们再来回顾一下整个过程。
第一次修改 -> git add -> 第二次修改 -> git commit -> git add ->git commit
也就是说第二次修改并没有被第一次git commit,所以说git add的是修改而不是文件。那正确的提交“姿势”是什么样的呢?
第一次修改 -> git add -> 第二次修改 -> git add -> git commit
因为我们在一开始就说过,git 可以多次add,一次commit还有一个问题:若是在commit之后又修改了文件,那该怎么操作来看看到底版本库中的文件和修改后的文件有什么不同呢?
答案是我们之前使用过的命令:git diff 。如果我们只想看一个文件的修改内容,可以执行命令:
git diff HEAD -- (filename)
撤销修改=git checkout /reset HEAD
示例:
撤销改动有三种情况:
修改了工作区(workingDirectory)的文件,但是没有添加(add)到暂存区(stage)中。上图演示的例子就是这种情况。
修改了工作区(workingDirectory)的文件,并且添加(add)到了暂存区(stage)中,但是没有提交(commit)到版本库(repository)中。
修改了工作区(workingDirectory)的文件,并且添加(add)到了暂存区(stage)中,同时提交(commit)到版本库(repository)中。
针对这三种情况,我们来分别进行撤销修改的展示。
紧接着上例,看看第一种情况,只修改,不add,怎么撤销修改:
发现执行完命令git checkout -- readme
后,readme文件回到了修改之前的样子。此时的status为clean.如果add到了stage,这个时候应该怎么撤销修改呢?
答案是unstage。unstage的效果就是清空暂存区,清空暂存区就相当于没有add,我们就回到第一种情况,然后在没有文件添加到stage的情况下,使用checkout命令撤销修改。
在上图中,我们使用命令
git reset HEAD readme
来实现unstage。
这里有两个问题要解决:为什么要先unstage之后再checkout呢?
因为checkout命令是先判断暂存区stage中是否有要恢复的文件,如果有,则直接从暂存区stage恢复,如果没有,则从版本库repository中恢复。
也就是说,我们add修改过的readme文件到stage之后,stage中的readme和workingDirectory中的readme文件内容相同。如果我们执行checkout命令,git会先在stage中去寻找,这个时候便达不到我们恢复文件的目的。所以,我们必须先要撤销我们的add操作,也就是使用git reset HEAD readme
回到第一种情况,再执行checkout 命令。这样我们才能从版本库中恢复我们的文件。reset命令我们之前介绍过,是用来做版本回退的
git reset HEAD readme
命令和它有什么关系吗?
我们知道,版本回退是在不同的版本库间跳转。使用命令git reset --hard HEAD/committed_id
。区别版本回退与unstage命令的区别就是参数 –hard。
如果不仅add到了stage,而且从stage中commit到了版本库,这个时候应该如何撤销修改?
还记得我们前面讲的版本回退吗?
只需一个命令git reset --hard HEAD^
我们便可以退回到提交前的状态。“什么事情都没有发生过”。除了命令git reflog
能发现这里有一次版本回退……
删除文件
在git中,删除也是一种修改。也就是说git add
和git rm
在逻辑上是对应的,一个是添加文件,一个是删除文件,本质上都是修改文件。
当文件被删除了,怎么样才能找回文件呢?
按照删除也是一种修改这个理念,当然是采用撤销修改的方式来找回文件。上一节已经详细讲述了如何撤销修改的知识。这里就不再赘述了。
到此有关本地仓库的知识都已讲述完毕。需要注意的是,虽然我们讲解了大部分常用的操作,但是还有很多git其他的特性没有涉及到。当我们需要使用到某些特性的时候,再查阅相关资料即可,毕竟命令这么多,每个细节都记到是几乎不可能的嘛。
最后,我们总结一下本节的内容。
- 初始化init
- 增 add/commit
- 删 rm
- 修改&撤销
- 版本库的修改&撤销
reset - 文件的修改 &撤销
checkout
- 版本库的修改&撤销
- 查
- 版本库状态查询
log/reflog - 文件状态查询
status/diff
- 版本库状态查询