本节内容主要演示如何配置并初始化一个仓库(repository)、开始或停止跟踪(track)文件、暂存(stage)或提交(commit)更改。
本节也将向你演示如何配置 Git 来忽略指定的文件和文件模式、如何迅速而简单地撤销错误操作、如何浏览你的项目的历史版本以及不同提交(commits)间的差异、如何向你的远程仓库推送(push)以及如何从你的远程仓库拉取(pull)文件。
1 获取仓库
有两种取得 Git 项目仓库的方法。 第一种是在现有项目或目录下导入所有文件到 Git 中;第二种是从一个服务器克隆一个现有的 Git 仓库。
1.1 在现有目录中初始化仓库
在一个本地的文件夹里,使用以下命令会创建一个仓库:
1 | git init |
该命令将创建一个名为 .git
的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。
但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪(如果有文件的话)。这时我们还需要把未跟踪的文件加入进来:
1 | git add <filename> |
<filename>
可以是明确指定的一个文件,也可以使用通配符,下面两个都是合法的使用方式:
1 | # 把Demo.txt加入进来 |
不过现在我们只是把文件变成以追踪的状态,但是文件本身的改动还没有提交。使用 commit
命令可以提交变动,它有一个参数 -m
后面跟的是提交时说明信息:
1 | git commit -m "Inital Project Version" |
1.2 克隆现有的仓库
如果你想获得一份已经存在了的 Git 仓库的拷贝,比如说,你想为某个开源项目贡献自己的一份力,这时就要用到 git clone
命令。命令格式是 git clone [url]
:
1 | git clone https://github.com/libgit2/libgit2 |
这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git
文件夹,从远程仓库拉取下所有数据放入 .git
文件夹,然后从中读取最新版本的文件的拷贝。如果你想在克隆远程仓库的时候,自定义本地仓库的名字,你可以使用如下命令:
1 | git clone https://github.com/libgit2/libgit2 mylibgit |
这将执行与上一个命令相同的操作,不过在本地创建的仓库名字变为 mylibgit
。
2 跟踪文件
现在我们已经有了一个Git仓库,并且里面有很多的文件。我们的任务就是跟踪文件的变化,并提交到仓库中。
工作目录下的每一个文件都不外乎这两种状态:
已跟踪
是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。
初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态。
未跟踪
工作目录中除已跟踪文件以外的所有其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。
编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。 我们逐步将这些修改过的文件放入暂存区,然后提交所有暂存了的修改,如此反复。所以使用 Git 时文件的生命周期如下:
2.1 查看文件状态
要查看哪些文件处于什么状态,可以用 git status
命令。如果所有已跟踪文件在上次提交后都未被更改过,会看到类似这样的输出:
1 | $ git status |
On branch master
nothing to commit, working tree clean
如果我们添加一个新的文件,那么再次使用 status
命令时输出就不同了:
1 | $ ls -al > DirInfo |
这句话告诉我们出现了一个未跟踪的文件"DirInfo"。未跟踪的文件意味着 Git 在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”。
2.2 跟踪新的文件
和之前一样,我们使用命令 git add
开始跟踪一个文件(并把它放入暂存区中)运行:
1 | $ git add DirInfo |
在 Changes to be committed
这行下面的,就说明是已暂存状态。如果此时提交(commit),那么该文件此时此刻的版本将被留存在历史记录中。
git add
命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。
2.3 暂存已修改的文件
现在我们来修改一个已被跟踪的文件,加入我们当前的仓库里的文件结构如下:
1 | $ ls -al |
我们现在修改以下demo1.txt的内容,再看一下文件的状态:
1 | $ vim demo1.txt |
文件 demo1.txt
出现在 Changes not staged for commit
这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行 git add
命令。
这里我们发现add
是个多功能命令,它可以:
开始跟踪新文件
把已跟踪的文件放到暂存区
用于合并时把有冲突的文件标记为已解决状态
其他等等
将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。
1 | $ git add demo1.txt |
现在两个文件都已暂存,下次提交(commit)时就会一并记录到仓库中。
另外add
还有几个小窍门:
git add -A
提交所有变化git add -u
提交被修改(modified)和被删除(deleted)文件,不包括新文件(new)git add .
提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件
2.4 关于提交的一点小问题
现在我们总结一下像把一个文件提交到仓库里需要的步骤:
add
到暂存区里commit
到仓库里
发现了吗?是先add
再commit
,下面说的很重要,请记住:
Git 只会暂存了你运行 git add
命令时的版本,当运行commit
命令时提交的版本是你最后一次运行 git add
命令时的那个版本,而不是你运行 git commit
时,在工作目录中的当前版本。所以,运行了 git add
之后又作了修订的文件,需要重新运行 git add
把最新版本重新暂存起来。
举个栗子,我们现在再次修改一下demo1.txt的内容:
1 | $ truncate -s 0 demo1.txt |
怎么回事?现在 demo1.txt
文件同时出现在暂存区和非暂存区。这怎么可能呢?
好吧,确实有可能,因为这两个demo1.txt根本就不是一个!前面一个是之前add
以后一直存在的文件,而后面那个才是我们刚刚那修改过的在工作区的文件。如果你确实希望将修改过后的demo1.txt
提交,那就需要先add
再commit
:
1 | $ git add demo1.txt |
2.5 忽略文件
在使用Git的过程中,我们喜欢有的文件比如日志,临时文件,编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交。
在这种情况下,我们可以创建一个名为 .gitignore
的文件,列出要忽略的文件模式。
文件 .gitignore
的格式规范如下:
- 所有空行或者以
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式可以以(
/
)开头防止递归。 - 匹配模式可以以(
/
)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。
- 星号(
*
)匹配零个或多个任意字符 [abc]
匹配任何一个列在方括号中的字符。使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配,比如[0-9]
表示匹配所有 0 到 9 的数字- 问号(
?
)只匹配一个任意字符 - 使用两个星号(
*
) 表示匹配任意中间目录,比如a/**/z
可以匹配a/z
,a/b/z
或a/b/c/z
等。
2.6 查看已暂存和未暂存的修改
如果你想知道具体修改了什么地方,可以用 git diff
命令,你可能通常会用它来回答这两个问题:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交?
现在我们先做一下前期准备工作:
- 修改一下
DirInfo
的内容,但是不暂存 - 修改以下
demo1.txt
的内容并且暂存
1 | $ git status |
使用git diff
比较的是工作目录中当前文件和暂存区域快照之间的差异, 也就是修改之后还没有暂存起来的变化内容。
若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --cached
命令。(Git 1.6.1 及更高版本还允许使用 git diff --staged
,效果是相同的,但更好记些。)
2.7 提交更新
建议每次准备提交前,先用 git status
看下,是不是都已暂存起来了, 然后再运行提交命令 git commit
。
如果不带参数-m
1 | $ git commit |
这种方式会启动文本编辑器以便输入本次提交的说明。默认会启用 shell 的环境变量 $EDITOR
所指定的软件,一般都是 vim 或 emacs。可以使用 git config --global core.editor
命令设定你喜欢的编辑软件。
1 | $ git commit |
提交之后的信息会提醒你在那个分值(master)提交的,本次提交的完整 SHA-1 校验和是什么(01b34e7
),以及在本次提交中,有多少文件修订过,多少行添加和删改过。
请记住,提交时记录的是放在暂存区域的快照。任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。 每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。
当然,尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤。
2.8 删除文件
删除文件?直接使用rm
不就行了吗?不,事实上没有那么简单。用 rm 来删除文件,仅仅是删除了物理文件,没有将其从 git 的记录中剔除。
删除一个未暂存的文件
比如这里我们删除了demo1.txt
文件以后:
1 | $ git status |
我们发现提交失败。
所以要删除的话就要使用git rm
命令:
1 | $ git rm DirInfo |
删除一个已经暂存的文件
要注意的是之前的操作要求我们的文件未修改、不存在于暂存区中,否则无法成功。举个例子,比如我们现在对demo1.txt
进行一些修改,然后add
到暂存区中:
1 | $ echo "Hello world" > demo1.txt |
可以看到我们有两个选择:
git rm -f demo1.txt
强制删除文件
git rm --cached demo1.txt
这种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。当你忘记添加
.gitignore
文件,不小心把一个很大的日志文件或一堆.a
这样的编译生成文件添加到暂存区时,这一做法尤其有用。
git rm
命令后面可以列出文件或者目录的名字,也可以使用 glob
模式。 比方说:
1 | $ git rm log/\*.log |
2.9 移动文件
使用git mv file_from file_to
可以移动或者重命名文件。
其实,运行 git mv
就相当于运行了下面三条命令:
1 | $ mv file_from file_to |
3 查看提交的历史
可以使用git log
来查看提交的历史纪录。
git log
有许多选项可以帮助你搜寻你所要找的提交, 接下来我们介绍些最常用的。
一个常用的选项是 -p
,用来显示每次提交的内容差异。 你也可以加上 -2
来仅显示最近两次提交。