要提前说的是,仓库是进行版本控制的一个文件目录。我们要想对文件进行版本控制,就必须先创建一个仓库出来。
创建⼀个 Git 本地仓库对应的命令为 git init ,注意命令要在文件目录下执行,例如:
我们发现,当前目录下多了一个 .git 的隐藏文件,.git 目录是 Git 来跟踪管理仓库的,不要手动修改这个目录里面的文件,不然改乱了,就把 Git 仓库给破坏了。
其中包含 Git 仓库的诸多细节:
当安装 Git 后首先要做的事情是设置自己的用户名称和 e-mail 地址,这是非常重要的。配置命令为:
- git config [--global] user.name "Your Name"
- git config [--global] user.email "email@example.com"
-
- # 把 Your Name 改成你的昵称
- # 把 email@example.com 改成邮箱的格式,只要格式正确即可
注意:其中 --global 是一个可选项。如果使用了该选项,表示这台机器上所有的 Git 仓库都会使用这个配置。如果希望在不同仓库中使用不同的 name 或 e-mail,可以不要 --global 选项,但要注意的是,执行命令时必须要在仓库里。
git config -l
git config [ --global ] --unset user.namegit config [ --global ] --unset user.email
在电脑上你要写代码或文件的目录。
又名仓库 。工作区有一个隐藏目录 .git ,它不算工作区,而是 Git 的版本库。这个版本库里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以 “还原”。
下面这个图展示了工作区、暂存区和版本库之间的关系:
图中左侧为工作区,右侧为版本库。Git 的版本库里存了很多东西,其中最重要的就是暂存区。
在创建 Git 版本库时,Git 会为我们自动创建一个唯一的 master 分支,以及指向 master 的⼀个指针叫 HEAD。
当对工作区修改(或新增、删除)的文件执行 git add 命令时,暂存区目录树的文件索引会被更新。
当执行提交操作 git commit 时,master 分支会做相应的更新,可以简单理解为暂存区的目录树才会被真正写到版本库中。
由上述描述我们便能得知:通过新建或粘贴进目录的文件,并不能称之为向仓库中新增文件,而只是在工作区新增了文件。必须要通过使用 git add 和 git commit 命令才能将文件添加到仓库中进行管理。
在包含 .git 的目录下新建一个 ReadMe ⽂件,但在目前情况下,Git 还不能管理我们的 ReadMe 文件。
可以使用 git add 命令可以将文件添加到暂存区:
添加一个或多个文件到暂存区: git add [file1] [file2] ...
添加指定目录到暂存区,包括子目录: git add [dir]
添加当前目录下的所有文件改动到暂存区: git add .
再使用 git commit 命令将暂存区内容添加到本地仓库中:
提交暂存区全部内容到本地仓库中: git commit -m "message"
提交暂存区的指定文件到仓库区: git commit [file1] [file2] ... -m "message"
git commit 命令执行成功后会告诉我们,1 个文件被改动(就是我们新添加的 ReadMe 文件),插入了一行内容(ReadMe 有一行内容)。 还可以多次 add 不同的文件,而只 commit 一次便可以提交所有文件,是因为需要提交的⽂件是通通被 add 到暂存区中,然后一次性 commit 暂存区的所有修改。如:
注意:git commit 后面的 -m 选项,要跟上描述本次提交的 message,由用户自己完成,这部分内容绝对不能省略,并要好好描述,是用来记录你的提交细节,是给我们自己看的。
截至目前为止,我们已经更够将代码直接提交至本地仓库了。可以使用 git log 命令,来查看下历史提交记录:
该命令显示从最近到最远的提交日志,并且可以看到我们 commit 时的日志消息。如果担心输出的信息太多,看得眼花缭乱,可以试试加上 --pretty=oneline 参数:
需要说明的是,我们看到的一大串类似 c64e0871...9c6502 的是每次提交的 commit id(版本号),Git 的 commit id 不是 1,2,3…… 递增的数字,而是⼀个 SHA1 计算出来的一个非常大的数字,用十六进制表示。
先来看看我们的 .git 的目录结构:
index 就是我们的暂存区,add 后的内容都是添加到这里的
HEAD 就是我们的默认指向 master 分支的指针
上面就是我们默认的 master 分支。
保存的就是当前最新的 commit id 。
objects 为 Git 的对象库,里面包含了创建的各种版本库对象及内容。当执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,就位于 ".git/objects" 目录下,来看看这些对象有何用处:
查找 object 时要将 commit id 分成 2 部分,其前 2 位是文件夹名称,后 38 位是文件名称。找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过 sha (安全哈希算法)加密过的文件,好在我们可以使用 git cat-file 命令来查看版本库对象的内容:
这就是我们最近⼀次的提交。
其中,还有一行 tree 15a37e9ef171cca4a5d985fccd1fcf9414b2c7cf,我们使用同样的方法,看看结果:
再看 ReadMe 对应的 8d0e41234f24b6da002d962a26c2495ea16a425f:
这是我们对 ReadMe 做的修改,被 git 记录了下来。
总结一下,在本地的 git 仓库中,有几个文件或者目录很特殊
- index:暂存区,git add 后会更新该内容。
- HEAD:默认指向 master 分支的一个指针。
- refs/heads/master:文件里保存当前 master 分支的最新 commit id。
- objects:包含了创建的各种版本库对象及内容,可以简单理解为放了 git 维护的所有修改。
后面再在学习的过程中,最好能将常见的 git 操作与 .git 目录当中的结构内容变化对应起来,这样有利于理解 git 细节流程。
下面展示另一种添加文件的场景,能加深对工作区、暂存区、版本库的理解,示例如下:
- 新增 file4 文件
- 将 file4 添加到暂存区
- 新增 file5 文件
- 提交修改
git add 是将文件添加到暂存区,git commit 是将暂存区的内容添加到本地仓库中。由于我们并没有使用 git add file5 ,file5 就不在暂存区中维护,所以在 commit 的时候其实只是把已经在暂存区的 file4 提交了,而遗漏了工作区的 file5。
再次 add,commit 即可。
Git 比其他版本控制系统设计得优秀,因为 Git 跟踪并管理的是修改,而非文件。
比如你新增了一行,这就是一个修改,删除了一行,也是⼀个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
让我们将 ReadMe 文件进行一次修改:
此时,仓库中的 ReadMe 和我们工作区的 ReadMe 是不同的。
用命令:git status 来查看在你上次提交之后是否有对文件进行再次修改。
上面的结果告诉我们,ReadMe 被修改过了,但还没有完成添加与提交。目前,我们只知道文件被修改了,如果能知道具体哪些地方被修改了,就更好了。可是我们还会记得一周之前写了什么代码吗,或者没写。
命令:git diff [file] 用来显示暂存区和工作区文件的差异,显示的格式正是 Unix 通用的 diff 格式。也可以使用 git diff HEAD -- [file] 命令来查看版本库和工作区文件的区别。知道了对 ReadMe 做了什么修改后,再把它提交到本地仓库就放心多了。
git add 之后,就没有看到上面 no changes added to commit (use "git add" and/or "git commit -a") 的消息了,接下来继续 git commit 即可:
前面也讲到过,Git 能够管理文件的历史版本,这也是版本控制器重要的能力。如果有一天发现之前的工作出现了很大的问题,需要在某个特定的历史版本重新开始,那么这个时候就需要用到版本回退的功能了。
执行 git reset 命令用于回退版本,可以指定退回某⼀次提交的版本。要解释一下 “回退” 的本质是要将版本库中的内容进行回退,工作区或暂存区是否回退由命令参数决定:
git reset 命令语法格式为: git reset [--soft | --mixed | --hard] [HEAD]
--mixed 为默认选项,使用时可以不用带该参数。该参数将暂存区的内容退回为指定提交版本内容,工作区文件保持不变。
--soft 参数对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。
--hard 参数将暂存区与工作区都退回到指定版本。切记工作区有未提交的代码时不要用这个命令,因为工作区会回滚,你没有提交的代码就再也找不回了,所以使用该参数前一定要慎重。
HEAD 说明:
可直接写成 commit id,表示指定退回的版本。
HEAD 表示当前版本。
HEAD^ 表示上一个版本。
HEAD^^ 表示上上一个版本。
以此类推...
可以使用 ~ 数字表示:
HEAD~0 表示当前版本。
HEAD~1 表示上一个版本。
HEAD^2 表示上上一个版本。
以此类推...
为了便于表述,方便测试回退功能,先做一些准备工作:更新 3 个版本的 ReadMe,并分别进行 3 次提交,如下所示:
可以继续使用 git reset 命令,回退到原先的版本,但我们必须要拿到原先版本的 commit id 去指定回退的版本。
但我们看到了 git log 并不能打印出之前版本的 commit id ,运气好的话我们可以从终端上去找找之前的历史记录,运气不好的话 commit id 已经被我们搞丢了。不过 Git 还提供了⼀个 git reflog 命令能够补救一下,这个命令用来记录本地的每一次命令。
是原先最新的版本。Git 版本回退的时候,也可以使用部分 commit id 来代表目标版本。
可以发现,此时 ReadMe 文件的内容,已经回退到最开始提交的时候了。
貌似现在就不可能做到了,但值得说的是,Git 的版本回退速度非常快,因为 Git 在内部有个指向当前分支(此处是 master)的 HEAD 指针, refs/heads/master 文件里保存当前 master 分支的最新 commit id 。当我们在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储⼀个特定的 version,可以简单理解成如下示意图:
如果我们在我们的工作区写了很长时间代码,越写越写不下去,想恢复到上⼀个版本。
新增代码后:
可以直接删掉目前在工作区新增的代码,但如果我们写了 3 天,⼀直都没有提交,该怎么删掉呢?自己忘了新增过哪些,可以 git diff xxx ⼀下,看看差别再删。
那我们肯定又要花上 3 天时间来删代码了,并且很大的概率还会改出 bug。Git 其实还为我们提供了更好的方式,可以使用 git checkout -- [file] 命令让工作区的文件回到最近一次 add 或 commit 时的状态。
注意 : git checkout -- [file] 命令中的 -- 很重要,切记不要省略。⼀旦省略,该命令就变为其他意思了。
回忆⼀下上面讲的 git reset 回退命令,该命令如果使用 --mixed 参数,可以将暂存区的内容退回为指定的版本内容,但工作区文件保持不变,那我们就可以回退下暂存区的内容了。
用 git status 查看⼀下,发现现在暂存区是干净的,工作区有修改。
注意:--mixed 是默认参数,使用时可以省略。
可以 git reset --hard HEAD^ 回退到上⼀个版本。但是这么做有条件的,就是还没有把自己的本地版本库推送到远程。⼀旦推送到远程版本库,就撤回不了了。
在 Git 中,删除也是⼀个修改操作。
如果要删除 file5 文件,要怎么操作呢?如果是按照下面这种做法:
这样直接删除是没有用的,反而徒增烦恼, git status 命令会立刻告诉我们哪些文件被删除了:
此时,工作区和版本库就不⼀致了,要删文件,目前除了要删工作区的文件,还要清除版本库的文件。
⼀般走到这里,有两种可能:
- 确实要从版本库中删除该文件。
- 不小心删错了。
对第二种情况,很明显误删,需要使用 git 来进行恢复,很简单,我们刚学过(删除也是修改):
对于第一种情况,很明显是没有删完,我们只删除了工作区的文件。这时就需要使用 git rm 将文件从暂存区和工作区中删除,并且 commit :
现在,文件就从版本库中被删除了。