记录一些Git的工作原理和最基本的使用
文件的三种状态
在 Git 内部中,任何一个文件可分为三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。
因此,Git 管理项目时,文件流转在三个工作区域:Git 的工作目录,暂存区域,本地仓库。
每个项目都有一个 Git 目录,如果 git clone
出来的话,就是其中 .git
的目录。它是 Git 用来保存元数据和对象数据库的地方。每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。
从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。这些文件实际上都是从 Git 目录中的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。
所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。
基本的 Git 工作流程如下:
- 在工作目录中修改某些文件。
- 对修改后的文件进行快照,然后保存到暂存区域。
- 提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。
取得项目的 Git 仓库
有两种取得 Git 项目仓库的方法。第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库。第二种是从已有的 Git 仓库克隆出一个新的镜像仓库来。
在工作目录中初始化新仓库
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行:
$ git init
初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。
不过此时,我们只是在该目录下初始化好了Git的相关结构,但是此时Git还没有开始跟踪管理项目中的任何一个文件。
如果当前目录下有几个文件想要纳入版本控制,需要先用 git add
命令告诉 Git 开始对这些文件进行跟踪,然后提交:
$ git add *.c //将所有的.c后缀文件加入跟踪
$ git add README
$ git commit -m 'initial project version'
从现有仓库克隆
如果想克隆别人已有的仓库,可以使用git clone
命令。
克隆仓库的命令格式为 git clone [url]
例如:$ git clone git://github.com/schacon/grit.git
这会在当前目录下创建一个名为grit
的目录,其中包含一个 .git
的目录,用于保存下载下来的所有版本记录,然后从中取出最新版本的文件拷贝。
如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:
$ git clone git://github.com/schacon/grit.git mygrit
Git 支持许多数据传输协议。
git协议:git://
,http协议:http(s)://
,ssh协议:user@server:/path.git
记录每次更新到仓库
工作目录下面的所有文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是未更新,已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。初次克隆某个仓库时,工作目录中的所有文件都属于已跟踪文件,且状态为未修改。
检查当前文件状态
可以用 git status
命令确定哪些文件当前处于什么状态。
如果在克隆仓库之后立即执行此命令,会看到类似这样的输出:
$ git status
# On branch master
nothing to commit (working directory clean)
这说明所有已跟踪文件在上次提交后都未被更改过。
创建一个新文件 README
,保存退出后运行git status
会看到该文件出现在未跟踪文件列表中:
$ vim README
$ git status
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# README
nothing added to commit but untracked files present (use "git add" to track)
在状态报告中可以看到新建的README文件出现在“Untracked files”
下面,这说明此时README
并未被Git跟踪。
跟踪新文件
使用命令 git add
开始跟踪一个新文件。
$ git add README
此时再运行 git status
命令,会看到 README 文件已被跟踪,并处于暂存状态:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
在 git add
后面可以指明要跟踪的文件或目录路径。如果是目录的话,就说明要递归跟踪该目录下的所有文件。
暂存已修改文件
现在我们修改下之前已跟踪过的文件benchmarks.rb
,然后再次运行 status
命令:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: benchmarks.rb
#
文件 benchmarks.rb
出现在 “Changes not staged for commit” 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行 git add
命令。
git add
是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。
git status
的输出:
$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
现在两个文件都已暂存,下次提交时就会一并记录到仓库。假设此时,在 benchmarks.rb
里再加条注释,重新编辑存盘后,准备好提交。此时运行git status
,会看到这样的情况:
$ vim benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: benchmarks.rb
#
benchmarks.rb
文件出现了两次,一次算未暂存,一次算已暂存。Git 只不过暂存了你运行 git add
命令时的版本,如果现在提交,那么提交的是添加注释前的版本,而非当前工作目录中的版本。所以,运行了 git add
之后又作了修订的文件,需要重新运行 git add
把最新版本重新暂存起来。
忽略某些文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们可以创建一个名为 .gitignore
的文件,列出要忽略的文件模式。
例如:
$ cat .gitignore
*.[oa]
*~
第一行告诉 Git 忽略所有以 .o
或 .a
结尾的文件。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件。
查看已暂存和未暂存的更新
如果要查看具体修改了什么地方,可以用 git diff
命令。
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff:
$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..da65585 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
@commit.parents[0].parents[0].parents[0]
end
+ run_code(x, 'commits 1') do
+ git.commits.size
+ end
+
run_code(x, 'commits 2') do
log = git.commits('master', 15)
log.size
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。
若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --staged
命令。
$ git diff --cached
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README2
@@ -0,0 +1,5 @@
+grit
+ by Tom Preston-Werner, Chris Wanstrath
+ http://github.com/mojombo/grit
+
+Grit is a Ruby library for extracting information from a Git repository
提交更新
使用git commit
命令,将暂存区域的文件提交到git仓库中。
这种方式会启动文本编辑器以便输入本次提交的说明。
通常我们在后面加上 -m参数,参数后直接跟这次提交的注解,可以直接在一行命令中提交。
例如:
$ git commit -m "Story 182: Fix benchmarks for speed"
[master]: created 463dc4f: "Fix benchmarks for speed"
2 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 README
跳过使用暂存区域
每次提交前,我们都要先使用git add
将修改后的文件,放入暂存区,才可以使用git commit
命令提交。
如果想省事,我们可以直接跳过使用暂存区域,直接提交更新后的文件。
Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤。
移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。用 --cached
选项即可:
$ git rm --cached readme.txt
重命名文件
要在 Git 中对文件改名,可以使用 git mv
命令。
例如:$ git mv file_from file_to
其实,运行 git mv
就相当于运行了下面三条命令:
$ mv README.txt README
$ git rm README.txt
$ git add README
查看提交历史
可以使用 git log
命令查看提交历史。
默认不用任何参数的话,git log
会按提交时间列出所有的更新,最近的更新排在最上面。每次更新都有一个 SHA-1 校验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示提交说明。
我们常用 -p
选项展开显示每次提交的内容差异,用 -2
则仅显示最近的两次更新:
$ git log -p -2
撤消操作
修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend
选项重新提交:
$ git commit --amend
此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,但将要提交的文件快照和之前的一样。
启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。
如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend
提交:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
上面的三条命令最终只是产生一个提交,第二个提交命令修正了第一个的提交内容。
取消已经暂存的文件
下面的例子,有两个修改过的文件,我们想要分开提交,但不小心用 git add .
全加到了暂存区域。该如何撤消暂存其中的一个文件呢?其实,git status
的命令输出已经告诉了我们该怎么做:
$ git add .
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: README.txt
# modified: benchmarks.rb
#
就在 “Changes to be committed” 下面,括号中有提示,可以使用 git reset HEAD <file>...
的方式取消暂存。好吧,我们来试试取消暂存 benchmarks.rb
文件:
$ git reset HEAD benchmarks.rb
benchmarks.rb: locally modified
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: README.txt
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: benchmarks.rb
#
取消对文件的修改
如果觉得刚才对 benchmarks.rb
的修改完全没有必要,该如何取消修改,回到之前的状态(也就是修改之前的版本)呢?git status
同样提示了具体的撤消方法:
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: benchmarks.rb
#
即,使用命令:
git checkout -- <file>...
远程仓库的使用
远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写。同他人协作开发某个项目时,需要管理这些远程仓库,以便推送或拉取数据,分享各自的工作进展。
查看当前的远程库
要查看当前配置有哪些远程仓库,可以用 git remote
命令,它会列出每个远程库的简短名字。在克隆完某个项目后,至少可以看到一个名为 origin
的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库:
$ git clone git://github.com/schacon/ticgit.git
Initialized empty Git repository in /private/tmp/ticgit/.git/
remote: Counting objects: 595, done.
remote: Compressing objects: 100% (269/269), done.
remote: Total 595 (delta 255), reused 589 (delta 253)
Receiving objects: 100% (595/595), 73.31 KiB | 1 KiB/s, done.
Resolving deltas: 100% (255/255), done.
$ cd ticgit
$ git remote
origin
也可以加上 -v
选项,显示对应的克隆地址:
$ git remote -v
origin git://github.com/schacon/ticgit.git
添加远程仓库
要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,运行 git remote add [shortname] [url]
:
$ git remote add pb git://github.com/paulboone/ticgit.git
$ git remote -v
pb git://github.com/paulboone/ticgit.git
从远程仓库抓取数据
如果设置了某个分支用于跟踪某个远端仓库的分支,可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。
推送数据到远程仓库
项目进行到一个阶段,要同别人分享目前的成果,可以将本地仓库中的数据推送到远程仓库:
git push [remote-name] [branch-name]
如果要把本地的 master
分支推送到 origin
服务器上(再次说明下,克隆操作会自动使用默认的 master
和 origin
名字),可以运行下面的命令:
$ git push origin master
只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,已经有其他人推送了若干更新,那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,合并到自己的项目中,然后才可以再次推送。
查看远程仓库信息
我们可以通过命令 git remote show [remote-name]
查看某个远程仓库的详细信息,比如要看所克隆的 origin
仓库,可以运行:
$ git remote show origin
* remote origin
URL: git://github.com/schacon/ticgit.git
Remote branch merged with 'git pull' while on branch master
master
Tracked remote branches
master
ticgit
远程仓库的删除和重命名
可以用 git remote rename
命令修改某个远程仓库在本地的简称,比如想把 pb 改成 paul:
$ git remote rename pb paul
$ git remote
origin
paul
碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,可以运行 git remote rm
命令:
$ git remote rm paul
$ git remote
origin