或如何用Git烤蛋糕
Git很难。 Git令人生畏。 学习曲线很大。 作为软件工程师,这至关重要。
Git是用于版本控制的行业标准。 这是我们大多数人在学校或编码训练营中都不学的东西。
我在新手训练营期间就第一次听说了Git,并完成了一些教程,但是直到找到工作并开始与其他开发人员合作,我才真正使用它。 实际上,我看到新的开发人员无时无刻不在出现,并且在学习Git方面遇到困难。 因此,如果您感到困惑,您并不孤单。
我讨厌每分钟学习如何使用Git。 很难为绝对的初学者找到资源,而我发现的少数资源写得不好或不太清楚。 他们通常会假设您可能没有(我没有)一定水平的知识。 因此,我想为准备开始从事第一项技术工作的人员或已经从事该工作并像我一样拥有" oh sh * t"时刻的工程师提供资源。
我不是Git专家。 但是我确实知道一些基础知识,这些基础知识是通过从开发人员的经验和投入中学到的,这些知识使我能够与同事进行协作,因此我可以真正接触到自己的专长:编写代码。 我将在这里学到的东西也希望能帮助您达到自己的专长。
无论如何,Git到底是什么?
Git是您在计算机上安装的软件。 如果尚未安装,请进行安装。
使Git与iTunes或Minesweeper(甚至比其功能更多)不同的原因在于Git没有图形用户界面(或GUI)。 GUI彻底改变了计算,使Steve Jobs和Bill Gates变得富有。 在使用GUI之前,您必须通过命令行与程序进行交互。 由于Git没有GUI,因此您也可以通过命令行与它进行交互(至少有时会在以后进行介绍)。
$ git checkout -b [branch name] origin/[branch name]
当您在教学网站上看到类似的内容时,这是一个命令行命令,可以在Mac的终端机,Windows的命令提示符或Linux的Shell中运行。
因此,这是人们想知道的其中一件事:$代表美元登录终端前的所有垃圾。 这是我的:
Kevins-MacBook-Pro-2:~ kevinmiller$
在教程中看到$时,只需在其右侧输入内容即可。 []或{}或<>表示您应该被视为自己的信息中的一员。 输入终端后,该命令的外观如下:
Kevins-MacBook-Pro-2:~ kevinmiller$ git checkout -b master origin/master
稍后我们将详细讨论命令。 接下来的逻辑问题:
但是它是做什么的呢?
好问题。 Git是用于源代码控制(有时称为版本控制)的工具。 它跟踪文件中的更改,并允许多个人在同一文件或一组文件上进行协作。 如果您曾经有多个名为" Paper-Draft.docx"," Paper-Final.docx"," PaperFinal2.docx"," PaperFinalFinal.docx"," PaperTHISISTHERIGHTONE.docx"的Microsoft Word文件,那么您就知道需要进行源代码控制。 您可以看到,如果您有五个,五十个,或者对于大型公司而言,成千上万的人在同一文档上工作,那怎么可能真正失控。 Git通常用于软件开发,但可以用来跟踪几乎所有文件集中的更改。
听起来不错……它是如何做到的?
Git为您的工作拍摄快照,称为提交。 回到我们的Microsoft Word Nightmare Analogy(™),您可以想到一次提交,就好像您打印了每个版本的学期论文并将其归档在文件柜中一样。 Git对存储库中的每个文件执行此操作。 每次文件更改时,都会创建一个新副本。 存储库只是" Git为您跟踪的所有文件"的花哨词。
多个人如何在不互相踩脚的情况下进行协作? 人们通常会在自己的部门工作。
根据Atlassian
分支代表独立的发展线。 分支充当编辑/阶段/提交过程的抽象。 您可以将它们视为请求全新的工作目录,暂存区和项目历史记录的方式。
有点稠密。 想象一下,您与一位合伙人开始了学期论文。 您负责撰写一个主题,而您的伴侣负责撰写另一个主题。 您是一起编写介绍段落的,但是现在每个人都需要独立工作。 你们两个都将从相同的文件" Paper-Intro.docx"开始,但是每个人都将拥有自己的文件柜! 您将能够独立工作并跟踪自己的版本,但是两者都是在同一时间开始的。 在工作时,每次获取要保存(提交)的版本时,都会将其打印出来并保存在文件柜中。 您的伴侣将对他们的文件柜做同样的事情。
许多教程使用树隐喻来描述分支。 您可以将原始分支(称为主分支)视为树干。 树上的每个分支都来自树干,但它自己分裂。 但是,我喜欢将其视为高速公路-假设您有一条只有一条车道的高速公路。 这是您的主分支。 如果创建另一个分支,则高速公路将变为两车道高速公路。 当您更改分支机构时,就像车道分支到了自己的出口一样。 您可以开车去做任何您想做的事,因为它与公路分开。 最终,它将合并回到高速公路上。
由于您已记录了所做的所有更改(如果选择提交),因此可以轻松地进行协作,回到旧版本,合并版本等。无限的可能性。 一个项目很可能具有多个分支或多个系列的版本。 随着开发的进行,这些分支将全部合并在一起以创建最终产品。
信不信由你,这是Git的基础。 理解它的最好方法就是使用它。 所以,让我们做一个蛋糕! 好吧,我们将为蛋糕制作购物清单。 我强烈建议您在自己的计算机上进行以下操作。 这样做会更有意义。
但是如何Git?
首先,我们需要建立一个存储库。 请记住,这只是计算机上的一个文件夹(及其子文件夹),文件在其中存放并由Git跟踪。
我在计算机上创建了一个名为Shopping List的文件夹。 进行相同的操作,在可以找到它的地方。
好的,我们有文件夹。 至此,我们还没有列表,也没有Git。 因此,让我们将文件夹放入Git存储库中。 在命令行上,运行以下命令:
$ cd
看看我在那里做什么? 我使用了我们之前提到的速记。 这实际上是我的命令行上的样子:
Kevins-MBP-2:gittutorial-ios kevinmiller$ cd /Users/kevinmiller/Developer/Shopping List
这是做什么的? 它将当前目录(cd)从旧目录(当前工作项目)更改为我们想要Git存储库的目录。 因此,一旦进入,请运行以下命令:
$ git init
这将初始化一个Git存储库。 如果您导航到系统上的该文件夹,则会看到一个名为.git的新文件夹,Git将在该文件夹中跟踪您的项目,在本例中为我们的购物清单。 注意:如果看不到.git文件夹,则可能必须启用查看系统上的隐藏文件夹。 通常,以"。"开头的文件和文件夹 隐藏起来是因为它们很重要,如果您不知道自己在做什么,很容易搞砸。
好的,现在让我们列出购物清单。 让我们保持简单,并创建一个名为ShoppingList.txt的.txt文件(如果需要,可以使用任何文本编辑器或命令行)。 让我们添加三种成分:
Eggs
Flour
Sugar
现在我们有了蛋糕的基本原料。 可能不花哨,但仍然可能很好。
记住,为了清楚和易于理解,我们正在制作一个简单的购物清单,但这通常是代码……Git对两者都起作用!
保存文件并输入:
$ git status
这是您应该看到的:
On branch master
No commits yet
Untracked files: (use "git add ..." to include in what will be committed) ShoppingList.txt
nothing added to commit but untracked files present
(use "git add" to track)
Kevins-MBP-2:Shopping List kevinmiller$
到目前为止,一切都很好! 如果您看到除此以外的其他内容,请放弃所有希望并放弃尝试。 开玩笑。 您可能在错误的目录中,也许尚未安装Git,或者您忘记保存文件。
在解开以上内容之前,我们先讨论一下GUI。 Git有几个不错的GUI包装器。 我使用SourceTree并强烈推荐它。 但是,您不必担心命令行。 一切都可以从命令行完成,在某些时候,您将不得不使用它。 GUI可以完成很多工作,但不能完成所有工作。 这实际上是个人喜好问题。 最终,一旦您知道要使用Git做什么,就无关紧要(通过命令行或GUI)。
在上方,我们询问了我们的状态,并收到Git的回复。 它告诉我们我们的分支机构(我们的快照历史,还记得吗?)。 这就说得通了。 主文件是第一个分支的默认名称,因为它是"主副本"。
现在它说"没有提交"。 这也是有道理的。 我们尚未告诉它进行任何提交。
这就是它变得有趣的地方。 它说:
Untracked files: (use "git add ..." to include in what will be committed)ShoppingList.txtnothing added to commit but untracked files present (use "git add" to track)
Git注意到我们制作了一个新文件-这就是我们制作的文件。 凉! 这也告诉我们尚未为我们跟踪此文件。 我们希望它做到这一点。 对我们来说幸运的是,它告诉我们如何使用$ git add 来执行此操作…现在我们知道这意味着$ git add ShoppingList.txt。 继续做吧。
Cool。 怎么办? 再次执行
On branch masterNo commits yetChanges to be committed: (use "git rm --cached ..." to unstage)new file: ShoppingList.txt
好! 现在,我们要进行一些更改! 是。 我们从没有文件转到了包含3个项目的ShoppingList.txt文件。 Git告诉我们下一次提交的内容。 但是我们仍然没有告诉它要提交。 来做吧。
$ git commit -m "Created shopping list for cake"
-m是什么? 这是输入提交消息的快捷方式。 永远不要在没有提交消息的情况下进行提交,而永远不要在带有" WIP"之类的通用提交消息的情况下进行提交。 您希望简明扼要,但要在提交消息中进行描述。 看起来似乎有些痛苦,但是请考虑一下:我们使用Git的全部原因是为了跟踪文件中的更改。 如果(当您)必须回顾文件的历史记录或浏览他人的工作,则清除提交消息至关重要。 请参阅本文,以获取编写提交消息的最佳实践的示例。
恭喜你! 您是第一次提交。 让我们做更多。
那个蛋糕有点无聊。 让我们添加一些浇头并将其分类。 我们也想将面粉改为黄油,因为我们家里已经有面粉了。 (您可以将下面的列表想象为代码吗?每个"配方"都可以是一个函数。)
--- Cake ---EggsButterSugar--- Toppings ---StrawberriesRaspberriesChocolate
好吧,让我们做和以前一样的事情:
$ git statusOn branch masterChanges not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory)modified: ShoppingList.txtno changes added to commit (use "git add" and/or "git commit -a")
所以现在我们有了一些不同。 "没有为提交而进行的更改……"是什么意思? 这表示我们进行了一些更改,但尚未提交或"保存"。
除了简要介绍Git的工作方式之外,我还提到过:Git保留整个项目的快照或提交。 如果您制作每个文件的副本,并且在您的项目中拥有成千上万的提交和成千上万的文件(我目前的项目有2,446个),那么它将变得非常快。 那么Git如何处理呢? 如果有更改,它只会创建文件的新副本。 否则,它将引用文件的旧副本。 在这里,它将文件的最新副本与此新副本进行比较,然后说:"嘿! 这些是不同的,您想做什么?"
让我们看看这些变化是什么…
diff --git a/ShoppingList.txt b/ShoppingList.txtindex b61f001..774565b 100644--- a/ShoppingList.txt+++ b/ShoppingList.txt@@ -1,3 +1,9 @@+--- Cake --- Eggs -Flour+Butter Sugar++--- Toppings ---+Strawberries+Raspberries+Chocolate
这使我们可以看到上一次提交与现在提交之间的差异。 它用-显示删除的行,以及我们用+添加的行。暂时不要担心顶部的内容。 这对您对Git概念的一般理解很重要,但并不重要。
现在,我们必须决定所做的更改将包含在下一次提交中。 这称为暂存。 登台是您选择要提交或"保存"的更改的地方。 暂存实际上并没有"保存"任何内容,您必须致力于做到这一点。 如果您一次对多个文件进行了更改,则可以选择要在此处进行的更改。 在很多情况下,您可能不想包含所有更改,但现在,我们希望包含所有更改。
$ git add ShoppingList.txt$ git commit -m "Added ingredients for fruit torte, removed flour"
太棒了! 现在购物清单看起来不错。 但是在我们购物之前,保罗·好莱坞和玛丽·贝瑞想在购物清单中添加一些物品。 让我们为他们创建新的分支,以便他们可以将项目添加到列表中。
(在这里播放……我们将自己做所有这一切,但是想像您正在与需要处理同一文件的同事进行真正的协作。)
让我们为Paul创建一个分支,为Mary创建一个分支:
$ git branch pauls-branch
$ git branch marys-branch
好吧! 现在,我们已经为Paul和Mary创建了分支机构,以便分别工作。 尽管它并不完全准确,但是您可以将创建分支视为创建项目的新副本。 然后,Paul和Mary将对自己的副本而不是主副本(分支)进行更改。 他们(我们)将做出改变。 首先,执行以下操作:
$ git branch
这是您应该看到的:
marys-branch* masterpauls-branch
我们有三个分支:我们刚刚创建的两个分支和最初的一个分支,即master。 从本文开头继续我们的高速公路隐喻,我们现在有了一条三车道的高速公路。 这些车道之一即将驶出。
因此,让我们检查一个分支并开始制作购物清单:
$ git checkout pauls-branch
如果需要,请再次运行$ git branch,您会看到星星现在在pauls-branch上。 检出分支使其成为您的活动工作副本。 您对文件所做的任何更改都将提交到该分支。
好的,该添加一些东西到Paul的购物清单中了。 打开您的购物清单并进行以下更改:
--- Cake ---EggsUnsalted ButterSugar--- Toppings ---BlackberriesRaspberriesChocolate
保罗自命不凡,喜欢无盐黄油,所以他换了黄油。 他还把草莓换成了黑莓。 保存文件,然后暂存并提交这些更改。
$ git add -A
这是什么? 这只是暂存所有已修改文件的快捷方式。 在Git中,有多种方法可以执行相同的操作。
$ git commit -m "Added ingredients for summer fruit genoise"
我们只想要一个购物清单,所以让我们将其合并为一个清单。 首先结帐的主人(请记住,这是主要的高速公路)。
$ git checkout master
当您签出其他分支时,Git实际上会自动交换目录中的文件。 如果看不到文本文件中的更改,请关闭然后重新打开。
现在,我们将Paul的分支机构合并回我们的原始购物清单:
$ git merge pauls-branch
还不错吧? 现在,保罗的更改应反映在我们的清单中! 打开并签出。 我们来看看Mary的分支,并向其中添加一些内容。
$ git checkout marys-branch
打开购物清单。 您会看到它回到了我们分支的状态。 让我们对玛丽进行更改。 (将草莓换成蓝莓。)
--- Cake ---EggsButterSugar--- Toppings ---BlueberriesRaspberriesChocolate
好。 保存文件,然后暂存并提交更改。
$ git add -A$ git commit -m "Added ingredients for blueberry cake"
好! 让我们将其合并到master分支中。
$ git checkout master$ git merge marys-branch
哦! 出问题了!
CONFLICT (content): Merge conflict in ShoppingList.txtAutomatic merge failed; fix conflicts and then commit the result.
那这是怎么回事? 您可能已经猜到了,这是故意的。 这称为合并冲突,就像生活中的冲突一直在发生。
当我刚开始时,听到"合并冲突"一词使我感到恐惧。 让我们尝试理解它,以减少恐惧感。 是什么导致合并冲突? 根据Github的帮助,"合并冲突会在合并具有竞争提交的分支时发生。" 让我们稍微打开一下包装。 保罗和玛丽都同时分支,这意味着他们俩都从相同版本的购物清单开始。 保罗做了一些更改,玛丽做了一些更改。 我们将Paul的更改合并到我们的主购物清单中,现在我们尝试将Mary的版本合并到其中。
即使这不是我们开始使用的原始文件,Git仍会尝试进行更改。它知道在第6行上我们正在尝试更换草莓->蓝莓,但我们用黑莓代替了草莓,因为保罗改了。我们有“竞争提交”。 Git不知道该怎么办,需要人工指导。那就是我们进来的地方。
打开您的ShoppingList.txt文件。 您会在其中看到一些新内容:
--- Cake ---EggsUnsalted ButterSugar--- Toppings ---<<<<<<< HEADBlackberries=======Blueberries>>>>>>> marys-branchRaspberriesChocolate
<<<<<<
如果我们想说"不,保罗,黑莓现在太贵了",我们将删除黑莓。 但是,我们将继续保留两者。 这就是Git不仅更换黑莓->蓝莓的原因。 我们可能想要两者-我们都这样做。 删除冲突标记,编辑文件,然后保存。
现在您可能会问自己,为什么黄油不存在冲突? 玛丽的树枝上有普通黄油,而保罗的树枝上有无盐黄油? 好吧,玛丽没有尝试换黄油,所以吉特选择了保罗的零钱,这已经掌握了。
--- Cake ---EggsUnsalted ButterSugar--- Toppings ---BlackberriesBlueberriesRaspberriesChocolate
赞! 我们已经解决了冲突。 但是,我们仍然必须完成合并。 记住,上面说修复冲突,然后提交结果。
$ git add -A$ git commit -m "Merged marys list"
您刚刚解决了第一次合并冲突! 现实世界中的合并冲突将比这个简单的示例更为复杂,但是基本概念是相同的。
当一切顺利时,Git工作流程非常简单:
- · checkout分支
- · 做出改变
- · 阶段变更
- · 提交变更
- · 回到1
远程与本地
我们在本教程中所做的所有操作都是本地的,这意味着我们的计算机会跟踪我们计算机上的所有这些文件,而这些文件在其他任何地方都不存在。 如果您要与同事合作,并且想做一些非常合理的事情(例如使用不同的计算机),则此功能不是很有用。 所以你会怎么做? 使用远程存储库在线跟踪这些更改。 通常,它会托管在BitBucket或GitHub之类的网站上,这意味着您的代码库(在我们的情况下,我们的购物清单)将存储在其他人的计算机上。
因此,如果代码存储在其他位置,我们如何对其进行更改? 首先,我们需要克隆该存储库。
克隆说明因存储库的托管位置,身份验证类型等而异,因此,我将由您自己决定。 什么是克隆? 顾名思义,克隆存储库会在您的计算机上复制一个副本。 拥有此副本后,就可以对其进行处理-像我们上面讨论的那样进行更改。
对副本进行更改后,它们仅在计算机上本地存在。 您如何将它们发送到远程存储库? 有一些命令。 这些绝不是详尽无遗的,并且有许多不同的情况需要不同类型的推拉,但这将使您对该概念有基本的了解。
推送是指对所做的更改并将其推送到远程(代码库的存储位置)。 还记得玛丽对蛋糕食谱的更改吗? 之后,她需要将自己的更改推送到遥控器,如下所示:
$git push origin marys-branch
如果分支尚不存在,则必须使用:
git push -u origin marys-branch
-u是–set-upstream的缩写,它指示Git创建"上游"或远程分支。 玛丽·贝瑞(Mary Berry)当然是知道的,因为她是Git专家。
拉就是拉其他人对您的本地计算机所做的任何更改。 想象一下,保罗想查看主购物清单的新变化,自从克隆存储库以来,该更新已更新:
$ git pull master 获取此后所做的所有更改,并更新本地分支。
我认为,pull request是一个非常令人困惑的名称。 假设玛丽负责购物清单代码存储库。 保罗脱离了大师,进行了一些更改,并将这些更改推到了远端。 他现在希望将这些更改合并到母版中。 好吧,由于玛丽负责并需要批准这些更改,因此他提交了一个称为拉取请求或简称PR的内容。 他要求玛丽将其分支机构拉到主分支机构。 看到? 我认为这很麻烦。 他真的是要求玛丽将其分支机构合并为大师。
通过诸如BitBucket或Github之类的服务发出拉请求。 除了托管您的存储库外,他们还为同事提供了一个提交和审阅彼此的请求请求的地方(称为代码审阅)。 玛丽只会登录Github来查看Paul的公关。 她将看到他要进行的更改,并有机会在批准或拒绝之前发表评论并请求更改。
Git:最终疆界
在现实世界中,您的项目将比这更复杂。 您将拥有更长的文件,更多的文件和更多的分支(希望不是太多)。 通常,将有一个主分支,一个开发分支和多个功能分支。 master分支通常代表生产中的代码(例如,实时网站或iTunes Store中的应用程序)。 Develop表示正在进行的代码。 开发人员通常会针对他们正在使用的每个功能从开发中分离出新的分支,然后在完成后将其合并回开发中。 当一个新版本经过测试,稳定并且可以发布时,develop将合并到master中。
信不信由你,Git的工作流程在上面的简单购物清单和现实世界中几乎相同。 让我们看一下我们所做的工作流程,并将其与实际工作流程进行比较。
工作需要完成。 在我们的案例中,我们需要添加蛋糕的配料。 一个真实的示例可能是在您的数据服务对象中添加一种方法以连接到API
有人开始工作。 上面,保罗和玛丽做了分支以添加成分。 在现实世界中,您可能会脱离开发并创建一个名为"功能/集成所有食谱-API"的分支
进行更改。 通常,这意味着代码已编写。 例如,保罗在饼干食谱中添加了成分。 在现实世界中,从API获取配方可能类似于以下功能:
func getRecipesWith(ingredients: [String], success: @escaping (([Recipe]) -> Void), failure: @escaping ((APIError) -> Void)) { let endpoint = "(DataStore.ingredientFilterEndpoint)" requestManager.request(withEndpoint: endpoint, method: .GET, params: [DataStore.ingredientsKey: ingredients.commaDelimitedComponents], success: { responseObject in guard let json = responseObject as? JSON, let recipeList = RecipeList(json: json), let recipes = recipeList.recipes else { failure(APIError(failingURL: endpoint, message: "Cannot parse Recipe from JSON")) return } success(recipes) }, failure: { error in if let error = error { failure(error) } }) }
变更已上演。 我们进行了本教程中的所有更改,但是如果我们编写了一些我们不想提交的内容,例如,一个测试函数,例如:
func generateFakeRecipe() -> Recipe
我们可能选择不上演。
更改已提交。 我们承诺要保留的更改。 保罗的作品之一是"为夏季黑莓添加了水果",现实世界中可能是"数据存储中的添加方法可以检索按成分过滤的配方阵列"
更改已推送。 工作完成后,将分支推送到远程。
更改已审核。 同行审查和评论/请求更改。
更改被合并。 分支合并了!
这是有关Git的全部知识吗? 离得很远。 在您感到自在之前,您可能需要自己做更多的研究。 您肯定必须练习,弄糟,然后再练习一些。 尽量不要太害怕-几乎所有内容都可以撤消。 Git非常安全。 不要害怕寻求帮助;不要害怕。 大多数开发人员都记得学习Git的感觉。
希望您能从本文中获得我所没有的东西:一个可以轻松理解而无需做任何假设的综合场所,以学习Git的基础知识。 祝您好运,编码愉快!
(本文翻译自Kevin Miller的文章《A Survivor's Guide to Git》,参考:https://medium.com/@kevin.thom.miller/a-survivors-guide-to-git-5cd93b13335b)