前言:
教程来源于Udacity的免费教程-用Git进行版本控制,这里仅是个人的笔记,参考使用。
git tag:给特定提交(commit)添加额外的标签,额外的提示信息。
git branch:用于创建分支,以此来并行开发项目的不同功能 。
git checkout:在不同的tags和branches之间进行切换。
git merge:合并分支。将不同分支上的修改自动合并。
目前所处的位置
你可以在任一项目中执行这些步骤,我将在 new-git-project 项目中执行。
我们来看看该项目到目前为止的 git log 输出结果:
以上是已经提交的信息。
git tag 命令
注意所显示的结果(只需注意 SHA 和 commit 消息)
我们将使用 git tag 命令与仓库的标签进行交互:
$ git tag -a v1.0
上述命令将打开代码编辑器,并等待你为标签输入信息。输入"Ready for content"怎么样?
注意:在上述命令 (git tag -a v1.0) 中,使用了 -a 选项。该选项告诉 git 创建一个带注释的标签。如果你没有提供该选项(即 git tag v1.0),那么它将创建一个轻量级标签。
建议使用带注释的标签,因为它们包含了大量的额外信息,例如:
标签创建者
标签创建日期
标签消息
因此,你应该始终使用带注释的标签。
默认是给最近一次commit添加标签。
验证标签
保存并退出编辑器后,命令行上什么也不会显示。那么如何知道已经向项目中添加了标签呢?只需输入 git tag,命令行会显示仓库中的所有标签。
我们已经验证了该标签位于仓库中,但是我们想知道它位于仓库的哪个位置。为此,我们需要调用一直在使用的 git log!
git log 的 --decorate 选项
正如你所了解的,git log 是一个非常强大的工具,可以让我们查看仓库的 commit。我们已经学习了几个选项,现在该学习一个新的选项了。--decorate 选项将显示默认视图隐藏起来的一些详情。
马上去试试运行 git log --decorate 吧!
💡 Git 2.13 中 --decorate 选项的变化 💡
在 2.13 版 git 中,log 命令已改为自动启用 --decorate 选项。这意味着,你不需要在命令中包含 --decorate 选项,因为它已经自动包含了!因此下面的命令输出结果完全一样:
git log --decorate git log
标签信息位于第一行的末尾
HEAD -> master?
你是否注意到,日志中除了显示标签信息之外,--decorate 还显示了 HEAD -> master?这是关于分支的信息!接下来我们将了解 git 中的分支。
删除标签
如果将标签消息中的某个字打错了,或标签名称打错了(输入 v0.1,而不是 v1.0),如何修正这个错误?最简单的方法是删除这个标签并重新创建。
可以通过输入 -d 选项 (表示 delete 删除!)加上标签名称来删除 git 标签:
$ git tag -d v1.0
向以前的 commit 添加标签
运行 git tag -a v1.0 将为最近的 commit 添加标签。但是如果你想向仓库中很久之前的 Commit 添加标签呢?
只需提供要添加标签的 commit 的 SHA 即可!
$ git tag -a v1.0 a87984
向以前的 commit 添加标签
运行 git tag -a v1.0 将为最近的 commit 添加标签。但是如果你想向仓库中很久之前的 Commit 添加标签呢?
只需提供要添加标签的 commit 的 SHA 即可!
$ git tag -a v1.0 a87984
(在弹出代码编辑器以便让你提供标签消息之后)此命令将向 SHA 为 a87084 的 commit 添加标签 v1.0。借助这一技巧,你可以为整个 git 仓库中的任何 commit 添加标签!很强大吧?并且只需在你已经知道的 git 标签命令中加上 commit 的 SHA 即可。
git tag 小结
总结下,git tag 命令用来标记特定的 commit 。当添加新的 commit 时,标签不会移动。
$ git tag -a beta
此命令将:
向最近的 commit 添加标签
如果提供了 SHA,则向具体的 commit 添加标签
分支
Git有个默认的分支叫master,我们提交commit一次,master就会移动到最近的commit上,像游标一样,而上面提到过的
tag则相当于一个里程碑,是永久不变的,默认分支是master,我们可以通过命令进行切换:
git checkout xxxxx
切换到不同的分支,并进行commit提交后,只会在当前的分支下能够显示,比如我在分支branch1下提交了A,那么我切换回
到master分支,我是看不到A的commit的。
我们可以在指定的commit上创建分支,并针对该分支做其它的功能开发。
git branch 命令
git branch 命令用来与 git 的分支进行交互:
$ git branch
它可以用来:
列出仓库中的所有分支名称
创建新的分支
删除分支
如果我们只输入 git branch,则 git 将列出仓库中的分支:
创建分支
要创建分支,只需使用 git branch 并提供要创建的分支对应的名称。因此,如果你想创建一个叫做"sidebar"的分支,只需运行以下命令:
$ git branch sidebar
git checkout 命令
注意,在进行 commit 时,该 commit 将添加到当前分支上。虽然我们创建了新的 sidebar 分支,但是没有向其添加新的 commit,因为我们尚未切换到该分支。如果我们现在进行 commit 的话,该 commit 将添加到 master 分支,而不是 sidebar 分支。我们已经在演示中看到这一情况,要在分支之间进行切换,我们需要使用 git 的 checkout 命令。
$ git checkout sidebar
请务必了解该命令的工作方式。运行该命令将:
从工作目录中删除 git 跟踪的所有文件和目录
(git 跟踪的文件存储在仓库中,因此什么也不会丢失)
转到仓库,并提取分支指向的 commit 所对应的所有文件和目录
因此此命令将删除 master 分支中的 commit 引用的所有文件。它会将这些文件替换为 sidebar 分支中的 commit 引用的文件。理解这一部分十分重要,所以请务必多读几遍工作方式。
在上述输出中,注意我们之前见到的特殊指示符"HEAD"具有一个指向 sidebar 分支的箭头。它指向 sidebar 是因为 sidebar 分支是当前分支,现在提交的任何 commit 将添加到 sidebar 分支
活跃分支
提示符将显示活跃分支。但这是我们对提示符进行的特殊自定义,如果你使用的是不同的计算机,判断活跃分支的最快速方式是查看 git branch 命令的输出结果。活跃分支名称旁边会显示一个星号。
删除分支
分支用来进行开发或对项目进行修正,不会影响到项目(因为更改是在分支上进行的)。在分支上做出更改后,你可以将该分支组合到 master 分支上(这种“分支组合过程”叫做“合并”(merge),稍后将详细讲解)
合并了分支的更改后,你可能不再需要该分支了。如果你想删除分支,可以使用 -d 选项。下面的命令包含 -d 选项,告诉 git 删掉给出的分支(这里是"sidebar"分支)。
$ git branch -d sidebar
注意,无法删除当前所在的分支。因此要删除 sidebar 分支,你需要切换到 master 分支,或者创建并切换到新的分支。
删除内容让人比较紧张。但是不用担心。如果某个分支上有任何其他分支上都没有包含的 commit(也就是这个 commit 是要被删除的分支独有的),git 不会删除该分支。如果你创建了 sidebar 分支,向其添加了 commit,然后尝试使用 git branch -d sidebar 删除该分支,git 不会让你删除该分支,因为你无法删除当前所在的分支。如果你切换到 master 分支并尝试删除 sidebar 分支,git 也不会让你删除,因为 sidebar 分支上的新 commit 会丢失!要强制删除,你需要使用大写的 D 选项 - git branch -D sidebar。
git branch 小结
总结下,git branch 命令用来管理 git 中的分支:
列出所有分支
$ git branch
创建新的"footer-fix"分支
$ git branch footer-fix
删除"footer-fix"分支
$ git branch -d footer-fix
此命令用来:
列出本地分支
创建新的分支
删除分支
高效分支
你已经学会了如何创建、列出和删除分支,我们来运用下所学的知识吧!
首先,确保我们保持相同的进度,并拥有相同的起始代码。我们将在 new-git-project 项目中进行操作。该项目具有以下文件:
index.html
css/app.css(空文件)
js/app.js(空文件)
CSS 和 JavaScript 文件是空的。确保 index 文件具有以下内容:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blog Project</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<header>
<h1>Expedition</h1>
</header>
<div class="container">
<main>
</main>
</div>
<footer>
Made with ♥ @ Udacity
</footer>
<script src="js/app.js"></script>
</body>
</html>
(这里我建议是重新建立一个repo,然后跟着操作一步一步来,之前的repo我做了太多的操作,比较乱)
完成图中所必要的每一步操作。
策略
现在,所有代码都位于 master 分支(默认分支)上。我们通过以下操作利用分支进行工作:
向分支中添加内容
创建新的分支
在分支之间切换
让我们使用分支完成以下更改:
在 master 分支上 - 向页面添加默认颜色
创建一个 sidebar 分支 - 为页面创建侧栏
在 master 分支上 - 更改页面的标题
在 sidebar 分支上 - 向侧栏中添加更多内容
创建一个 footer 分支 - 向脚注中添加社交链接
更改 1 - 添加页面颜色
确保位于 master 分支上,并向 css/app.css 添加以下内容:
body {
background-color: #00cae4;
}
保存文件,然后将该文件添加到暂存区,并将其 commit 到仓库。
更改 2 - 添加侧栏
我们向页面上添加一个侧栏。但是假定我们不确定是否喜欢新的背景色。因此我们要将 sidebar 分支放在设置页面颜色的 commit 之前。你的 SHA 可能会不一样,但对我来说,在添加颜色的 commit 之前的 commit 具有 SHA 759e74e(已经改为我的分支SHA,即最新的一次commit,我们创建分支,然后在分去上添加侧栏的代码!!!)。因此向该 commit 添加分支的命令是:
$ git branch sidebar 759e74e
现在使用 git checkout 命令切换到新的 sidebar 分支。运行 git log --oneline --decorate 显示以下结果:
通过向 HTML 文件添加以下 <aside> 代码添加一个侧栏:
<div class="container">
<main>
</main>
</div>
<!-- start of new content -->
<aside>
<h2>About Me</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, debitis earum molestias veniam suscipit aliquam totam exercitationem tempore neque vitae. Minima, corporis pariatur facere at quo porro beatae similique! Odit.</p>
</aside>
<!-- end of new content -->
<footer>
Made with ♥ @ Udacity
</footer>
我将 <aside> 内容添加到 <main> 元素旁边,作为 <div class="container"> 元素的子级。
(不要直接copy上面的内容复制到index.html中,复制中间的内容到
指定的位置)
你可以在 <aside> 元素中添加任何内容!
更改 3 - 更改 master 上的标题
切换到 master 分支并更新页面标题。
使用 git checkout 命令切换到 master 分支。(注意,新的侧栏的 HTML 不在了!因为所有代码都妥善地保存在 sidebar 分支上。)
*****!!!切换到master以后,查看index.html后发现中间的内容不见了,因为切换分支后,commit也会切换到当前分支下的,这里我做了好几次操作,我一直以为是我没有保存commit成功或是notepad++的锅......
现在将页面的 <h1> 标题从"Expedition"改为其他内容。要不改为吸引人的"Adventure"?!
更改 4 - 向侧栏中添加更多内容
切换到 sidebar 分支(注意,我们添加到 master 分支的内容在 sidebar 分支上不可见)。
现在,在 <aside> 元素中添加一些内容。添加一些个人信息,例如你喜欢的电影或书籍(我最喜欢的是《指环王》!)。任何内容都可以,只需添加一些内容。
同样确保不要更改 CSS 文件。
保存 index.html 文件并提交 commit。
更改 5 - 向脚注中添加社交链接
我们已经做出了好几个更改,下面要进行最后一项更改了。我们向页面脚注中添加一些社交图标。为了加以区分,我们在基于 master 分支的新 footer 分支上做出这一更改,因此需要先创建一个新的 footer 分支。
但是你知道吗?git checkout 命令也可以创建一个新的分支。如果你添加 -b 选项,则能够用一个命令创建分支并切换到该分支。
我们使用新的 git checkout 命令来创建新的 footer 分支并让此 footer 分支的起点位置与 master 分支的一样:
$ git checkout -b footer master
(即基于master来创建一个分支,并切换到新的分支)
添加社交链接
现在我们已经位于新分支上,我们向页面脚注中添加一些社交链接。我添加了以下内容:
<footer>
<!-- start of new content -->
<section>
<h3 class="visuallyhidden">Social Links</h3>
<a class="social-link" href="https://twitter.com/udacity">
<img src="img/social-twitter.png" alt="Twitter">
</a>
<a class="social-link" href="https://www.instagram.com/udacity/">
<img src="img/social-instagram.png" alt="Instagram">
</a>
<a class="social-link" href="https://plus.google.com/+Udacity">
<img src="img/social-google.png" alt="Google Plus">
</a>
</section>
<!-- end of new content -->
</footer>
你也可以添加你自己的社交帐户链接。
即打开index.html,替换到<footer>..</footer>中间的内容。这时候master,sidebar,footer三个分支均产生了冲突!
同时查看所有分支
我们已经做出了所有需要做出的更改!很棒!
我们已经在三个不同的分支上进行了多项更改。我们在 git log 输出结果中看不到其他分支,触发切换到某个分支。如果能在 git log 输出结果中看到所有分支,是不是很棒?
你到现在为止已经知道,git log 命令非常强大,可以显示此信息。我们将使用新的 --graph 和 --all 选项:
$ git log --oneline --decorate --graph --all
--graph 选项将条目和行添加到输出的最左侧。显示了实际的分支。--all 选项会显示仓库中的所有分支。
运行此命令将显示仓库中的所有分支和 commit:
HEAD当前指向的是活跃分支,目前在footer分支上。
更改小结
我们做出了以下更改:
我们在 master 分支上向页面添加了默认颜色
我们创建了 sidebar 分支并为侧栏添加了代码
我们在 master 分支上更改了页面的标题
我们在 sidebar 分支上向侧栏添加了更多内容
我们创建了 footer 分支并向脚注中添加了社交链接
这些更改都发生在不同的分支上。让我们用 git 合并所有这些更改吧。将分支组合到一起称为合并(merge)。
合并
注意,主题分支(例如 sidebar)的作用是让你做出不影响 master 分支的更改。当你在主题分支上做出更改后,如果觉得不想要该分支上的更改,则可以删掉该分支,或者你决定要保留更改,则可以将该分支上的更改与其他分支上的更改合并。
将分支组合到一起称为合并。
注意 git 中的两种合并:普通合并和快进合并。
快速合并即是MASTER和SOCIAL_LINKS有相同的commit提交,但SOCIAL_LINKS比MASTER分支靠前,SOCIAL_LINKS中有MASTER中未包含的commit提交,如果我们想将这些提交并纳入到MASTER分支中,就需要将SOCIAL_LINKS合并到MASTER中,因为HEAD指向提是MASTER,所以合并后,MASTER分支将会向前移动到SOCIAL_LINKS分支的位置。
普通合并即两条不同的分支进行合并。
⚠️ 了解分支 ⚠️
当你要合并分支时,务必知道当前位于哪个分支上。注意,合并分支会提交 commit。
现在我们不知道如何撤消更改。下节课将介绍这一技巧,但是如果你在错误的分支上进行了合并,可以使用以下命令撤消合并:
git reset --hard HEAD^
(确保包含 ^ 字符!它属于“相对 commit 引用”并表示“父 级 commit”。我们将在下节课学习相对 commit 引用。)
合并指令
git merge 指令用来合并 git 分支:
$ git merge <name-of-branch-to-merge-in>
发生合并时,git 将:
查看将合并的分支
查看分支的历史记录并寻找两个分支的 commit 历史记录中都有的单个 commit
将单个分支上更改的代码行合并到一起
提交一个 commit 来记录合并操作
当我们合并时,我们将其他分支合并到当前(检出的)分支上。我们不是将两个分支合并到一个新的分支上。也不是将当前分支合并到其他分支上。
如我当前的分支在master上,则合并即是合并到master上,合并到当前checkout的分支上。
因为 footer 直接在 master 前面,因此这种合并最简单。将 footer 合并到 master 中将导致快进合并(Fast-forward merge)。快进合并将使当前检出的分支向前移动,直到它指向与另一个分支(这里是 footer)指向的 commit 一样为止。
要合并 footer 分支,运行:
$ git merge footer
进行普通合并
终于完成快进合并流程了!也没那么难,对吧?
但是你可能会说“当然简单了,所有 commit 都已经在那,分支指针只是向前移动了!”…说的对,这是最简单的合并。
现在我们将进行更常见的合并,其中两个分支完全不一样。你会惊讶地发现,实际合并 sidebar 这样的独特分支,操作是完全一样的!
要合并 sidebar 分支,确保你位于 master 分支上,并运行:
$ git merge sidebar
因为合并的是两个完全不一样的分支,因此将提交 commit。在进行 commit 时,需要提供 commit 消息。因为这是合并 commit,因此已经提供了默认消息。你也可以更改消息,但通常都会直接使用默认的合并 commit 消息。因此当你的代码编辑器打开并包含该消息时,直接关闭编辑器以确认使用该 commit 消息。
在使用默认 commit 消息后,我的终端如下所示:
(注意:vim的退出要按ESC,然后输入:wq)
如果合并失败了呢?
我们刚刚执行的合并能够成功合并。git 能够灵活地合并不同分支上的大量工作。但是,有时候也无法合并分支。如果在进行合并时失败了,则称为合并冲突(merge conflict)。我们将在下节课了解合并冲突及其背后的原因,并学习如何解决冲突。
合并小结
总结下,git merge 命令用来在 git 中合并分支:
$ git merge <other-branch>
合并有以下两种类型:
快进合并 – 要合并的分支必须位于检出分支前面。检出分支的指针将向前移动,指向另一分支所指向的同一 commit。
普通类型的合并
两个完全不同的分支被合并
创建一个合并 commit
合并冲突
有时候合并会失败
大部分情况下,git 将能够成功地合并分支。但是,有时候 git 无法完全自动地进行合并。合并失败时,就称为合并冲突。
如果出现合并冲突,git 将尝试尽可能合并多的内容,然后将留下特殊选项(例如 >>> 和 <<<),告诉你(没错,告诉作为程序员的你!)需要从何处手动修复。
什么导致了合并冲突
正如你所知道的,git 会跟踪文件中的代码行。如果完全相同的行在不同的文件中更改了,将产生合并冲突。例如,如果你在 alternate-sidebar-style 分支上并将侧栏的标题改为"Information About Me",git 应该选择哪个标题?你在两个分支上都更改了标题,因此 git 根本不知道你要保留哪个标题。它肯定不会随机选择一个标题!
我们来人为制造一个合并冲突,学习如何解决这种冲突。当你学会了后,就非常简单!当 git 不确定你要使用即将合并的分支中的哪些行时,就会出现合并冲突。因此我们需要在两个不同的分支上修改同一行,然后重设合并它们。
人为制造合并冲突!
当同一行在两个分支中都更改了时,就会出现合并冲突。我们在两个不同的分支上更改同一页面的标题:
更改 master 分支上的标题
在最近修改 master 分支的 commit 前面创建一个 heading-update 分支
更改同一标题
切换到 master 分支
合并 heading-update 分支
在分支上更改标题 1
因为 master 分支就像所有其他分支一样,也是个普通分支,我们在 master 分支上更改标题。将 <h1> 标题改为其他内容。对我来说,标题当前在第 13 行是"Adventure",我将其改为"Quest"。
做出更改后,保存文件并 commit 到仓库。
在分支上更改标题 2
现在我们需要创建不同的分支并在该分支上更新标题。
现在要注意的是,我们需要创建一个不是从 master 分支上分叉的分支。如果我们在从 master 分支上分叉的分支上做出更改,那么该更改将在此更改前面,git 将直接使用该更改,而不是使用我们刚刚在 master 上做出的更改。因此我们需要将该分支“放在过去”。
(如果我在master上创建了一个分支,那么新的分支是在master前面的,这时候我commit,也都是在master之前,合并的时候,快进合并,将直接使用新的分支commit,所以我们需要在最近的commit之前的某个commit上,创建一个分支,并修改标题,这时候合并分支到master,便会出现合并冲突的现象!
我们创建一个位于最近 commit 之前的 commit 上的分支。使用 git log 获取上一个 commit 的 SHA,并在该 commit 上创建一个分支。在创建 heading-update 分支后,我的 git log 输出结果如下所示:
现在已经创建好分支heading_update,我们只需再次在该分支上更新标题。确保更改在 master 分支上做出更改的同一行。我将第 13 行的"Adventure"改为"Crusade"。
然后保存文件,并将文件 commit 到仓库
下面我们要进行合并分支,将分支heading_update合并到master,所以我们必须首先要切换到master分支下
git checkout master
确保你位于 master 分支(我们也可以在另一分支上操作,但是我习惯将 master 分支作为主分支,让另一个分支合并到该分支上)上,并合并 heading-update 分支:
$ git merge heading-update
你应该看到以下结果:
合并冲突输出结果解释
终端中显示的输出结果为:
$ git merge heading-update
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
注意在 git merge heading-update 命令之后,git 尝试合并在两个分支上都更改了的文件 (index.html),但是出现冲突。此外,它告诉你发生了什么:"Automatic merge failed; fix conflicts and then commit the result"。
还记得 git status 命令吗?在处理合并冲突时,该命令将非常有用。
git status 的输出结果告诉我们 index.html 中存在合并冲突。因此在代码编辑器中查看该文件!
合并冲突指示符解释
编辑器具有以下合并冲突指示符:
1.<<<<<<< HEAD 此行下方的所有内容(直到下个指示符)显示了当前分支上的行
2.||||||| merged common ancestors 此行下方的所有内容(直到下个指示符)显示了原始行的内容
3.======= 表示原始行内容的结束位置,之后的所有行(直到下个指示符)是被合并的当前分支上的行的内容
4.>>>>>>> heading-update 是要被合并的分支(此例中是 heading-update 分支)上的行结束指示符
解决合并冲突
git 使用合并冲突指示符来告诉你两个不同分支上的哪些行导致了合并冲突,以及原始行是什么。要解决合并冲突,你需要:
选择保留哪些行
删掉所有带指示符的行
因为某种原因,我不太喜欢现在的"Crusade"一词,但是"Quest"也不太合适。要不将标题设为"Adventurous Quest"?
修改为:
删掉所有包含合并冲突指示符的行并选择保留哪个标题后,直接保存文件,并将其添加到暂存区,然后 commit!就像普通合并一样,代码编辑器会弹出,并让你提供 commit 消息。和之前一样,我们经常会使用自动生成的合并 commit 消息,因此在编辑器打开后,直接关闭编辑器并使用自动生成的 commit 消息。
就这些内容!当你明白合并指示符所显示的内容后,合并冲突就没那么复杂了。
合并冲突小结
当相同的行在要合并的不同分支上做出了更改时,就会出现合并冲突。git 将在合并途中暂停,并告诉你存在冲突,以及哪些文件存在冲突。要解决文件中的冲突:
找到并删掉存在合并冲突指示符的所有行
决定保留哪些行
保存文件
暂存文件
提交 commit
注意一个文件可能在多个部分存在合并冲突,因此检查整个文件中的合并冲突指示符,搜索 <<< 能够帮助你找到所有这些指示符。