前言
这里简单介绍一下Git的历史。
同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。到 2002 年,整个项目组开始启用分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统制订了若干目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许上千个并行开发的分支)
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统(见第三章),可以应付各种复杂的项目开发需求。
一、Git起步
1、直接记录快照,而非差异比较
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容,具体如图
Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。Git 的工作方式就像图所示
2、运行Git前的配置
这里如果还有小伙伴们没有安装好Git,请自行去安装一下先哦(https://git-scm.com/downloads)。
Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:
/etc/gitconfig
文件:系统中对所有用户都普遍适用的配置。若使用git config
时用--system
选项,读写的就是这个文件。~/.gitconfig
文件:用户目录下的配置文件只适用于该用户。若使用git config
时用--global
选项,读写的就是这个文件。- 当前项目的 git 目录中的配置文件(也就是工作目录中的
.git/config
文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以.git/config
里的配置会覆盖/etc/gitconfig
中的同名变量。
a. 用户信息配置
第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录
git config --global user.name "qiangdada"
git config --global user.email qiangdada@example.com
如果用了 --global
选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global
选项重新配置即可,新的设定保存在当前项目的 .git/config
文件里。
b. 差异分析工具
Git 可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff 等合并工具的输出信息,这里,如果说在解决合并冲突时使用的是vimdiff差异分析工具。改用命令如下
git config --global merge.tool vimdiff
c. 查看配置信息
git config --list
#user.name=qiangdada
#user.email=qiangdada@tencent.com
#color.status=auto
#color.branch=auto
#color.interactive=auto
#color.diff=auto
#...
这里会看到重复的变量名,那就说明它们来自不同的配置文件(比如
/etc/gitconfig
和 ~/.gitconfig
),不过最终 Git 实际采用的是最后一个。也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,像这样git config user.name
#qiangdada
d. 获取帮助
想了解 Git 的各式工具该怎么用,可以阅读它们的使用帮助,方法有三
# 方法1
git help
#方法二
git --help
#方法三
man git
比如,要学习 config 命令可以怎么用,运行
git help config
二、Git基础
1、取得项目的Git仓库
有两种取得 Git 项目仓库的方法。第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库。第二种是从已有的 Git 仓库克隆出一个新的镜像仓库来
a. 从工作目录中初始化新仓库
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行
git init
初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。如果当前目录下有几个文件想要纳入版本控制,需要先用
git add
命令告诉 Git 开始对这些文件进行跟踪,然后提交
# step 1
git add *.c
# step 2
git add README
# step 3
git commit -m 'initial project version'
b. 从现有仓库克隆
如果想对某个开源项目出一份力,可以先把该项目的 Git 仓库复制一份出来,这就需要用到 git clone
命令。如果你熟悉其他的 VCS 比如 Subversion,你可能已经注意到这里使用的是 clone
而不是 checkout
。这是个非常重要的差别,Git 收取的是项目历史的所有数据(每一个文件的每一个版本),服务器上有的数据克隆之后本地也都有了。实际上,即便服务器的磁盘发生故障,用任何一个克隆出来的客户端都可以重建服务器上的仓库,回到当初克隆时的状态
git clone [url]
# 如 git clone https://github.com/xuqiang521/data-visualization.git
2、记录每次更新到仓库
先上一张文件的状态变化周期的图示
a. 检查当前文件状态
git status
b. 跟踪新文件
使用命令
git add
开始跟踪一个新文件README。运行
# step 1
git add README
# step 2
git status
# 此时文件属于暂存状态
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
# new file: README
假设在这之前我就已经跟踪过一个文件叫
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
将 benchmarks.rb 放到暂存区,然后再看看
git status
的输出
# step 1
git add benchmarks.rb
# step 2
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
看看
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
把最新版本重新暂存起来
# step 1
git add benchmarks.rb
# step 2
git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
c. 忽略某些文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们可以创建一个名为 .gitignore
的文件,列出要忽略的文件模式。来看一个实际的例子
cat .gitignore
#告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的,我们用不着跟踪它们的版本
*.[oa]
#告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本
*~
#此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。
#要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件
文件 .gitignore
的格式规范如下:
- 所有空行或者以注释符号
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式最后跟反斜杠(
/
)说明要忽略的是目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9]
表示匹配所有 0 到 9 的数字)。
我们再看一个 .gitignore
文件的例子
# 此为注释 – 将被 Git 忽略
# 忽略所有 .a 结尾的文件
*.a
# 但 lib.a 除外
!lib.a
# 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目录下的所有文件
build/
# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt
d. 查看已暂存和未暂存的更新
实际上 git status
的显示比较简单,仅仅是列出了修改过的文件,假如再次修改 README
文件后暂存,然后编辑 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
如果这个时候我需要查看尚未暂存的文件更新了哪些部分,那么不妨不加参数直接输入
git diff
git diff
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。
若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --cached
命令。
git diff --cached
e. 提交更新
现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有 git add
过,否则提交的时候不会记录这些还没暂存起来的变化。所以,每次准备提交前,先用 git status
看下,是不是都已暂存起来了,然后再运行提交命令 git commit
git commit
这种方式会启动文本编辑器以便输入本次提交的说明,编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示)
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
~
~
~
".git/COMMIT_EDITMSG" 10L, 283C
另外也可以用 -m 参数后跟提交说明的方式,在一行命令中提交更新
git commit -m "up"
如果我们需要跳过使用暂存区域,不用担心。Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤
# step 1
git status
# On branch master
#
# Changes not staged for commit:
#
# modified: benchmarks.rb
#
# step 2
git commit -a -m 'up'
#[master 83e38c7] added new benchmarks
#1 files changed, 5 insertions(+), 0 deletions(-)
f. 移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
如果只是简单地从工作目录中手工删除文件,运行 git status
时就会在 “Changes not staged for commit” 部分(也就是未暂存清单)看到
# step 1
rm benchmarks.rb
# step 2
git status
# On branch master
#
# Changes not staged for commit:
# (use "git add/rm <file>..." to update what will be committed)
#
# deleted: benchmarks.rb
然后再运行
git rm
记录此次移除文件的操作:
# step 1
git rm benchmarks.rb
# step 2
git status
# On branch master
#
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: benchmarks.rb
这样最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
(force 的首字母),以防误删除文件后丢失修改的内容。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a
编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore
文件中补上,用 --cached
选项即可
git rm --cached readme.txt
3、查看提交历史
在提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,可以使用 git log
命令查看。
git log
默认不用任何参数的话,git log
会按提交时间列出所有的更新,最近的更新排在最上面。
git log
命令支持的选项具体如下所示
#选项 说明
-p 按补丁格式显示每个更新之间的差异。
--stat 显示每次更新的文件修改统计信息。
--shortstat 只显示 --stat 中最后的行数修改添加移除统计。
--name-only 仅在提交信息后显示已修改的文件清单。
--name-status 显示新增、修改、删除的文件清单。
--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
--graph 显示 ASCII 图形表示的分支合并历史。
--pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
-(n) 仅显示最近的 n 条提交
--since, --after 仅显示指定时间之后的提交。
--until, --before 仅显示指定时间之前的提交。
--author 仅显示指定作者相关的提交。
--committer 仅显示指定提交者相关的提交。
4、撤销操作
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend
选项重新提交:
git commit --amend
如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行
--amend
提交:
# step 1
git commit -m 'initial commit'
# step 2
git add forgotten_file
# step 3
git commit --amend
上面的三条命令最终只是产生一个提交,第二个提交命令修正了第一个的提交内容。
这篇博客先写到这里,后期我会再写上一篇作为后续补上,后续中详细介绍Git分支以及远程仓库的操作。希望可以帮助到大家,也希望大家可以支持一下我,大家的支持将是我写作最大的动力(*^__^*) !
原文发布时间为:2016年12月05日
原文作者:qiangdada
本文来源:开源中国 如需转载请联系原作者