简介:相信大部分开发者对 Git 都不陌生,Git 也已成为大部分开发者平常开发必用的工具。本文分享 Git 使用上的一些基础知识,通俗易懂,很是有用。git
在 Git Rev News #48 期的 LightReading 中有一篇文章(地址:https://hacker-tools.github.io/version-control/) 写的不错,不只干货满满并且还附带了操做视频。其中的内容不只覆盖了不少 Git 使用上的基础知识,也从使用角度上解答了不少刚接触 Git 的开发者的疑问。为了便于读者理解,我在翻译的同时也添加了一些内容。如下为正文部分。本文内容较长,建议收藏慢慢学习。github
不少人怕使用 Git,我我的以为主要多是两部分的缘由:算法
Never Be Afraid To Try Something New.
代码对于开发者是劳做成果的结晶,对于公司而言是核心资产,有一些担心也是正常的。但 Git 也并无咱们想象中的那么复杂,须要让咱们每次使用都心有余悸,其实咱们只须要稍微花一点时间尝试多多了解它,在不少时候你会发现,非但 Git 不会让你产生担心,并且会让本身的交付过程更加高效。vim
谈及 Git 就不得不提到版本控制,咱们不妨先来看下版本控制是作什么的,这将有助于后续对 Git 的理解。安全
当你在工做中面对的是一些常常变化的文档、代码等交付物的时候,考虑如何去追踪和记录这些 changes 就变得很是重要,缘由多是:对于频繁改动和改进的交付物,很是有必要去记录下每次变动的内容,每次记录的内容汇成了一段修改的历史,有了历史咱们才知道咱们曾经作了什么。服务器
记录的历史中必需要包含一些重要的信息,这样追溯才变得有意义,好比:数据结构
最好能够支持撤销变动,不让某一个提交的严重问题,去污染整个提交历史。app
版本控制系统(VCS: Version Control System),正会为你提供这种记录和追溯变动的能力。分布式
大多数的 VCS 支持在多个使用者之间共享变动的提交历史,这从实质上让团队协同变为了可能,简单说来就是:工具
VCS 历经多年的发展,目前业界中有许多 VCS 工具可供咱们选择。在本文中,咱们将会针对目前最流行的 Git 来介绍。
刚接触 Git 时,Git 确实有让人以为有点像黑魔法同样神秘,可是又有哪一个技术不是这样呢?当咱们了解其基本的数据结构结构后,会发现 Git 从使用角度来说其实并不复杂,咱们甚至能够更进一步的学习 Git 的一些优良的软件设计理论,从中获益。首先,让咱们先从 commit 提及。
提交对象(git commit object):每个提交在 Git 中都经过 git commit object 存储,对象具备一个全局惟一的名称,叫作 revision hash。它的名字是由 SHA-1 算法生成,形如"998622294a6c520db718867354bf98348ae3c7e2
",咱们一般会取其缩写方便使用,如"9986222
"。
让咱们经过实战来帮助理解,第一步咱们来初始化一个 repository(Git 仓库),默认初始化以后仓库是空的,其中既没有保存任何文本内容也没有附带任何提交:
$ git init hackers $ cd hackers $ git status
第二步,让咱们来看下执行事后 Git 给出的输出内容,它会指引咱们进行进一步的了解:
➜ hackers git:(master) git status On branch master No commits yet nothing to commit (create/copy files anduse "git add" to track)
1)output 1: On branch master
对于刚刚建立空仓库来讲,master 是咱们的默认分支,一个 Git 仓库下能够有不少分支 (branches),具体某一个分支的命名能够彻底由你本身决定,一般会起便于理解的名字,若是用 hash 号的话确定不是一个好主意。
branches 是一种引用 (ref),他们指向了一个肯定的 commit hash 号,这样咱们就能够明确咱们的分支当前的内容。
除了 branches 引用之外,还有一种引用叫作 tags,相信你们也不会陌生。
master 一般被咱们更加熟知,由于大多数的分支开发模式都是用 master 来指向“最新”的 commit。
On branch master 表明着咱们当前是在 master 分支下操做,因此每次当咱们在提交新的 commit 时,Git 会自动将 master 指向咱们新的 commit,当工做在其余分支上时,同理。
有一个很特殊的 ref 名称叫作 "HEAD",它指向咱们当前正在操做的 branches 或 tags (正常工做时),其命名上很是容易理解,表示当前的引用状态。
经过git branch
(或git tag
) 命令你能够灵活的操做和修改 branches 或 tags。
2)output 2:No commits yet
对于空仓库来讲,目前咱们尚未进行任意的提交。
nothing to commit (create/copy files anduse "git add" to track)
output 中提示咱们须要使用git add
命令,说到这里就必需要提到暂存或索引 (stage),那么如何去理解暂存呢?
一个文件从改动到提交到 Git 仓库,须要经历三个状态:
git add 的帮助文档中很详细的解释了暂存这一过程:
This command updates the index using thecurrent content found in the working tree, to prepare the content stagedfor the next commit.
git add 命令将更新暂存区,为接下来的提交作准备。
It typically adds the current content ofexisting paths as a whole, but with some options it can also be used toadd content with only part of the changes made to the working tree filesapplied, or remove paths that do not exist in the working tree anymore.The "index" holds a snapshot ofthe content of the working tree, and it is this snapshot that is taken as thecontents of the next commit.
暂存区的 index 保存的是改动的完整文件和目录的快照 (非 delta)。
Thus after making any changes to theworking tree, and before running the commit command, you must use the addcommand to add any new or modified files to the index.
暂存是咱们将改动提交到 git 仓库以前必须经历的状态。
对 Git 暂存有必定了解后,其相关操做的使用其实也很是简单,简要的说明以下:
1)暂存区操做
git add
命令将改动暂存。git add -p
来依次暂存每个文件改动,过程当中咱们能够灵活选择文件。中的变动内容,从而决定哪些改动暂存。git add
不会暂存被 ignore 的文件改动。git rm
命令,咱们能够删除文件的同时将其从暂存区中剔除。2)暂存区修正
git reset
命令进行修正,能够先将暂存区的内容清空,在使用git add -p
命令对改动 review 和暂存。git reset FILE
或git reset -p
命令。3)暂存区状态
git diff --staged
依次检查暂存区内每个文件的修改。git diff
查看剩余的还未暂存内容的修改。当你对须要修改的内容和范围满意时,你就能够将暂存区的内容进行 commit 了,命令为:git commit
。
若是你以为须要把全部当前工做空间的修改所有 commit,能够执行git commit -a
,这至关于先执行git add
后执行git commit
,将暂存和提交的指令合二为一,这对于一些开发者来讲是很高效的,可是若是提交过大这样作一般不合适。
咱们建议一个提交中只作一件事,这在符合单一职责的同时,也可让咱们明确的知道每个 commit 中作了一件什么事情而不是多个事情。因此一般咱们的使用习惯都是执行git add -p
来 review 咱们将要暂存内容是否合理?是否须要更细的拆分提交?这些优秀的工程实践,将会让代码库中的 commits 更加优雅。
ok,咱们已经在不知不觉中了解了不少内容,咱们来回顾下,它们包括了:
附带的,在了解 commit 过程当中咱们知道了从本地改动到提交到 Git 仓库,经历的几个关键的状态:
下图为上述过程当中各个状态的转换过程:
git commit
以后,暂存区的内容对象将会存储在 Git 仓库中,并执行更新 HEAD 指向等后续操做,这样就完成了引用与提交、提交与改动快照的——对应了。正是由于 Git 自己对于这几个区域(状态)的设计,为 Git 在本地开发过程带来了灵活的管理空间。咱们能够根据本身的状况,自由的选择哪些改动暂存、哪些暂存的改动能够 commit、commit 能够关联到那个引用,从而进一步与其余人进行协同。
咱们已经有了一个 commit,如今咱们能够围绕 commit 作更多有趣的事情:
git log
(或git log --oneline
)。diff:git log -p
。git checkout NAME
(若是 NAME 是一个具体的提交哈希值时,Git 会认为状态是 “detached (分离的)”,由于git checkout
过程当中重要的一步是将 HEAD 指向那个分支的最后一次 commit。因此若是这样作,将意味着没有分支在引用此提交,因此若咱们这时候进行提交的话,没有人会知道它们的存在)。git revert NAME
来对 commit 进行反转操做。git diff NAME
将旧版本与当前版本进行比较,查看 diff。git log NAME
查看指定区间的提交。git reset NAME
进行提交重置操做。git reset --hard NAME
:将全部文件的状态强制重置为 NAME 的状态,使用上须要当心。引用 (refs) 包含两种分别是 branches 和 tags, 咱们接下来简单介绍下相关操做:
git branch b
命令可让咱们建立一个名称为 b 的分支。git checkout b
能够切换到b分支上,切换后新的提交都会在b分支上,理所应当。git checkout master
切换回 master 后,b 分支的提交也不会带回 master 上,分支隔离。分支上提交隔离的设计,可让咱们很是轻松的切换咱们的修改,很是方便的作各种测试。
tags 的名称不会改变,并且它们有本身的描述信息 (好比能够做为 release note 以及标记发布的版本号等)。
可能不少人的提交历史是长这个样子的:
commit 14: add feature x – maybe even witha commit message about x! commit 13: forgot to add file commit 12: fix bug commit 11: typo commit 10: typo2 commit 9: actually fix commit 8: actually actually fix commit 7: tests pass commit 6: fix example code commit 5: typo commit 4: x commit 3: x commit 2: x commit 1: x
单就 Git 而言,这看上去是没有问题并且合法的,但对于那些对你修改感兴趣的人(极可能是将来的你!),这样的提交在信息在追溯历史时可能并无多大帮助。可是若是你的提交已经长成这个样子,咱们该怎么办?
不要紧,Git 有办法能够弥补这一些:
咱们能够将新的改动提交到当前最近的提交上,好比你发现少改了什么,可是又不想多出一个提交时会颇有用。
若是咱们认为咱们的提交信息写的并很差,我要修改修改,这也是一种办法,可是并非最好的办法。
这个操做会更改先前的提交,并为其提供新的 hash 值。
这个命令很是强大,能够说是 Git 提交管理的神器,此命令含义是咱们能够针对以前的 13 次的提交在 VI 环境中进行从新修改设计:
操做选项 p 意味着保持原样什么都不作,咱们能够经过 vim 中编辑提交的顺序,使其在提交树上生效。
操做选项 r:咱们能够修改提交信息,这种方式比commit --amend
要好的多,由于不会新生成一个 commit。
操做选项 e:咱们能够修改 commit,好比新增或者删除某些文件改动。
操做选项 s:咱们能够将这个提交与其上一次的提交进行合并,并从新编辑提交信息。
操做选项 f:f表明着 "fixup"。例如咱们若是想针对以前一个老的提交进行 fixup,又不想作一次新的提交破坏提交树的历史的逻辑含义,能够采用这种方式,这种处理方式很是优雅。
版本控制的一个常见功能是容许多我的对一组文件进行更改,而不会互相影响。或者更确切地说,为了确保若是他们不会踩到彼此的脚趾,不会在提交代码到服务端时偷偷的覆盖彼此的变化。
在 Git 中咱们如何保证这一点呢?
Git 与 SVN 不一样,Git 不存在本地文件存在 lock 的状况,这是一种避免出现写做问题的方式,可是并不方便,而 Git 与 SVN 最大的不一样在于它是一个分布式 VCS,这意味着:
git push
推送本地内容到任意咱们有权限的 Git 远端仓库。SVN,图片出自 git-scm
Git,图片出自 git-scm
冲突的产生几乎是不可避免的,当冲突产生时你须要将一个分支中的更改与另外一个分支中的更改合并,对应 Git 的命令为git merge NAME
,通常过程以下:
Git 将会保证这个过程改动不会丢失,另一个命令你可能会比较熟悉,那就是git pull
命令,git pull
命令实际上包含了git merge
的过程,具体过程为:
git fetch REMOTE
git merge REMOTE/BRANCH
若是每次 merge 都如此顺利,那确定是很是完美的,但有时候你会发如今合并时产生了冲突文件,这时候也不用担忧,如何处理冲突的简要介绍以下:
<<<<<<<
,这是你须要开始处理冲突的地方,而后找到=======
,等号上面的内容是 HEAD 到共同祖先之间的改动,等号下面是 NAME 到共同祖先之间的改动。用 git mergetool 一般是比较好的选择,固然如今大多数 IDE 都集成了不错的冲突解决工具。git add.
来暂存这些改动吧。git commit
,若是你想放弃当前修改从新解决可使用git merge --abort
,很是方便。当你完成了以上这些艰巨的任务,最后git push
吧!
排除掉远端的 Git 服务存在问题之外,咱们 push 失败的大多数缘由都是由于咱们在工做的内容其余人也在工做的关系。
Git 是这样判断的:
1)会判断 REMOTE 的当前 commit 是否是你当前正在 pushing commit 的祖先。
2)若是是的话,表明你的提交是相对比较新的,push 是能够成功的 (fast-forwarding)。
3)不然 push 失败并提示你其余人已经在你 push 以前执行更新 (push is rejected)。
当发生“push is rejected”后咱们的几个处理方法以下:
git pull
合并远程的最新更改(git pull
至关于git fetch
+git merge
)。--force-with-lease
参数,这样只有远端的 ref 自上次从 fetch 后没有改变时才会强制进行更改,不然“reject the push”,这样的操做更安全,是一种很是推荐使用的方式。本文只是选取部分 Git 基本命令进行介绍,目的是抛砖引玉,让你们对 Git 有一个基本的认识。当咱们深刻挖掘 Git 时,你会发现它自己有着如此多优秀的设计理念,值得咱们学习和探究。
不要让 Git 成为你认知领域的黑魔法,而是让 Git 成为你掌握的魔法。