30分钟让你掌握Git的黑魔法

本文转载自云效公众号

在Git Rev News #48期的LightReading 中有一篇文章写的不错,不仅干货满满而且还附带了操作视频。其中的内容不仅覆盖了很多git使用上的基础知识,也从使用角度上解答了很多刚接触git的开发者的疑问。为了便于读者理解,我在翻译的同时也添加了一些内容。以下为正文部分。

**注:本文内容较长,建议收藏慢慢学习。

担忧

很多人怕使用git,我个人觉得主要可能是两部分的原因:

  • 没接触过:平时接触的代码还托管在SVN或CVS等工具上。
  • 不太熟悉:可能对git的使用还不太熟悉和全面,导致了在使用git时步步为营。

Never Be Afraid To Try Something New.

代码对于开发者是劳作成果的结晶,对于公司而言是核心资产,有一些担忧也是正常的。但git也并没有我们想象中的那么复杂,需要让我们每次使用都心有余悸,其实我们只需要稍微花一点时间尝试多多了解它,在很多时候你会发现,非但git不会让你产生担忧,而且会让自己的交付过程更加高效。

Version Control

谈及git就不得不提到版本控制,我们不妨先来看下版本控制是做什么的,这将有助于后续对git的理解。

当你在工作中面对的是一些经常变化的文档、代码等交付物的时候,考虑如何去追踪和记录这些changes就变得非常重要,原因可能是:

  • 对于频繁改动和改进的交付物,非常有必要去记录下每次变更的内容,每次记录的内容汇成了一段修改的历史,有了历史我们才知道我们曾经做了什么
  • 记录的历史中必须要包含一些重要的信息,这样追溯才变得有意义,比如:Who: 是谁执行的变更?When:什么时候做出的变更?What:这次变更做了什么事情?
  • 最好可以支持撤销变更,不让某一个提交的严重问题,去污染整个提交历史。

版本控制系统(VCS: Version Control System),正会为你提供这种记录和追溯变更的能力。

大多数的VCS支持在多个使用者之间共享变更的提交历史,这从实质上让团队协同变为了可能,简单说来就是:

  •  你可以看到我的变更提交。
  • 我也可以看到你的变更提交。
  • 如果双方都进行了变更提交,也可以以某种方式方法进行比对和合并,最终作出统一的变更版本。

VCS历经多年的发展,目前业界中有许多VCS工具可供我们选择。在本文中,我们将会针对目前最流行的 git 来介绍。

git是黑魔法么?

刚接触git时,git确实有让人觉得有点像黑魔法一样神秘,但是又有哪个技术不是这样呢?当我们了解其基本的数据结构结构后,会发现git从使用角度来讲其实并不复杂,我们甚至可以更进一步的学习git的一些优良的软件设计理论,从中获益。首先,让我们先从commit说起。

git object commit

  • 提交对象(git commit object): 每一个提交在git中都通过git commit object存储,对象具有一个全局唯一的名称,叫做 revision hash。它的名字是由 SHA-1 算法生成,形如”998622294a6c520db718867354bf98348ae3c7e2″,我们通常会取其缩写方便使用,如”9986222″。
  • 对象构成: commit对象包含了author + commit message 的基本信息。
  • 对象存储:git commit object保存一次变更提交内的所有变更内容,而不是增量变化的数据delta(很多人都理解错了这一点),所以git对于每次改动存储的都是全部状态的数据。
  • 大对象存储:因对于大文件的修改和存储,同样也是存储全部状态的数据,所以可能会影响git使用时的性能(glfs可以改进这一点)。
  • 提交树: 多个commit对象会组成一个提交树,它让我们可以轻松的追溯commit的历史,也能对比树上commit与commit之间的变更差异。

git commit 练习

让我们通过实战来帮助理解,第一步我们来初始化一个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)

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(或 gittag) 命令你可以灵活的操作和修改branches或tags。

output 2: No commits yet

  • 对于空仓库来说,目前我们还没有进行任意的提交

nothing to commit (create/copy files anduse “git add” to track)

output中提示我们需要使用 gitadd 命令,说到这里就必须要提到暂存或索引(stage),那么如何去理解暂存呢?

一个文件从改动到提交到git仓库,需要经历三个状态:

  • 工作区:工作区指的是我们本地工作的目录,比如我们可以在刚才创建的hackers目录下新增一个readme文件,readme文件这时只是本地文件系统上的修改,还未存储到git。
  • 暂存(索引)区: 暂存实际上是将我们本地文件系统的改动转化为git的对象存储的过程
  • 仓库:git commit后将提交对象存储到git仓库

git add 的帮助文档中很详细的解释了暂存这一过程:

DESCRIPTION

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会认为目前没有改动需要被提交
  •  如果我们想分阶段(or 分文件)进行reset,可以使用 git reset FILE or git reset -p 命令

3、暂存区状态

  • 可以用 git diff –staged 依次检查暂存区内每一个文件的修改
  • 用 git diff 查看剩余的还未暂存内容的修改

4、Just Commit!

  • 当你对需要修改的内容和范围满意时,你就可以将暂存区的内容进行commit了,命令为: git commit
  • 如果你觉得需要把所有当前工作空间的修改全部commit,可以执行 git commit -a ,这相当于先执行 git add后执行 git commit,将暂存和提交的指令合二为一,这对于一些开发者来说是很高效的,但是如果提交过大这样做通常不合适。
  • 我们建议一个提交中只做一件事,这在符合单一职责的同时,也可以让我们明确的知道每一个commit中做了一件什么事情而不是多个事情。所以通常我们的使用习惯都是执行 git add -p 来review我们将要暂存内容是否合理?是否需要更细的拆分提交?这些优秀的工程实践,将会让代码库中的commits更加优雅☕️

ok,我们已经在不知不觉中了解了很多内容,我们来回顾下,它们包括了:

  • commit包含的信息?
  • commit是如何表示的?
  • 暂存区是什么?如何全部添加、一次添加、删除、查询和修正?
  • 如何将暂存区的改动内容commit?
  • 不要做大提交,一个提交只做一件事

附带的,在了解commit过程中我们知道了从本地改动到提交到git仓库,经历的几个关键的状态:

  • 工作区(Working Directory)
  • 暂存区(Index)
  • Git仓库(Git Repo)

下图为上述过程中各个状态的转换过程:

  • 本地改动文件时,此时还仅仅是工作区内的改动
  • 当执行 git add 之后,工作区内的改动被索引在暂存区
  • 当执行 git commit 之后,暂存区的内容对象将会存储在git仓库中,并执行更新HEAD指向等后续操作,这样就完成了引用与提交、提交与改动快照的一一对应了。

正是因为git本身对于这几个区域(状态)的设计,为git在本地开发过程带来了灵活的管理空间。我们可以根据自己的情况,自由的选择哪些改动暂存、哪些暂存的改动可以commit、commit可以关联到那个引用,从而进一步与其他人进行协同。

提交之后

我们已经有了一个commit,现在我们可以围绕commit做更多有趣的事情:

  • 查看commit历史: git log(or git log –oneline)
  • 在commit中查看改动的diff:git log -p
  • 查看ref与提交的关联关系,如当前master指向的commit: git show master
  • 检出覆盖: git checkout NAME(如果NAME是一个具体的提交哈希值时,git会认为状态时“detached(分离的)”,因为gitcheckout过程中重要的一步是将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 的分支
  • 当我们创建了一个 b 分支后,这也相当于意味着 b 的指向就是 HEAD 对应的commit
  • 我们可以先在 b 分支上创建一个新的commitA ,然后假如切回 master 分支上,这时再提交了一个新的commitB,那么 master 和 HEAD 将会指向了新的commit __B,而b分支指向的还是原来的commit A
  • 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有办法可以弥补这一些:

git commit –amend 

我们可以将新的改动提交到当前最近的提交上,比如你发现少改了什么,但是又不想多出一个提交时会很有用。

如果我们认为我们的提交信息写的并不好,我要修改修改,这也是一种办法,但是并不是最好的办法。

这个操作会更改先前的提交,并为其提供新的hash值。

git rebase -i HEAD~13 

这个命令非常强大,可以说是git提交管理的神器,此命令含义是我们可以针对之前的13次的提交在VI环境中进行重新修改设计:

  • 操作选项 p 意味着保持原样什么都不做,我们可以通过vim中编辑提交的顺序,使其在提交树上生效
  • 操作选项 r: 我们可以修改提交信息,这种方式比commit –amend要好的多,因为不会新生成一个commit
  • 操作选项 e: 我们可以修改commit,比如新增或者删除某些文件改动
  • 操作选项 s: 我们可以将这个提交与其上一次的提交进行合并,并重新编辑提交信息
  • 操作选项 f: f代表着”fixup”。例如我们如果想针对之前一个老的提交进行fixup,又不想做一次新的提交破坏提交树的历史的逻辑含义,可以采用这种方式,这种处理方式非常优雅

关于git

版本控制的一个常见功能是允许多个人对一组文件进行更改,而不会互相影响。或者更确切地说,为了确保如果他们不会踩到彼此的脚趾,不会在提交代码到服务端时偷偷的覆盖彼此的变化。

在git中我们如何保证这一点呢?

git与svn不同,git不存在本地文件存在lock的情况,这是一种避免出现写作问题的方式,但是并不方便,而git与svn最大的不同在于它是一个分布式VCS,这意味着:

  • 每个人都有整个存储库的本地副本(其中不仅包含了自己的,也包含了其他人的提交到仓库的所有内容)。
  • 一些VCS是集中式的(例如,svn):服务器具有所有提交,而客户端只有他们“已检出”的文件。所以基本上在本地我们只有当前文件,每次涉及本地不存在的文件操作时,都需要访问服务端进行进一步交互。
  • 每一个本地副本都可以当作服务端对外提供git服务
  • 我们可以用git push推送本地内容到任意我们有权限的git远端仓库
  • 不管是集团的force、github、gitlab等工具,其实本质上都是提供的git仓库存储的相关服务,在这一点上其实并没有特别之处,针对git本身和其协议上是透明的。

svn,图片出自git-scm

git,图片出自git-scm

 git冲突解决

冲突的产生几乎是不可避免的,当冲突产生时你需要将一个分支中的更改与另一个分支中的更改合并,对应git的命令为 git merge NAME ,一般过程如下:

  • 找到HEAD和NAME的一个共同祖先(common base)
  • 尝试将这些NAME到共同祖先之间的修改合并到HEAD上
  • 新创建一个merge commit对象,包含所有的这些变更内容
  • HEAD指向这个新的mergecommit

git将会保证这个过程改动不会丢失,另外一个命令你可能会比较熟悉,那就是 git pull 命令,git pull 命令实际上包含了 git merge 的过程,具体过程为:

  • git fetch REMOTE
  • git merge REMOTE/BRANCH
  • 和 git push一样,有的时候需要先设置 “tracking”(-u) ,这样可以将本地和远程的分支一一对应。

如果每次merge都如此顺利,那肯定是非常完美的,但有时候你会发现在合并时产生了冲突文件,这时候也不用担心,如何处理冲突的简要介绍如下:

  • 冲突只是因为git不清楚你最终要合并后的文本是什么样子,这是很正常的情况
  • 产生冲突时,git会中断合并操作,并指导你解决好所有的冲突文件
  • 打开你的冲突文件,找到 <<<<<<< ,这是你需要开始处理冲突的地方,然后找到=======,等号上面的内容是HEAD到共同祖先之间的改动,等号下面是NAME到共同祖先之间的改动。用 git mergetool 通常是比较好的选择,当然现在大多数IDE都集成了不错的冲突解决工具
  • 当你把冲突全部解决完毕,请用 git add . 来暂存这些改动吧
  • 最后进行git commit,如果你想放弃当前修改重新解决可以使用 git merge –abort ,非常方便
  • 当你完成了以上这些艰巨的任务,最后 git push 吧!

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 强制推送本地变化到远端饮用进行覆盖,需要注意的是 这种覆盖操作可能会丢失其他人的提交内容
  • 可以使用 –force-with-lease 参数,这样只有远端的ref自上次从fetch后没有改变时才会强制进行更改,否则reject the push,这样的操作更安全,是一种非常推荐使用的方式。
  • 如果rebase操作了本地的一些提交,而这些提交之前已经push过了的话,你可能需要进行force push了,可以想象看为什么?

本文只是选取部分Git基本命令进行介绍,目的是抛砖引玉,让大家对git有一个基本的认识。当我们深入挖掘Git时,你会发现它本身有着如此多优秀的设计理念,值得我们学习和探究。

不要让Git成为你认知领域的黑魔法,而是让Git成为你掌握的魔法。

原文:https://hacker-tools.github.io/version-control/

译者:滕龙(花名澳明),阿里巴巴研发效能部技术专家

Image placeholder
EricShen
未设置
  44人点赞

没有讨论,发表一下自己的看法吧

推荐文章
一文带你掌握常见的Pandas性能优化方法,让你的pandas飞起来!

微信公众号:「Python读财」如有问题或建议,请公众号留言Pandas是Python中用于数据处理与分析的屠龙刀,想必大家也都不陌生,但Pandas在使用上有一些技巧和需要注意的地方,尤其是对于较大

Shell脚本编程30分钟入门

什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd~ mkdirshell_tut cdshell_tut for((i=0;ibash 但在MacOS上不是,/bin/sh和/

iOS开发60分钟入门

===============本文面向已有其它语言(如Java,C,PHP,Javascript)编程经验的iOS开发初学者,初衷在于让我的同事一小时内了解如何开始开发iOSApp,学习目标包括: 能

10分钟搞懂:亿级用户的分布式数据存储解决方案!

来源:IT进阶思维原创,转载请注明原出处内容提供:李智慧,前阿里巴巴技术专家,《大型网站技术架构》作者6月6日晚,林志玲与Akira公布婚讯、徐蔡坤祝福高考同学超常发挥,粉丝们百万的转发和点赞造成微博

10分钟,用TensorFlow.js库,训练一个没有感情的“剪刀石头布”识别器

大数据文摘出品编译:Luciana、小七、宁静“剪刀石头布”是我们小时候经常玩的游戏,日常生活中做一些纠结的决策,有时候也常常使用这种规则得出最后的选择,我们人眼能很轻松地认知这些手势,“石头”呈握拳

python __slots__ 魔法

在Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性。这非常有用,因为它允许我们在运行时去设置任意的新属性。 然而,对于有着已知属性的小类来说,它可能是个瓶颈

谈谈魔法消失UI框架 Svelte

最近基于公司业务需求,可能会要开发一款浏览器插件,调查后发现插件UI开发本质上就是开发页面。于是我便开始寻找一个非常小又非常快的新玩具(工具)。毕竟前端3大框架无论哪一个去开发浏览器插件都无异于大炮打

互联网公司忽悠员工的黑话

作者推特:@siyecao66据说这些是互联网公司招工时忽悠的黑话,大家来看看是不是真的?再列举几个黑话:老板:市场很大=我还不知道怎么赚钱有一定的用户基础= 建立了QQ群和微信群自主研发的系统=XX

15 个 Docker 初学者必须掌握的命令

这篇文章我基本上不会做put操作。如果你认为这些命令缺少了什么其他方面重要的东西,那么你需要自行检查Docker文档(https://docs.docker.com/) pull pull命令和gi

2020年前必须掌握的数据库面试问题~

一、为什么用自增列作为主键1、如果我们定义了主键(PRIMARYKEY),那么InnoDB会选择主键作为聚集索引。如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索

盗版12306骗3000万人下载,暴利高仿App是如何花式捞钱的?

眼看着春运一天一天临近,我按捺不住激动的心情,准备加入抢票大军。可是,当我在应用商城搜索12306时,却发现一大批“12306”。这些App下载量从几万到几千万(未标“官方版”的累计下载量超一千万),

学习IT的实用工具和网站推荐

对于一些学习IT的初学者来说,掌握一些实用的软件工具和学习网站是十分有必要的。本文要为大家推荐一些学习IT的相关的资源,像是鸠摩搜书和脚本之家等电子书搜索网站,还有冰点文库和文件搜索工具等实用工具以及

程序员:我终于知道post和get的区别

IT界知名的程序员曾说:对于那些月薪三万以下,自称IT工程师的码农们,其实我们从来没有把他们归为我们IT工程师的队伍。他们虽然总是以IT工程师自居,但只是他们一厢情愿罢了。此话一出,不知激起了多少(码

美团BERT的探索和实践

他山之石,可以攻玉。美团点评NLP团队一直紧跟业界前沿技术,开展了基于美团点评业务数据的预训练研究工作,训练了更适配美团点评业务场景的MT-BERT模型,通过微调将MT-BERT落地到多个业务场景中,

Fortinet的云安全观:上云≠安全 云安全市场或迎“又一春”!

近年来网络攻击事件频繁发生,企业对于网络安全的关注度已经到达前所未有的高度,如何保证业务的正常运转是每个企业最为关注的问题之一。而随着越来越多的企业将业务扩展到云端,云上安全问题也成为企业必谈的话题!

从reddit的一亿美元商业逆袭,看移动与PC产品的时代天堑

提起有“互联网头版”之称的reddit,你会联想到什么?想到这一网站上层出不穷的搞笑梗或meme图?还是程序员们经常制造出的各种有趣小发明?说起来在这个体量巨大、包容性极强、时刻制造着互联网新热点的论

从ResNet的诞生讲起:美公司在北京的AI研究所出了成果,中美究竟谁受益更多?

大数据文摘出品来源:macropolo编译:狗小白、Aileen中美之间摩擦不断,如今,AI竞争也成为了其中重要的组成部分。让我们假设这样一个场景:美国AI公司设立在中国的实验室取得了一些突破,谁从中

为什么说IPA智能流程自动化是企业IT的下一波浪潮?

提到IPA,可能很多人会立刻想到RPA。RPA,即机器人流程自动化,是企业IT过去两年最热门的技术之一。仅在2018年,就有三家公司拿到了总额超过十亿美金的风投,包括AnywhereAutomatio

tomcat的重启

点击下方截图可插入当前视频播放画面,了解更多Mackdown语法可以点击上方?图标jsp运行不需要重启tomcat而servlet需要

如何改变react的行内样式

如何改变react的行内样式在react中,可以这样来设置行内样式:render(){ conststyles={color:"red",fontSize:"16px"}; return( ); }

vue和react的区别是什么?

vue和react的区别1、监听数据变化的实现原理不同Vue通过getter/setter以及一些函数的劫持,能精确知道数据变化。React默认是通过比较引用的方式(diff)进行的,如果不优化可能导

vue和react的主要区别是什么?

Vue是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue采用自底向上增量开发的设计,其核心库只关注视图层,并且非常容易学习,也易与其它库或已有项目整合。另一方面,Vue完全有能力驱动采用

【源码解析】扒开ArrayList的外衣

积千里跬步,汇万里江河;每天进步一点点,终有一天将成大佬。本文内容当然ArrayList里的方法不止这些,本文主要讲一些常用的方法方法变量Arraylist里的方法变量主要有以下几个1.构造方法1.1

基于ApiBoot的前后分离演示脚手架诞生了~

知识改变命运,撸码使我快乐,2020继续游走在开源界点赞再看,养成习惯给我来个Star吧,ApiBootAdmin源码仓库:https://gitee.com/minbox-projects/api-

vue基于vant的uploader上传图片

小白第一次使用有赞的vant组件库,这里记录一下个人在项目上的一些使用,方便以后查阅. {{item.goods_title}} {{item.sku_param_value}} 愉