git使用教程(翻译自UCB CS61B)
搬运者注
UCB cs61b的git教程相当好,里面的比喻也非常形象。抽时间简略翻译了一下,有错误还请指出。
准备工作
git安装
访问http://git-scm.com/download/ 网站,下载Windows版本的git。
下载好打开安装器后,会看到很多选项,不推荐使用Default(默认)选项。请按照下面的选项进行选择:
- 选择Windows Explorer integration选项,这个选项能使你在某文件上点击右键后,直接出现git相关选项(如Git Bash)。这个功能不是必须的,但是能给你带来很大的方便。
- 下一步,选择defalut Git Bash。我们强烈推荐你使用nano,除非你之前用过vim。
- Git Bash是一个具有内置Git支持的Bash shell,它允许您使用一些MinGW/Linux工具。如果您已经知道bash是什么,并且有一个首选的终端,那么欢迎您使用它。建议从Git Bash中使用Git。
- 基于Windows和Unix的系统使用不同的方法来表示文件中的行结束符。使用推荐的选项(第一个)来避免以后出现看起来很神秘的bug。
- 此选项允许您在两个不同的“终端仿真器”之间进行选择。这会对你的工作环境产生深远的影响,而IMO MinTTY是个更好的选择。我们的官方指南假设你选择了MinTTY,所以如果你选择了另一个选项,你就只能靠自己了。警告:如果您想使用git bash以交互模式运行python,您需要以winpty python或python -i的形式运行它,而不是仅仅输入python。
- 打开你的终端,用下列命令检查git命令是否可以使用:
git的版本号应该打印出来。如果您看到“git: command not found”或类似的命令,请尝试打开一个新的终端窗口、重新启动计算机或重新安装git。
git --version
学习如何使用终端
如果您已经知道如何打开和使用终端,请跳过此部分。
终端是一个应用程序,它允许您运行各种程序,以及在自己的计算机中操作文件。这是一个功能强大但也很危险的工具,所以请小心使用这些命令。在UNIX操作系统上,终端应用程序将为您提供所需的一切。在Mac OS上,例如,您可以使用Spotlight搜索终端应用程序。
- cd: 改变你的工作目录
cd hw
此命令将把您的目录更改为hw。 - pwd: 当前工作目录
pwd hw
如果您不确定当前目录的位置,此命令将告诉您当前目录的完整绝对路径。 - .: 表示当前目录
cd .
此命令将您的目录更改为当前目录(即。什么也不做)。 - …: 表示在当前目录之上的父目录
cd ..
此命令将把目录更改为其父目录。如果您在/workspace/day1/中,该命令将把您放在/workspace/中。 - ls: 列出目录中的文件/文件夹
cd ls
此命令将列出当前目录中的所有文件和文件夹。
cd ls -l
此命令将列出当前目录中所有文件和文件夹,并显示其时间戳和文件权限。这可以帮助您再次检查您的文件是否正确更新或更改您的文件的读写执行权限。 - mkdir: 新建一个文件夹
mkdir dirname
该命令将在当前目录下创建一个名为dirname的目录。 - rm: 删除一个文件
rm file1
此命令将从当前目录中删除file1。如果file1不存在,该命令将无法成功执行。
rm -r file1
此命令将递归地删除dir1目录。换句话说,它将删除dir1内的所有文件和目录。小心使用这个命令! - cp: 复制一个文件
cp lab1/original lab2/duplicate
这个命令将复制lab1目录中的原始文件,并在lab2目录中创建一个副本。 - mv: 移动或重命名一个文件
mv lab1/original lab2/original
该命令将original从lab1移动到lab2。与cp不同,mv在lab1目录中不保留原始内容。
mv lab1/original lab1/newname
此命令不移动文件,而是将其从原始名称重命名为newname。
还有一些其他有用的技巧,当光标在命令行时:
- UNIX可以使用tab补全来完成文件名和目录名。当您有一个不完整的名称(对于已经存在的内容)时,尝试按tab键进行自动完成或可能的名称列表。
- 如果您想要重新输入最近使用的相同指令,请按键盘上的上键直到看到正确的指令。如果您正在执行重复的指令(比如在测试时在命令行上运行Java程序),这将节省输入时间。
git使用
A. 版本控制系统简介
版本控制系统是追踪文件随时间变化的工具。版本控制允许您查看或恢复到以前的文件迭代。版本控制的某些方面实际上构建在常用的应用程序中。如我们熟悉的撤销命令。
在编码时,版本控制系统可以追踪代码修订的历史,从代码的当前状态一直追踪到第一次追踪时的状态。这允许用户引用其工作的旧版本,并与其他人员(如开发人员)共享代码更改。
版本控制已经成为软件开发和工业协作的支柱。在这个类中,我们将使用Git。Git有很出色的文档,所以我们强烈建议有兴趣的读者阅读本指南的其他部分。
Git简介
Git是一个分布式的版本控制系统,而不是一个集中式的版本控制系统。这意味着每个开发人员的计算机存储整个项目的整个历史(包括所有旧版本)!这与Dropbox等工具不同,后者的旧版本存储在别人拥有的远程服务器上。我们称整个项目的整个历史为“仓库”。由于仓库是在本地存储的,因此我们可以在自己的计算机上本地使用Git,即使没有internet连接。
除了我们将在本指南中学习使用的基于文本的界面之外,还有一个Git GUI(图形用户界面)。我们不对GUI进行正式的介绍。
B.本地仓库(叙述性介绍)
让我们来看一个如何使用git的示例。在这个故事中,我们将使用许多不熟悉的术语和概念。如果要查看这个叙述示例的视频版本,请参见此视频。
假设我们想要在我们的计算机上存储各种各样的食谱,并且想要在我们改变这些食谱时追踪它们的历史。我们可以首先为seitan和tofu recipes创建目录,然后使用sublime(在我的计算机上使用subl命令调用)创建每个食谱。
我们假设您正在阅读本文,而不是亲自尝试这些命令。如果你想把所有的东西都敲出来,你需要使用一个安装在你电脑上的文本编辑器,而不是直接用subl。
(搬者注:若想在windows git bash中使用subl命令,请下载Sublime Text3 并参见此文章配置)
$ cd /users/sandra
$ mkdir recipes
$ cd recipes
$ mkdir seitan
$ mkdir tofu
$ cd seitan
$ subl smoky_carrot_tahini_seitan_slaw.txt
$ subl boiled_seitan.txt
$ cd ../tofu
$ subl kung_pao_tofu.txt
$ subl basil_ginger_tofu.txt
现在我们有四种食谱,两种是豆腐(tofu),两种是小麦素肉(seitan)。为了设置我们的git仓库来存储我们的食谱演变的历史,我们需要使用以下命令:
$ cd /users/sandra/recipes
$ git init
git init所做的是告诉git版本控制系统,我们想要追踪当前目录的历史,在本例中是“/users/sandra/recipes”。但是,此时仓库中没有存储任何内容。就像我们买了个保险柜,但是我们还没有往里面放任何东西。
要在仓库中存储所有内容,首先需要添加文件。例如,我们可以这样做:
git add ./tofu/kung_pao_tofu.txt
现在,git开始变得有点奇怪了。即使调用了add命令,我们仍然没有将配方存储在仓库(即保险箱)中。
相反,我们所做的是将kung_pao_tofu.txt添加到要追踪的文件列表中(即稍后添加到保险箱中)。这里的思路是,您可能不想追踪/users/sandra/recipes文件夹中的每个文件,所以add命令告诉git应该追踪哪些文件。我们可以通过使用git status命令来查看这个命令的效果。
$ git status
在这种情况下,你会看到以下输出:
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: tofu/kung_pao_tofu.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
seitan/
tofu/basil_ginger_tofu.txt
输出中的 “changes to be commited” 部分列出了当前正在被追踪的所有文件,这些文件的改动已准备好commit了(即已准备好放入保险柜)。我们还看到了一些未被追踪的文件,即seitan文件夹和tofu/basil_ginger_tofu.txt文件,它们未被追踪,因为我们没有使用git add来添加它们。
让我们尝试添加豆腐/basil_ginger_tofu.txt,并再次检查status:
$ git add ./tofu/basil_ginger_tofu.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: tofu/basil_ginger_tofu.txt
new file: tofu/kung_pao_tofu.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
seitan/
我们可以看到,这两个豆腐食谱都被追踪了,但是两个seitan食谱都没有被追踪。接下来,我们将使用commit命令将豆腐食谱的副本粘贴到仓库中(即到保险箱中)。为此,我们使用git commit命令,如下所示:
$ git commit -m "added tofu recipes"
执行时,commit命令将所有新添文件(add过的文件,即当前豆腐配方)的快照存储到仓库中。因为我们没有对seitan菜谱使用git add,所以仓库里的快照中没有它们。我们得到的这张快照现在是永远安全的(只要我们的电脑硬盘不出故障,或者我们不损坏秘密存储库文件)。-m 命令允许我们在commit的快照中添加一条消息,这样我们就可以记住本次commit的最重要的内容。
作为另一个类比,你可以把整个过程想象成在相机上拍一张全景照片。add命令捕获图像的一部分,commit命令将所有的“添加” 项缝合到一个全景图中,并将该全景图放入一个保险箱中。正如全景图只包含您所指向的东西(而不是您周围的整个360度圆),commit命令只保护使用add命令添加过的那些文件(而不是recipe目录中的所有文件)。
使用commit之后,您会注意到git status不再列出“Changes to be committed”下的文件。这就像你拍完一张全景照片后,所有临时的小图像文件都会被扔掉一样。此时git status的结果如下图所示:
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
seitan/
nothing added to commit but untracked files present (use "git add" to track)
如果您查看tofu文件夹中的文件,您将看到commit过程不会影响我们计算机上的原始文件。这就像你给朋友们拍一张全景照片时,他们并不会被吸到你的手机中。
我们可以使用特殊的git log查看快照的证据。
$ git log
commit 9f955d85359fc8e4504d7220f13fad34f8f2c62b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:06:48 2016 -0800
added tofu recipes
这个巨大的字符串9f955d85359fc8e4504d7220f13fad34f8f2c62b是commit的ID。我们可以使用git show命令来查看这个commit的内部。
$ git show 9f955d85359fc8e4504d7220f13fad34f8f2c62b
commit 9f955d85359fc8e4504d7220f13fad34f8f2c62b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:06:48 2016 -0800
added tofu recipes
diff --git a/tofu/basil_ginger_tofu.txt b/tofu/basil_ginger_tofu.txt
new file mode 100644
index 0000000..9a56e7a
--- /dev/null
+++ b/tofu/basil_ginger_tofu.txt
@@ -0,0 +1,3 @@
+basil
+ginger
+tofu
diff --git a/tofu/kung_pao_tofu.txt b/tofu/kung_pao_tofu.txt
new file mode 100644 index
0000000..dad9bd9
--- /dev/null
+++ b/tofu/kung_pao_tofu.txt
@@ -0,0 +1,3 @@
+szechuan peppers
+tofu
+peanuts
+kung
+pao
git show命令允许我们直接查看commit的变化。我们并不期望它的所有内部结构您都能看懂,但是您可能会发现commit的是文件名称和内容的快照。
注意:在现实生活中或在61B课程中,很少使用git show命令,但是在这里,它对于查看commit内部很有用,以便更好地了解它们是什么。
假设我们现在要修改宫保豆腐的配方,因为我们决定要加入白菜。
$ subl ./tofu/kung_pao_tofu.txt
我们刚才对kung_pao_tofu.txt所做的更改没有保存在仓库中。事实上,如果我们再次执行git状态,我们将得到:
$ git status
On branch master
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: tofu/kung_pao_tofu.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
seitan/
你可能会对自己说“好吧,我们再来commit一次。”但是,如果我们尝试commit,git会告诉我们:
$ git commit -m "added bok choy"
On branch master
Changes not staged for commit:
modified: tofu/kung_pao_tofu.txt
Untracked files:
seitan/
no changes added to commit
这是因为即使已经追踪了kung_pao_tofu.txt,我们还没有为这次commit暂存我们的更改。想把改变的内容存储到仓库,我们首先需要再次使用add命令,它将为commit暂存更改 (换句话说,我们需要先给新kung_pao_tofu.txt拍照,再创建新的全景,把它放到保险柜里)。
$ git add ./tofu/kung_pao_tofu.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: tofu/kung_pao_tofu.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
seitan/
我们看到对kung_pao_tofu.txt的更改现在是“to be commited”,这意味着下一次的 commit 将包括对该文件的更改。我们像以前一样commit,并使用git日志查看所有已拍快照的列表。
$ git commit -m "added bok choy"
$ git log
commit cfcc8cbd88a763712dec2d6bd541b2783fa1f23b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:24:45 2016 -0800
added bok choy
commit 9f955d85359fc8e4504d7220f13fad34f8f2c62b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:06:48 2016 -0800
added tofu recipes
我们现在看到有两个commit。我们可以再次使用show来查看cfcc8cbd88a763712dec2d6bd541b2783fa1f23b中发生了什么变化,但是在本指南中我们不会这样做。
假设我们后来觉得白菜很恶心。我们可以使用 checkout 命令回到之前的文件,如下所示:
$ git checkout 9f955d85359fc8e4504d7220f13fad34f8f2c62b ./recipes/tofu
将checkout命令想象成一个进入我们的保险箱的机器人,它推算出当最新的全景图是9f955d85359fc8e4504d7220f13fad34f8f2c62b时的豆腐的配方,最后重新排列实际的 recipes/tofu 文件夹中的所有内容,使其与创建快照9f955d85359fc8e4504d7220f13fad34f8f2c62b时的内容完全一样。如果我们现在在运行这个命令后查看recipes/tofu/kung_pao_tofu.txt的内容,我们将看到白菜不见了(唷)!
szechuan
peppers
tofu
peanuts
kung
pao
重要提示:checkout命令不会更改commit的历史记录!或者换句话说,包含全景照片的保险箱完全不受checkout命令的影响。git的目的就是创建一个日志,记录所有发生在我们文件上的事情。换句话说,如果你在2014年和2015年给你的房间拍了一张全景照片,并把它们放在保险箱里,然后在2016年决定把它放回2014年的样子,你并不会把2015年的全景照片放回火堆里。2016年的照片也不会神奇地出现在你的保险箱里。如果你想记录2016年它是什么样子,你需要再拍一张照片(加上适当的-m信息来记住你刚刚做了什么)。
同样重要的是:确保在使用checkout时指定一个文件(或目录)。否则,您将使用一个更强大的checkout版本,这可能会使您感到困惑。如果出现这种情况,请参阅61B git- wtfs (git奇怪的技术故障场景)。
如果我们真的想要commit一张最新的宫保豆腐的快照(它已经不再有白菜了),我们必须commit:
$ git commit -m "went back to the original recipe with no bok choy"
$ git log
commit 4be06747886d0a270bf1d618d58f3273f0219a69
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:32:37 2016 -0800
went back to the original recipe with no bok choy
commit cfcc8cbd88a763712dec2d6bd541b2783fa1f23b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:24:45 2016 -0800
added bok choy
commit 9f955d85359fc8e4504d7220f13fad34f8f2c62b
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:06:48 2016 -0800
added tofu recipes
然后,我们可以使用show来查看最近一次commit的内容。
$ git show 4be06747886d0a270bf1d618d58f3273f0219a69
commit 4be06747886d0a270bf1d618d58f3273f0219a69
Author: Sandra Upson <sandra@Sandras-MacBook-Air.local>
Date: Sun Jan 17 19:32:37 2016 -0800
took boy choy out gross
diff --git a/tofu/kung_pao_tofu.txt b/tofu/kung_pao_tofu.txt
index 35a9e71..dad9bd9 100644
--- a/tofu/kung_pao_tofu.txt
+++ b/tofu/kung_pao_tofu.txt
@@ -1,4 +1,3 @@
szechuan
peppers
tofu
peanuts
kung
pao
-bok choy
\ No newline at end of file
不是很重要的提示:非常细心的读者可能已经注意到,我在删除bok choy之前没有使用git add。这是因为一个有趣的事实:checkout实际上也会对任何由于回滚而更改的文件进行自动git add。
这是git的基础。总而言之,用我们的照片类比:
git init创建一个用于永久存储全景图片的框。
git add:获取一个东西的临时照片,稍后可以将其组装成一张全景照片。
git commit:将所有可用的临时照片组装成一张全景照片。同时销毁所有临时照片。
git log:列出了我们拍过的所有全景照片。
git show:查看特定全景照片中的内容。
git checkout:重新排列文件,使其在给定的全景照片中显示出来。不会以任何方式影响您箱子中的全景照片。
关于git还有更多的内容需要学习,但是在开始之前,让我们对我们刚刚完成的工作进行更正式的解释。
C.本地仓库(技术概述)
初始化局部仓库
让我们首先从本地仓库开始。如前所述,仓库存储文件以及对这些文件的更改历史。在开始之前,您必须在终端中输入以下命令初始化Git仓库,并将其历史记录存储在本地存储库的目录中。如果您使用的是Windows,那么在键入这些命令时应该使用一个Git Bash终端窗口。
$ git init
额外内容:在初始化Git存储库时,Git将创建一个. Git子目录。在这个目录中,它将存储一组元数据,以及旧的文件的实际快照。但是,您永远不需要实际打开这个.git目录的内容,而且绝对不应该直接更改其中的任何内容!
根据操作系统的不同,您可能看不到文件夹,因为defaut不会显示以 “.” 开头的文件夹。UNIX命令ls -la将列出所有目录,包括您的.git目录,因此您可以使用该命令检查您的repo是否已正确初始化。
已追踪文件与未追踪文件(Tracked vs. Untracked Files)
Git repos一开始并不追踪任何文件。为了保存文件的修订历史,您需要追踪它。Git文档中有一个关于记录更改的优秀部分。这部分的图片放在这里方便你:
如图所示,文件主要分为两类:
未追踪的文件:这些文件要么从未被追踪,要么已从追踪中删除。Git没有维护这些文件的历史记录。
被追踪的文件:这些文件已经添加到Git存储库中,可以处于不同的修改阶段:未修改、修改或暂存。
未修改的文件是自最后一个版本的文件被添加到Git repo以来没有任何新更改的文件。
修改后的文件与Git保存的文件不同。
暂存文件是用户指定作为将来commit的一部分的文件(通常通过使用git add命令)。我们可以把这些看作是被标亮的文件。
下面的Git命令允许您查看每个文件的状态,即它是未追踪的、未修改的、修改的还是阶段的:
$ git status
git status 命令对于确定存储库中每个文件的确切状态非常有用。如果你对改变了什么和将要 commit 什么感到困惑,它可以提醒你下一步该做什么。
Staging & Committing
commit 是工作目录在特定时间的特定快照。用户必须通过 staging files 来指定什么组成了快照。
add命令允许您 stage 一个文件(在下面的示例中称为file)。
$ git add FILE
一旦您stage了所有希望包含在快照中的文件,就可以将它们作为一个整体来commit,并附带一条消息。
$ git commit -m MESSAGE
您的消息应该是描述性的,并解释您的commit对代码做了哪些更改。您可能希望简单地描述bug修复、实现的类等信息,以便稍后在查看commit日志时,您的消息能够提供帮助。
为了查看以前的commit,你可以使用log命令:
$ git log
Git参考指南中有一节很有用,它介绍了查看commit历史记录和在搜索特定commit时过滤日志结果。还值得检查gitk,它是由命令行提示的GUI。
作为开发工作流程的补充说明,尽可能频繁地commit代码是一个好习惯。当您对代码进行重大(甚至微小)更改时,请 commit。如果您正在尝试一些您可能不会坚持的东西,那么将其commit (可以commit到一个不同的分支,下面将对此进行解释)。
经验法则: 如果你commit了,你总是可以恢复你的代码或者改变它。但是,如果您不commit,就无法获得旧版本。所以经常commit!
取消修改
Git参考中有很多关于取消的内容。请注意,虽然Git围绕着历史的概念,但是如果您使用其中的一些取消命令进行恢复,可能会丢失您的工作。因此,在取消您的命令之前,请仔细阅读这会带来的影响。
-
取消一个你还没有commit的文件:
$ git reset HEAD [file]
这将把文件的状态恢复到修改后的状态,保持更改不变。不要担心这个命令会撤销任何工作。这个命令相当于删除一个要合并到全景图中的临时图像。
为什么我们需要使用这个命令?假设您不小心开始追踪一个您不想追踪的文件。(比如一段你自己的尴尬视频) 或者您对一个文件做了一些更改,您原以为会commit该文件,但现在还不想commit。 -
修改最新的commit(更改commit消息或添加忘记的文件):
$ git add [forgotten-file]
$ git commit --amend
请注意,这个新修改的commit将取代以前的commit。 -
将文件恢复到最近commit时的状态:
$ git checkout -- [file]
如果您想真正撤销您的工作,下一个命令非常有用。假设您在commit之前已经修改了某个文件,但是您希望您的文件恢复到修改之前的状态。
注意:此命令可能非常危险,因为自上次commit以来对文件所做的任何更改都将被删除。请谨慎使用!
D. 远程仓库
git一个特别方便的特性是能够将存储库的副本存储在其他计算机上。回想一下,我们的快照都存储在计算机的一个秘密文件夹中。这意味着,如果我们的电脑坏了或被毁了,我们所有的快照的下场也一样。
假设我们想要将豆腐和面饼食谱推到另一台计算机上,我们通常会使用以下命令。
$ git push origin master
然而,如果我们尝试这样做,我们只会得到以下信息:
fatal: 'origin' does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists.
这是因为我们还没有告诉git将文件发送到哪里。碰巧,有一家以盈利为目的的私人公司叫GitHub,它通过储存人们仓库的副本来赚钱。他们的商业模式很简单:你付钱给他们来存储你的仓库(尽管他们允许你拥有免费的仓库,如果你让它对全世界开放)。
在61B中,我们将使用GitHub来存储仓库。要在GitHub上创建存储库,您可能需要使用他们的web界面和账户。
下面列出了最重要的远程仓库命令,以及一些可能还没有意义的技术描述。
-
git clone [remote-repo-URL]: 在本地计算机上复制指定的存储库。并创建一个工作目录,其中的文件的排列与下载存储库中的最新快照完全相同。同时记录用于后续网络数据传输的远程存储库的URL,并为其提供特殊的远程报告名称“origin”。
-
git remote add [remote-repo-name] [remote-repo-URL] :记录网络数据传输的新位置。
-
git remote -v: 列出网络数据传输的所有位置。
-
git pull [remote-repo-name] master:获取remote-repo-name中文件的最新副本
-
git push [remote-repo-name] master:将文件的最新副本推送到remote-repo-name。
E. Git分支(高级Git)(选学)
到目前为止,我们介绍的每个命令都使用默认分支。这个分支通常称为主分支。但是,在某些情况下,您可能希望在代码中创建分支。
分支允许您同时追踪多个不同版本的工作。可以将分支看作是另一个维度。也许一个分支是选择使用链表的结果,而另一个分支是选择使用数组的结果。
创建分支的原因
下列是适合创建分支的情况。
-
您可能希望对现有代码进行重大更改(称为重构),但这会破坏项目的其他部分。但是你希望能够同时处理其他部分或者你有合作伙伴,你不想破坏他们的代码。
-
您想要开始项目的一个新部分,但是您还不确定您的更改是否能够工作并最终生成产品。
-
你正在和合作伙伴一起工作,不想把你现在的工作和他们的工作弄混,即使你想在将来把你的工作合并在一起。
分支将使您能够追踪代码的多个不同版本,并且一旦您完成了一个部分的工作并希望它连接到代码的其余部分,您就可以很容易地在版本之间切换并将分支合并在一起。
一个示例场景
例如,假设到目前为止,您已经完成了项目的一半。还有一个困难的部分要做,你不知道如何去做。也许你有三个不同的想法来做这件事,但你不确定哪一个是有效的。在这一点上,它可能是一个好主意,创建一个master分支并尝试您的第一个想法。
-
如果您的代码可以工作,您可以将分支合并回您的主代码(master分支上)并commit您的项目。
-
如果您的代码不能工作,不要担心代码的恢复和对Git历史的操作。您可以简单地切换回master,它不会有您的任何更改,然后创建另一个分支,并尝试您的第二个想法。
这种情况会一直持续下去,直到您找到编写代码的最佳方法,并且最终只需要将工作的分支合并回master中。
Git参考中有一节是关于分支和合并的,其中有一些关于分支如何在Git的底层数据结构中表示的图。事实证明,Git以带有分支指针的图的形式追踪commit历史,并在图中以节点的形式commit。(因此有了与树相关的术语。)
创建、删除和切换分支
一个名为HEAD的特殊分支指针引用当前的分支作为工作目录。分支指令修改分支并更改您的HEAD指向的位置,以便您可以看到文件的不同版本。
-
下面的命令将创建当前分支的分支。
$ git branch [new-branch-name]
-
该命令允许您通过更改HEAD指针引用的分支来从一个分支切换到另一个分支。
$ git checkout [destination-branch]
默认情况下,您的初始分支称为master。建议你坚持这项公约。但是,其他任何分支都可以随意命名。通常将分支称为一些描述性的东西(如fixing-ai-heuristicsr)是一个好主意,这样您就可以记住它包含了哪些commit。 -
你可以结合前两个命令创建一个新的分支,然后检查它与这个单一的命令:
$ git checkout -b [new-branch-name]
-
你可以用这条命令删除分支
$ git branch -d [branch-to-delete]
-
你可以很容易地找出你在哪个分支与此命令:
$ git branch -v
更特别的是,-v命令还将列出每个分支上的最后一次commit。
合并
经常需要将一个分支合并到另一个分支中。例如,假设您喜欢在fixing-ai-heuristicsr里所做的工作。你的人工智能现在是最高级老板,你希望你的master分支看到你对fixing-ai-heuristicsr的commit,并删除fixing-ai-heuristicsr分支。
在这种情况下,您应该checkout master分支并将fixing-ai-heuristicsr合并到master分支中。
$ git checkout master
$ git merge fixing-ai-heuristics
这个master命令将创建一个新的commit,将两个分支连接在一起,并更改每个分支的指针来引用这个新的commit。虽然大多数commit只有一个父commit,但是这个新的合并commit有两个父commit。master分支上的commit称为它的第一个父分支,而fixing-ai-heuristicsr分支上的commit称为它的第二个父分支。
合并冲突
可能发生的情况是,您试图合并的两个分支有冲突的信息。如果两个分支更改了相同的文件,就会发生这种情况。Git足够复杂,可以解决许多更改,即使它们发生在相同的文件中(尽管位置不同)。
但是,有时候Git无法解决冲突,因为更改会影响相同的方法/代码行。在这些情况下,它会将来自不同分支的两个更改作为合并冲突呈现给您。
解决合并冲突
Git会告诉您哪些文件存在冲突。您需要打开有冲突的文件并手动解决它们。完成此操作后,您必须commit以完成两个分支的合并。
具有冲突的文件将包含如下片段:
<<<<<<< HEAD
for (int i = 0; i < results.length; i++) {
println(results[i]);
println("FIX ME!");
}
=======
int[] final = int[results.length];
for (int i = 0; i < results.length - 1; i++) {
final[i] = results[i] + 1;
println(final[i]);
}
>>>>>>> fixing-ai-heuristics
基本上,你会看到两段类似的代码:
- 上面的代码片段来自您最初运行merge命令时签出的分支。它被称为HEAD,因为HEAD指针在合并时引用了这个分支。继续上面的示例,这段代码将来自master分支。
- 下面的代码片段来自您要合并到签出分支中的分支。这就是为什么它显示代码来自fixing-ai-heuristics。
基本上,您需要遍历所有标记的部分,并选择希望保留的代码片段。
在前面的例子中,我更喜欢下面这段代码,因为我刚刚修复了AI,而上面这段代码仍然打印“FIX ME!”因此,我将删除顶部部分以及多余的行来得到这个:
int[] final = int[results.length];
for (int i = 0; i < results.length - 1; i++) {
final[i] = results[i] + 1;
println(final[i]);
}
不严肃的注释: 我不知道这段代码是如何fixes AI heuristics的。不要把它用在你的项目上!我告诉你,这是没有用的。没用的!
(在开玩笑)
对用冲突解决标记划分的所有段执行此操作可以解决冲突。对所有冲突的文件执行此操作之后,就可以commit了。这将完成您的合并。
F. Git的其他特性
还有大量其他很酷的Git命令。不幸的是,我们需要继续讨论远程存储库。因此,这部分将只列出一些其他有趣的功能,鼓励你在自己的时间去探索:
(未翻译)
G.远程仓库(高级)
Private vs. Public Repos
默认情况下,GitHub上的存储库是公共的,而不是私有的。这意味着任何在互联网上的人都可以在公开回购中查看代码。对于所有的课堂作业,你都需要使用私有仓库。
你可以通过GitHub education申请教育折扣来获得免费的私人repos。Bitbucket也是GitHub的一个很好的替代品,因为它提供了无限的私有代码repos。
添加远程端
添加远程仓库意味着告诉git仓库的位置。您不必对您可以添加的每个仓库都具有读/写访问权限。实际上,在远程端中访问和修改文件将在后面讨论,并且这取决于是否添加了远程端。
如果您使用的是HTTP,远程URL类似于https://github.com/berkeley-cs61b/on.git;如果您使用的是SSH,远程URL类似于git@github.com:berkeley-cs61b/ on.git。
按照惯例,主远程的名称称为origin(用于原始远程仓库)。因此,下面两个命令中的任何一个都允许我将berkeley-cs61b/skeleton仓库添加为远程端。
$ git remote add origin https://github.com/berkeley-cs61b/skeleton.git
$ git remote add origin git@github.com:berkeley-cs61b/skeleton.git
添加远程端之后,所有其他命令都使用其关联的短名称(如origin)。
重命名、删除和列出远程操作
-
你可以使用以下命令重命名你的远程端:
$ git remote rename [old-name] [new-name]
-
如果你不再使用远程端,你也可以把它移除:
-
$ git remote rm [remote-name]
-
要查看您有哪些远程端,可以列出它们。v命令可以告诉您每个远程仓库的URL(而不仅仅是它的名称)。
$ git remote -v
你可以在Pro Git的书中读到更多关于使用远程仓库的内容。
克隆一个远程仓库
经常有带有代码的远程仓库,您希望将这些代码复制到自己的计算机上。在这些情况下,通过克隆远程仓库,你可以很容易地下载整个仓库与commit历史:
$ git clone [remote-url]
$ git clone [remote-url] [directory-name]
第一个命令将创建与远程仓库同名的目录。第二个命令允许您为复制的存储库指定不同的名称。
当您克隆一个远程存储库时,克隆的远程将通过名称origin与您的本地存储库关联。这是默认的,因为克隆的远程存储库是本地存储库的origin。
推送commit
您可能希望通过添加一些本地commit来更新远程repo的内容。你可以通过pushing你的commit:
$ git push [remote-name] [remote-branch]
请注意,您将从您的HEAD指针当前引用的分支推送到远程分支。例如,假设我克隆了一个repo,然后在master分支上做了一些更改。我可以给远程我的本地更改与此命令:
$ git push origin master
Fetching & Pulling Commits
还有一些时候,您希望从远程仓库获得一些新的当前不在本地repo上的commits。例如,您可能克隆了一个伙伴创建的远程,并希望获得他/她的最新更改。您可以通过从远程获取或提取来获得这些更改。
- fetch: 这类似于下载commits。它不会将它们合并到您自己的代码中。
$ git fetch [remote-name]
你为什么要用这个?您的合作伙伴可能已经创建了一些您想要查看的更改,但是这些更改与您自己的代码是分开的。获取更改只会下载远程代码到本地副本,而不会将更改合并进您自己的代码中。
对于更具体的示例,假设您的合作伙伴在远程上创建了一个名为fixing-ai-heuristics的新分支。你可以通过以下步骤查看该分支的commit:
$ git fetch origin
$ git branch review-ai-fix origin/fixing-ai-heuristics
$ git checkout review-ai-fix
第二个命令创建一个名为review-ai-fix的新分支,它追踪origin远程仓库上的fixing-ai-heuristics分支。 - pull:这相当于fetch + merge。它不仅可以获取最近的更改,还可以将更改合并到您的HEAD分支中。
$ git pull [remote-name] [remote-branch-name]
假设我的boss合作伙伴将一些commits push给了我们共享的远程的master分支,从而修正了我们的AI启发(fix our AI heuristics,即之前的名字fixing-ai-heuristics)。我碰巧知道它不会破坏我的代码,所以我可以把它拉过来,并立即把它合并到我自己的代码中。
$ git pull origin master
H. Remote Repository Exercise
对于下面的示例,您将需要一个合作伙伴。您将与您的合作伙伴在远程仓库中工作,并且必须处理诸如合并冲突之类的事情。还要注意的是,你们两个都需要同一个服务的账户,不管是GitHub还是Bitbucket。
-
合作伙伴1将在GitHub或Bitbucket上创建一个私有存储库,并以合作者的身份添加合作伙伴2。这个repo可以称为learning-git。
-
合作伙伴2将创建一个README文件,commit该文件,并将此commit推到learing -git远程。
-
合作伙伴2还将添加合作伙伴1创建的远程,并push这个新的commit。
使用GitHub或Bitbucket,您可以在repo的主页上找到远程URL。
-
合作伙伴1现在将把远程repo克隆到自己的机器上,然后在README的底部添加一行代码。(注意:在这一点上,图片可能会有点混乱,因为我假装是双方的合作伙伴。)
-
合作伙伴1将commit此更改并将其push回到远程仓库。
-
合作伙伴2将类似地在自述底部添加一行,并commit此更改。
-
伙伴2现在将pull并发现存在合并冲突。
-
合作伙伴2应该通过重新安排行来解决合并冲突。然后伙伴2应该添加README并commit和push。
-
伙伴1现在可以pull出并获得两个新的commits -新添加的行与合并commit。现在两个合作伙伴的本地仓库都是最新的。