diff --git "a/sources/2.3-\344\277\235\345\255\230\344\275\240\347\232\204\346\233\264\346\224\271.md" "b/sources/2.3-\344\277\235\345\255\230\344\275\240\347\232\204\346\233\264\346\224\271.md" index 83408b0..90efcb9 100644 --- "a/sources/2.3-\344\277\235\345\255\230\344\275\240\347\232\204\346\233\264\346\224\271.md" +++ "b/sources/2.3-\344\277\235\345\255\230\344\275\240\347\232\204\346\233\264\346\224\271.md" @@ -1,158 +1,28 @@ - +# 保存你的更改 ![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) -***git add / git commit / git diff / git stash / .gitignore*** +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** -> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 10 月 26 日(部分章节未更新) +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 1 日 > > ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 -“保存”这个概念在 Git 等版本控制系统和 Word 等文本编辑应用中不太一样。传统软件里的“保存”在 Git 里被叫做“提交”(commit)。 我们常说的的保存可以理解成在文件系统中覆盖一个已有的文件或者创建一个新的文件。而在 Git 中,提交这个操作作用于若干个文件和目录。 - -在 Git 和 SVN 里保存更改也不一样。SVN 提交或检入(check-in)将会推送到远端的中央服务器。也就是说 SVN 的提交需要联网才能完全“保存”项目更改。Git 提交可以在本地完成,然后再使用`git push -u origin master`命令推送到远端服务器。这两种方法的区别体现了两种架构设计的本质区别。Git 是一个分布式的应用,而 SVN 是一个中心化的应用。分布式应用一般来说更可靠,因为它们不存在中央服务器这样的单点故障。 - -`git add`、`git status`和`git commit`这三个命令通常一起使用,将 Git 项目当前的状态保存成一份快照。 - -Git 还有另一个保存机制:“储藏”(stash)。储藏是一个临时的储存区域,保存还没准备好提交的更改。储藏操作作用于工作目录,三个文件树中的第一棵。它有很多用法,访问 git stash 页面了解更多。 - -Git 仓库可以通过设置忽略一些文件或目录。Git 将不会保存这些文件的任何更改。Git 有多种方式管理忽略文件列表。访问 git ignore 页面了解更多 Git 忽略文件设置。 - -## git add - -`git add` 命令将工作目录中的变化添加到暂存区。它告诉 Git 你想要在下一次提交时包含这个文件的更新。但是,`git add` 不会实质上地影响你的仓库——在你运行 `git commit` 前更改都还没有真正被记录。 - -使用这些命令的同时,你还需要 `git status` 来查看工作目录和暂存区的状态。 - -### 用法 - -``` -git add -``` - -将 `` 中的更改加入下次提交的缓存。 - -``` -git add -``` - -将 `` 下的更改加入下次提交的缓存。 - -``` -git add -i -``` - -开始交互式的缓存,你可以选择文件的一部分加入到下次提交缓存。它会向你展示一堆更改,等待你输入一个命令。`y` 将这块更改加入缓存,`n` 忽略这块更改,`s` 将它分割成更小的块,`e` 手动编辑这块更改,以及 `q` 退出。 - -### 讨论 - -`git add` 和 `git commit` 这两个命令组成了最基本的 Git 工作流。每一个 Git 用户都需要理解这两个命令,不管他们团队的协作模型是如何的。我有一千种方式可以将项目版本记录在仓库的历史中。 - -在一个只有编辑、缓存、提交这样基本流程的项目上开发。首先,你要在工作目录中编辑你的文件。当你准备备份项目的当前状态时,你通过 `git add` 来缓存更改。当你对缓存的快照满意之后,你通过 `git commit` 将它提交到你的项目历史中去。 - -![Git Tutorial: git add Snapshot](https://wac-cdn.atlassian.com/dam/jcr:0f27e004-f2f5-4890-921d-65fa77ba2774/01.svg) - -`git add` 命令不能和 `svn add` 混在一起理解,后者将文件添加到仓库中。而 `git add` 发生于更抽象的 *更改* 层面。也就是说,`git add` 在每次你修改一个文件时都需要被调用,而 `svn add` 只需要每个文件调用一次。这听上去很多余,但这样的工作流使得一个项目更容易组织。 - -#### 缓存区 - -缓存区是 Git 更为独特的地方之一,如果你是从 SVN(甚至是 Mercurial)迁移而来,那你可得花点时间理解了。你可以简单地把它想成是工作目录和项目历史之间的缓冲区。 - -缓存允许你在实际提交到项目历史之前,将相关的更改组合成一份高度专注的快照,而不是将你上次提交以后产生的所有更改一并提交。也就是说你可以更改各种不相关的文件,然后回过去将它们按逻辑切分,将相关的更改添加到缓存,一份一份提交。在任何修改控制系统中,很重要的一点是提交必须是原子性的,以便于追踪 bug,并用最小的代价回滚更改。 - -### 栗子 - -当你开始新项目的时候,`git add` 和 `svn import` 类似。为了创建当前目录的初始提交,使用下面两个命令: - -``` -git add . -git commit -``` - -当你项目设置好之后,新的文件可以通过路径传递给 `git add` 来添加: - -``` -git add hello.py -git commit -``` - -上面的命令同样可以用于记录已有文件的更改。重复一次,Git 不会区分缓存的更改来自新文件,还是仓库中已有的文件。 - -## git commit - -`git commit`命令将缓存的快照提交到项目历史。提交的快照可以认为是项目安全的版本,Git 永远不会改变它们,除非你这么要求。和 `git add` 一样,这是最重要的 Git 命令之一。 - -尽管和它和 `svn commit` 名字一样,但实际上它们毫无关联。快照被提交到本地仓库,不会和其他 Git 仓库有任何交互。 - -### 用法 - -``` -git commit -``` - -提交已经缓存的快照。它会运行文本编辑器,等待你输入提交信息。当你输入信息之后,保存文件,关闭编辑器,创建实际的提交。 - -``` -git commit -m "" -``` - -提交已经缓存的快照。但将 `` 作为提交信息,而不是运行文本编辑器。 - -``` -git commit -a -``` - -提交一份包含工作目录所有更改的快照。它只包含跟踪过的文件的更改(那些之前已经通过 `git add` 添加过的文件)。 - -### 讨论 - -快照总是提交到 *本地* 仓库。这一点和 SVN 截然不同,后者的工作拷贝提交到中央仓库。而 Git 不会强制你和中央仓库进行交互,直到你准备好了。就像缓存区是工作目录和项目历史之间的缓冲地带,每个开发者的本地仓库是他们贡献的代码和中央仓库之间的缓冲地带。 - -这一点改变了 Git 用户基本的开发模型。Git 开发者可以在本地仓库中积累一些提交,而不是一发生更改就直接提交到中央仓库。这对于 SVN 风格的协作有着诸多优点:更容易将功能切分成原子性的提交,让相关的提交组合在一起,发布到中央仓库之前整理好本地的历史。开发者得以在一个隔离的环境中工作,直到他们方便的时候再整合代码。 - -#### 记录快照,而不是记录差异 - -SVN 和 Git 除了使用上存在巨大差异,它们底层的实现同样遵循截然不同的设计哲学。SVN 追踪文件的 *变化* ,而 Git 的版本控制模型基于 *快照* 。比如说,一个 SVN 提交由仓库中原文件相比的差异(diff)组成。而 Git 在每次提交中记录文件的 *完整内容* 。 - -![Git Tutorial: Snapshots, Not Differences](https://www.atlassian.com/dam/jcr:7406fe56-d36d-44cf-92e3-b28e4bae36f8/02.svg) - -这让很多 Git 操作比 SVN 来的快得多,因为文件的某个版本不需要通过版本间的差异组装得到——每个文件完整的修改能立刻从 Git 的内部数据库中得到。 - -Git 的快照模型对它版本控制模型的方方面面都有着深远的影响,从分支到合并工具,再到协作工作流,以至于影响了所有特性。 - -### 栗子 - -下面这个栗子假设你编辑了 `hello.py` 文件的一些内容,并且准备好将它提交到项目历史。首先,你需要用 `git add` 缓存文件,然后提交缓存的快照。 - -``` -git add hello.py -git commit -``` +在 Git 等版本控制系统中,“保存”这个概念与我们熟悉的 Word 保存不太一样。传统软件里的“保存”对应的是 Git 里的“提交”(commit)。我们以前说的保存可以理解成是在文件系统中覆盖**一个**已有的文件或者创建**一个**新的文件。而在 Git 中,提交的操作对象可以是多个文件和目录。 -它会打开一个文件编辑器(可以通过 `git config` 设置) 询问提交信息,同时列出将被提交的文件。 +Git 和 SVN 里保存更改的流程也有所区别。SVN 提交或检入(check-in)会将更新的内容推送到远端的中央服务器。也就是说,SVN 需要联网才能成功“保存”项目更改。Git 提交可以在本地完成,然后再通过`git push -u origin master`命令推送到远端服务器。这两种方法的差异体现了两种完全不同的架构设计。Git 是一个分布式的应用,而 SVN 是一个中心化的应用。一般来说分布式应用更可靠,因为中心化的服务器可能会导致单点故障。 -``` -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# On branch master -# Changes to be committed: -# (use "git reset HEAD ..." to unstage) -# -#modified: hello.py -``` +`git add`、`git status`和`git commit`这三个命令通常一同使用,来将 Git 项目当前的状态保存成一份快照。 -Git 对提交信息没有特定的格式限制,但约定俗成的格式是:在第一行用 50 个以内的字符总结这个提交,留一空行,然后详细阐述具体的更改。比如: +Git 还有另一个保存机制:“储藏”(stash)。储藏是一个临时的储存区域,仅仅存放尚未准备好提交的更改。储藏的作用对象是工作目录,也就是 Git 三个树状文件结构的第一个。它有很多用法,请访问 git stash 页面了解更多。 -``` -Change the message displayed by hello.py +你可以通过设置在保存时跳过 Git 仓库里一些文件或目录。Git 将不会保存这些文件的任何更改。Git 有多种方式可以管理忽略文件列表。请访问 git ignore 页面了解更多。 -- Update the sayHello() function to output the user's name -- Change the sayGoodbye() function to a friendlier message -``` +## 总结 -注意,很多开发者倾向于在提交信息中使用一般现在时态。这样看起来更像是对仓库进行的操作,让很多改写历史的操作更加符合直觉。 +当你想要让 Git 将当前的项目状态保存成快照添加到提交历史中时,`git add` 是你要用到的第一个命令。单独使用时,`git add` 会将工作目录中的更改添加到缓存区。`git status` 命令用于检查仓库的当前状态。它能够帮助你判断 `git add` 操作成功与否。``git reset` 命令用于撤销 `git add` 命令。`git commit` 命令会发送一份缓存区的快照到仓库的提交历史。 -> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 > > 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 > diff --git a/sources/2.3.1-git_add.md b/sources/2.3.1-git_add.md new file mode 100644 index 0000000..010617a --- /dev/null +++ b/sources/2.3.1-git_add.md @@ -0,0 +1,77 @@ +# Git add + +![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) + +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** + +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 1 日 +> +> ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 + +## git add + +`git add` 命令将工作目录中的变化添加到缓存区。它告诉 Git 你希望下一个提交中包含这个文件的更新。不过,`git add` 不会实际上并不不会改变你的仓库——直到你运行 `git commit` 前更改都还不会真正被记录。 + +使用这些命令的同时,你还需要 `git status` 来查看工作目录和缓存区的状态。 + +## 工作原理 + +`git add` 和 `git commit` 这两个命令组成了最基本的 Git 工作流。每个 Git 用户都必须理解这两个命令,不管团队使用的是哪种协作模型。它们的作用是将项目的诸多版本记录到仓库历史中。 + +一个项目的编写离不开这个基本模式:编辑、缓存和提交。首先,你在工作目录中编辑你的文件。当你想要备份当前的项目状态时,你通过 `git add` 来缓存更改。当你觉得这个缓存的快照没问题了,你通过 `git commit` 将它提交到项目历史。`git reset` 命令用于撤销提交或缓存的快照。 + +除了 `git add` 和 `git commit` 之外,`git push` 也是完整的 Git 协作流程中的重要一环。`git push` 用于将提交的更改发送到远端仓库。操作完成后,其他团队成员也可以看到这些更改。 + +![Git Tutorial: git add Snapshot](https://wac-cdn.atlassian.com/dam/jcr:0f27e004-f2f5-4890-921d-65fa77ba2774/01.svg) + +`git add` 命令和 `svn add` 不同。后者直接将**文件**添加到仓库中,而 `git add` 作用于更抽象的**更改**(更低一层)。也就是说,哪怕是同一个文件时,每次修改时你都需要使用 `git add` ,而 `svn add` 只需要使用一次。这听上去多此一举,但实际上使得项目更容易管理。 + +## 缓存区 + +`git add` 命令最主要的作用是将工作目录中的更改添加到 Git 的缓存区。缓存区是 Git 特有的功能之一,如果你之前使用的是 SVN(甚至是更古老的 Mercurial),那你可得花点时间理解了。你可以把它理解成工作目录和项目历史中间的缓冲区。缓存区是 Git 三个树状文件结构之一,另外两个是工作目录和提交历史。 + +缓存允许你把密切相关的一些更改合并成一份快照,而不是直接提交所有新的更改。也就是说你可以同时进行多个无关的更改,最后将相关的更改添加到缓存区,分成几次分别提交。对于任何版本控制系统来说,保持提交的原子性非常重要,以便于追踪 bug 以及用最小的代价撤销更改。 + +## 用法 + +``` +git add <文件> +``` + +缓存 `<文件>` 中的更改,准备下次提交。 + +``` +git add <目录> +``` + +缓存 `<目录>` 下的所有更改,准备下次提交。 + +``` +git add -p +``` + +开始交互式的缓存,你可以将某个文件的其中一处更改加入到下次提交缓存。Git 会显示一堆更改,并等待用户命令。使用 `y` 缓存某一处更改,使用 `n` 忽略某一处更改,使用 `s` 将某一处分割成更小的几份,使用 `e` 手动编辑某一处更改,使用 `q` 退出编辑。 + +## 栗子 + +对于新项目来说,`git add` 和 `svn import` 的作用类似。使用下面两个命令为当前目录创建首个提交: + +``` +git add . +git commit +``` + +项目设置好之后,新的文件可以通过路径传递给 `git add` 来添加到缓存区: + +``` +git add hello.py +git commit +``` + +上面的命令同样可以用于记录已有文件的更改。重复一次,Git 不会区分被缓存的更改来自新文件,还是仓库中已有的文件。 + +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> +> 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 +> +> 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。 \ No newline at end of file diff --git a/sources/2.3.2-git_commit.md b/sources/2.3.2-git_commit.md new file mode 100644 index 0000000..ea35c04 --- /dev/null +++ b/sources/2.3.2-git_commit.md @@ -0,0 +1,89 @@ +# Git commit + +![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) + +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** + +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 5 日(上游未更新,根据旧版教程审校) +> +> ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 + +## git commit + +`git commit` 命令将缓存的快照提交到项目历史。提交的快照可以认为是项目安全的版本,Git 永远不会改变它们,除非你这么要求。和 `git add` 一样,这是最重要的 Git 命令之一。 + +尽管和它和 `svn commit` 名字一样,但实际上它们毫无关联。快照被提交到本地仓库,不会和其他 Git 仓库有任何交互。 + +## 工作原理 + +快照总是提交到 *本地* 仓库。这一点和 SVN 截然不同,后者的工作拷贝提交到中央仓库。而 Git 不会强制你和中央仓库进行交互,直到你准备好了。就像缓存区是工作目录和项目历史之间的缓冲地带,每个开发者的本地仓库是他们贡献的代码和中央仓库之间的缓冲地带。 + +这一点改变了 Git 用户基本的开发模型。Git 开发者可以在本地仓库中积累一些提交,而不是一发生更改就直接提交到中央仓库。这对于 SVN 风格的协作有着诸多优点:更容易将功能切分成原子性的提交,让相关的提交组合在一起,发布到中央仓库之前整理好本地的历史。开发者得以在一个隔离的环境中工作,直到他们方便的时候再整合代码。 + +#### 记录快照,而不是记录差异 + +SVN 和 Git 除了使用上存在巨大差异,它们底层的实现同样遵循截然不同的设计哲学。SVN 追踪文件的 *变化* ,而 Git 的版本控制模型基于 *快照* 。比如说,一个 SVN 提交由仓库中原文件相比的差异(diff)组成。而 Git 在每次提交中记录文件的 *完整内容* 。 + +![Git Tutorial: Snapshots, Not Differences](https://www.atlassian.com/dam/jcr:7406fe56-d36d-44cf-92e3-b28e4bae36f8/02.svg) + +这让很多 Git 操作比 SVN 来的快得多,因为文件的某个版本不需要通过版本间的差异组装得到——每个文件完整的修改能立刻从 Git 的内部数据库中得到。 + +Git 的快照模型对它版本控制模型的方方面面都有着深远的影响,从分支到合并工具,再到协作工作流,以至于影响了所有特性。 + +## 用法 + +``` +git commit +``` + +提交已经缓存的快照。它会运行文本编辑器,等待你输入提交信息。当你输入信息之后,保存文件,关闭编辑器,创建实际的提交。 + +``` +git commit -m "" +``` + +提交已经缓存的快照。但将 `` 作为提交信息,而不是运行文本编辑器。 + +``` +git commit -a +``` + +提交一份包含工作目录所有更改的快照。它只包含跟踪过的文件的更改(那些之前已经通过 `git add` 添加过的文件)。 + +## 栗子 + +下面这个栗子假设你编辑了 `hello.py` 文件的一些内容,并且准备好将它提交到项目历史。首先,你需要用 `git add` 缓存文件,然后提交缓存的快照。 + +``` +git add hello.py +git commit +``` + +它会打开一个文件编辑器(可以通过 `git config` 设置) 询问提交信息,同时列出将被提交的文件。 + +``` +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +#modified: hello.py +``` + +Git 对提交信息没有特定的格式限制,但约定俗成的格式是:在第一行用 50 个以内的字符总结这个提交,留一空行,然后详细阐述具体的更改。比如: + +``` +Change the message displayed by hello.py + +- Update the sayHello() function to output the user's name +- Change the sayGoodbye() function to a friendlier message +``` + +注意,很多开发者倾向于在提交信息中使用一般现在时态。这样看起来更像是对仓库进行的操作,让很多改写历史的操作更加符合直觉。 + +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> +> 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 +> +> 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。 \ No newline at end of file diff --git a/sources/2.3.3-git_diff.md b/sources/2.3.3-git_diff.md new file mode 100644 index 0000000..69afa9b --- /dev/null +++ b/sources/2.3.3-git_diff.md @@ -0,0 +1,237 @@ +# Git diff + +![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) + +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** + +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 11 日 +> +> ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 + +## 使用 git diff 对比更改 + +`diff` 命令读入两个数据集,并输出两者之间的更改。`git diff` 是一个用途广泛的 Git 命令,对 Git 中的数据进行 `diff` 差异比较。它接受的参数可以是提交、分支和文件等数据。本文将会介绍 `git diff` 的常见使用场景和差异差异比较的工作流。`git diff` 命令通常和 `git status`和 `git log` 一同使用来分析 Git 仓库当前的状态。 + +## 读懂 diff 命令的输出 + +### 原始输出 + +以下栗子都是在同一个仓库中执行的。使用以下命令来创建这个仓库: + +```shell +$:> mkdir diff_test_repo +$:> cd diff_test_repo +$:> touch diff_test.txt +$:> echo "this is a git diff test example" > diff_test.txt +$:> git init . +Initialized empty Git repository in /Users/kev/code/test/.git/ +$:> git add diff_test.txt +$:> git commit -am "add diff test file" +[master (root-commit) 6f77fc3] add diff test file +1 file changed, 1 insertion(+) +create mode 100644 diff_test.txt +``` + +如果我们现在执行 `git diff`,我们不会看到任何输出。这和我们的预期相符,因为目前仓库里还没有任何更改。当我们创建好仓库并缓存了 `diff_test.txt` 文件后,我们就可以通过修改这个文件来实验 diff 命令的输出: + +```shell +$:> echo "this is a diff example" > diff_test.txt +``` + +执行这个命令将会改变 `diff_test.txt` 文件的内容。修改后,你可以看到前后差异并分析输出。现在执行 `git diff` 命令将会产生下面的输出: + +```shell +diff --git a/diff_test.txt b/diff_test.txt +index 6b0c6cf..b37e70a 100644 +--- a/diff_test.txt ++++ b/diff_test.txt +@@ -1 +1 @@ +-this is a git diff test example ++this is a diff example +``` + +现在,让我们更仔细地研究 diff 命令的输出内容。 + +### 1. 进行对比的数据源 + +```shell +diff --git a/diff_test.txt b/diff_test.txt +``` + +这一行显示了 diff 命令的数据来源。我们可以看到 `a/diff_test.txt` 和 `b/diff_test.txt` 被传给了 diff 命令。 + +### 2. 元数据 + +```shell +index 6b0c6cf..b37e70a 100644 +``` + +这一行显示了一些 Git 自身的元数据。一般来说你不会用到这个信息。输出中的数字是 Git 对象的哈希版本标识。 + +### 3. 更改标记 + +```shell +--- a/diff_test.txt ++++ b/diff_test.txt +``` + +这些行包含了每个输入来源的更改标记。如上所示,`a/diff_test.txt`被标记为 `---` (删除),而 `b/diff_test.txt` 被标记为 `+++`(增加)。 + +### 4. 差异片段 + +diff 输出的剩余部分是一些差异片段。一个差异片段只显示文件中一处存在差异的区域。在这个简单的栗子中,我们只有一处差异。这些片段有自己的输出粒度语义。 + +```shell +@@ -1 +1 @@ +-this is a git diff test example ++this is a diff example +``` + +第一行是片段头部信息。头部信息位于每个片段开头的 `@@` 标记内。头部的内容是一份对该文件更改的摘要。在这个简单的栗子中,-1 +1 表示第 1 行存在变动。在一个真实的栗子中,你会看到这样的头部: + +```shell +@@ -34,6 +34,8 @@ +``` + +在这个头部栗子中,从 34 行起有 6 行被删除,从 34 行起有 8 行被增加。 + +差异片段中接下来的内容是最近进行的更改。被更改的每一行开头都有一个 `+` 或 `-` 标记,表示差异的来源版本。 `-`表示源于 `a/diff_test.txt` 的更改,+ 表示源于 `b/diff_test.txt` 的更改。 + +## 高亮变更内容 + +### 1. `git diff --color-words` + +`git diff` 有一个特别的模式,可以用更细的粒度高亮变更内容:`‐‐color-words`。这个模式根据空格将增加和删除的行切分成单词,然后再进行差异比较。 + +```shell +$:> git diff --color-words +diff --git a/diff_test.txt b/diff_test.txt +index 6b0c6cf..b37e70a 100644 +--- a/diff_test.txt ++++ b/diff_test.txt +@@ -1 +1 @@ +this is agit difftest example +``` + +现在,输出中只显示存在改动的单词,增加和删除被不同颜色高亮。 + +### 2. `git diff-highlight` + +如果你拷贝了 git 的源码,你会发现有一个 contrib 子目录。它包含了一系列 git 相关的工具和还没有选入 git 核心代码的一些有趣的东西。Diff-highlight 这个 Perl 脚本就是其中之一。它将差异输出的行进行配对,然后高亮单词中改变的字符。 + +```shell +$:> git diff | /your/local/path/to/git-core/contrib/diff-highlight/diff-highlight +diff --git a/diff_test.txt b/diff_test.txt +index 6b0c6cf..b37e70a 100644 +--- a/diff_test.txt ++++ b/diff_test.txt +@@ -1 +1 @@ +-this is a git diff test example ++this is a diff example +``` + +这已经是我们可以看到的最小可能更改了。 + +## 对比二进制文件 + +除了我们之前演示的文本文件之外, `git diff` 还可以用于二进制文件。不过,默认的输出并没有什么用。 + +```shell +$:> git diff +Binary files a/script.pdf and b/script.pdf differ +``` + +Git 有一个功能是允许你指定一个命令行命令,在执行对比之前将二进制文件的内容转换成文本。不过在这之前你需要一些设置。首先,你需要设置 textconv 过滤器描述如何将某种类型的二进制转换成文本。我们使用的是一个叫做 pdftohtml(可以通过 homebrew 安装)的工具,它可以将你的 PDF 转换成可读的 HTML。你可以编辑 `.git/config` 文件为某个特定的仓库设置该过滤器,或者编辑 `~ /.gitconfig` 应用至全局。 + +```shell +[diff "pdfconv"] +textconv=pdftohtml -stdout +``` + +然后,你只需要将一个或多个文件名规则关联到这个 pdfconv 过滤器。你可以在仓库根目录创建一个 `.gitattributes` 文件来执行此操作。 + +```shell +*.pdf diff=pdfconv +``` + +配置完成后,`git diff` 会首先使用配置好的转换脚本运行该二进制文件,然后再对转换后的输出进行差异对比。同样的技术可以用于任意一种二进制文件来获得有意义的差异对比。例如,使用 `unzip -l` 或类似命令替换 pdf2html,从而解压 zip 和 jar等格式的压缩文件,你可以看到不同提交镜像中新增或删除文件的路径;使用 exiv2 显示图像尺寸等元数据改动;另外还有转换工具可以将 .odf、.doc 和其他格式的文档转换成纯文本文件。简而言之,对于没有严格转换器的二进制文件来说,我们通常可以转换成字符串。 + +## 对比特定文件 + +`git diff` 命令可以传入一个显式的文件路径选项。当文件路径被传入 `git diff` 命令时,diff 操作将会被限定在某个特定的文件。下面这个例子展示了这种用法。 + +```shell +git diff HEAD ./path/to/file +``` + +这个例子中,该命令的作用域在使用时被限制在 `./path/to/file`,它会对比工作目录和缓存区下特定的修改,显示尚未缓存的修改。默认情况下,`git diff` 的对比是相较 `HEAD` 而言。忽略上述例子中的 `HEAD` 后的`git diff ./path/to/file` 命令有着相同作用。 + +```shell +git diff --cached ./path/to/file +``` + +如果在 `git diff` 执行时使用 `--cached` 选项,该命令会对比本地仓库和缓存区中的差异。`--cached` 选项与 `--staged` 同义。 + +## 对比所有更改 + +不加文件路径的 `git diff` 命令会对比仓库中所有的更改。上述针对特定文件的栗子,在执行时也可以移除 `./path/to/file` 参数从而使本地仓库中所有文件获得相同的输出。 + +## 上次提交之后的更改 + +默认情况下, `git diff` 会显示上次提交后所有未提交的更改。 + +```shell +git diff +``` + +## 两次提交之间的更改 + +`git diff` 可以将 Git 提交引用传给 diff 命令。例如,引用包括 `HEAD`、标签和分支名称。每个 Git 中的提交都有一个提交编号,你就有执行 `git log` 命令获得。你也可以将这个编号传给 `git diff`。 + +```shell +$:> git log --prety=oneline +957fbc92b123030c389bf8b4b874522bdf2db72c add feature +ce489262a1ee34340440e55a0b99ea6918e19e7a rename some classes +6b539f280d8b0ec4874671bae9c6bed80b788006 refactor some code for feature +646e7863348a427e1ed9163a9a96fa759112f102 add some copy to body + +$:> git diff 957fbc92b123030c389bf8b4b874522bdf2db72c ce489262a1ee34340440e55a0b99ea6918e19e7a +``` + +## 对比分支 + +### 对比两个分支 + +分支的比较与其他传给 `git diff` 的引用相同: + +```shell +git diff branch1..other-feature-branch +``` + +这个栗子引入了“点”操作符。其中两个点表示 diff 的输入是两个分支的顶端。当你用空格替代这两个点时,它们的效果相同。另外还有三点操作符: + +This example introduces the dot operator. The two dots in this example indicate the diff input is the tips of both branches. The same effect happens if the dots are omitted and a space is used between the branches. Additionally, there is a three dot operator: + +```shell +git diff branch1...other-feature-branch +``` + +三点操作符首先将第一个输入参数 `branch1` 修改成两个 diff 输入 branch1 分支和 other-feature-branch 分支的共同公共祖先的引用。最后一个输入参数保留不变,为 other-feature-branch 分支的顶端。 + +## 对比两个分支中的文件 + +将文件名作为第三个参数传入 `git diff` 命令,以比较不同分支上的同一文件: + +```shell +git diff master new_branch ./diff_test.txt +``` + +## 总结 + +本文讨论了 Git 差异比较的过程和 `git diff` 命令。我们讨论了如何阅读 `git diff` 输出和输出中的各种数据。我们举例讨论了如何为 `git diff` 输出加上高亮和颜色。我们太累了不同的差异对比策略,例如如何对比分支和特定提交上的更改。除了 `git diff` 命令外,我们还使用了 `git log` 和 `git checkout`。 + +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> +> 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 +> +> 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。 \ No newline at end of file diff --git a/sources/2.3.4-git_stash.md b/sources/2.3.4-git_stash.md new file mode 100644 index 0000000..ad2fcde --- /dev/null +++ b/sources/2.3.4-git_stash.md @@ -0,0 +1,318 @@ +# Git stash + +![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) + +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** + +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 11 日 +> +> ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 + +`git stash` 临时储藏了你在当前工作副本中的更改,让你可以进行其它更改,之后再重新应用储藏的更改。储藏对这样的应用场景来说非常方便:你需要快速切换上下文,进行其它工作,但你之前的代码更改还未完成,也没准备好提交。 + +## 目录 + +- [储藏你的工作](#储藏你的工作) +- [重新应用储藏的更改](#重新应用储藏的更改) +- [储藏未追踪或被忽略的文件](#储藏未追踪或被忽略的文件) +- [管理多个储藏](#管理多个储藏) +- [查看储藏与当前的差异对比](#查看储藏与当前的差异对比) +- [储藏文件片段](#储藏文件片段) +- [从当前储藏创建分支](#从当前储藏创建分支) +- [清理储藏](#清理储藏) +- [git stash 工作原理](#git stash 工作原理) + +## 储藏你的工作 + + `git stash` 命令将你未提交的更改(不论是否缓存)储存下来,然后将它们移出当前工作副本。例如: + +```shell +$ git status +On branch master +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +$ git stash +Saved working directory and index state WIP on master: 5002d47 our new homepage +HEAD is now at 5002d47 our new homepage +$ git status +On branch master +nothing to commit, working tree clean +``` + +此时,你可以进行任何修改,创建新的提交,切换分支或者执行任意的 Git 操作;在你完成后再回来重新应用储藏的内容。 + +注意,储藏是一个本地的概念;在推送时它们不会被推送到远端服务器。 + +## 重新应用储藏的更改 + +你可以使用 `git stash pop` 应用之前储藏的更改: + +```shell +$ git status +On branch master +nothing to commit, working tree clean +$ git stash pop +On branch master +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a) +``` + +*移出(pop)* 储藏内容将会将更改从储藏中移除并将它们重新添加到你的工作副本。 + +或者,你可以将更改重新添加到工作副本,但同时使用 `git stash apply` 将它们留在储藏中。 + +```shell +$ git stash apply +On branch master +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +``` + +这个命令可以帮助你将储藏的更改添加到多个分支。 + +现在你已经知道了储藏的基本用法,在使用时需要注意一点:Git 默认 *不会* 储藏未追踪或被忽略的文件。 + +## 储藏未追踪或被忽略的文件 + +默认情况下,执行 `git stash` 将会储藏: + +- 添加到缓存区的更改(缓存的更改) +- 当前被 Git 追踪的文件的更改(未缓存的更改) + +但是它 **不会** 储存: + +- 工作副本中尚未缓存的新文件 +- 被忽略的文件 + +因此,如果我们在上面的栗子中加入第三个文件,但是没有缓存它(不运行 `git add`),`git stash` 将不会储藏它。 + +```shell +$ script.js +$ git status +On branch master +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +Untracked files: +script.js +$ git stash +Saved working directory and index state WIP on master: 5002d47 our new homepage +HEAD is now at 5002d47 our new homepage +$ git status +On branch master +Untracked files: +script.js +``` + +`-u` 选项(或 `--include-untracked`)告诉 `git stash` 同时储藏未追踪的文件: + +```shell +$ git status +On branch master吃c +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +Untracked files: +script.js +$ git stash -u +Saved working directory and index state WIP on master: 5002d47 our new homepage +HEAD is now at 5002d47 our new homepage +$ git status +On branch master +nothing to commit, working tree clean +``` + +你也可以在运行 `git stash` 时传入 `-a` 选项(或 `--all`)储藏被忽略的文件中的更改。 + +## 管理多个储藏 + +储藏并不只限一个。你可以运行多次 `git stash` 创建多个储藏,然后使用 `git stash list` 查看所有储藏。默认情况下,储藏被认为是所在分支或提交顶端的“WIP”(work in progress,正在进行的工作)。一段时间后,分清每个储藏里的内容可能会变得有些困难。 + +```shell +$ git stash list +stash@{0}: WIP on master: 5002d47 our new homepage +stash@{1}: WIP on master: 5002d47 our new homepage +stash@{2}: WIP on master: 5002d47 our new homepage +``` + +为了提供更多的上下文信息,最好使用 `git stash save "描述"` 给你的储藏加上一段描述: + +```shell +$ git stash save "add style to our site" +Saved working directory and index state On master: add style to our site +HEAD is now at 5002d47 our new homepage +$ git stash list +stash@{0}: On master: add style to our site +stash@{1}: WIP on master: 5002d47 our new homepage +stash@{2}: WIP on master: 5002d47 our new homepage +``` + +默认情况下, `git stash pop` 会应用最近创建的储藏:`stash@{0}`。 + +通过传入标识符作为最后一个参数,你可以选择应用哪个储藏,例如: + +```shell +$ git stash pop stash@{2} +``` + +## 查看储藏与当前的差异对比 + +你可以使用 `git stash show` 查看储藏摘要: + +```shell +$ git stash show +index.html | 1 + +style.css | 3 +++ +2 files changed, 4 insertions(+) +``` + +或者传入 `-p` 选项( 或`--patch`)查看储藏的完整差异列表: + +```shell +$ git stash show -p +diff --git a/style.css b/style.css +new file mode 100644 +index 0000000..d92368b +--- /dev/null ++++ b/style.css +@@ -0,0 +1,3 @@ ++* { ++ text-decoration: blink; ++} +diff --git a/index.html b/index.html +index 9daeafb..ebdcbd2 100644 +--- a/index.html ++++ b/index.html +@@ -1 +1,2 @@ ++ +``` + +## 储藏文件片段 + +你还可以选择只储藏一个文件、几个文件或者文件中的变更片段。如果你将 `-p` 选项(或 `--patch`)传给 `git stash`,它会遍历工作副本中所有被更改的文件片段,并询问是否想要储藏: + +```shell +$ git stash -p +diff --git a/style.css b/style.css +new file mode 100644 +index 0000000..d92368b +--- /dev/null ++++ b/style.css +@@ -0,0 +1,3 @@ ++* { ++ text-decoration: blink; ++} +Stash this hunk [y,n,q,a,d,/,e,?]? y +diff --git a/index.html b/index.html +index 9daeafb..ebdcbd2 100644 +--- a/index.html ++++ b/index.html +@@ -1 +1,2 @@ ++ +Stash this hunk [y,n,q,a,d,/,e,?]? n +``` + +你可以点击 **?** 查看完整的片段操作命令。常见的用法有: + +| **命令** | **描述** | +| -------- | ------------------------------ | +| */* | 使用正则表达式搜索片段 | +| *?* | 帮助 | +| *n* | 不缓存该片段 | +| *q* | 退出(已选择的片段将会被缓存) | +| *s* | 将当前片段切分成更小的片段 | +| *y* | 缓存该片段 | + +虽然没有显式的“退出”命令,键入 `CTRL-C`(SIGINT,系统中断信号)将会退出储藏流程。 + +## 从当前储藏创建分支 + +如果分支与缓存中的更改出现了分叉,你在重新应用储藏时可能会遇到冲突。因此,你可以使用 `git stash branch` 将储藏中的更改应用到一个新的分支: + +```shell +$ git stash branch add-stylesheet stash@{1} +Switched to a new branch 'add-stylesheet' +On branch add-stylesheet +Changes to be committed: +new file: style.css +Changes not staged for commit: +modified: index.html +Dropped refs/stash@{1} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a) +``` + +以上命令在储藏所在的提交上检出了一个新的分支,然后将储藏中的更改应用到这个分支上。 + +## 清理储藏 + +如果你不再需要某个储藏,你可以使用 `git stash drop` 删除: + +```shell +$ git stash drop stash@{1} +Dropped stash@{1} (17e2697fd8251df6163117cb3d58c1f62a5e7cdb) +``` + +或者你可以使用以下命令删除所有储藏: + +``` +$ git stash clear +``` + +## git stash 工作原理 + +如果你只想知道如何使用 `git stash`,你可以跳过这一节。但如果你对 Git(以及 `git stash`)的底层实现感兴趣,那就看下去吧! + +储藏实际上是仓库中的提交对象。`.git/refs/stash` 中特殊的引用标记指向你最近创建的储藏。之前创建的储藏出现在 `stash` 引用的引用日志中。这也就是为什么你会通过 `stash@{n}:` 引用储藏——事实上你指向的是 `stash`引用对象的第 n 个引用日志对象。由于储藏本质是一个提交,你可以使用 `git log` 查看它: + +```shell +$ git log --oneline --graph stash@{0} +*-. 953ddde WIP on master: 5002d47 our new homepage +|\ \ +| | * 24b35a1 untracked files on master: 5002d47 our new homepage +| * 7023dd4 index on master: 5002d47 our new homepage +|/ +* 5002d47 our new homepage +``` + +根据储藏的内容,一个 `git stash` 操作会创建两个或三个新的提交。对于上图来说,这些提交是: + +- `stash@{0}` ,运行 `git stash` 时用于存放工作目录中被追踪文件的一个新的提交的 +- `stash@{0}` 的上一个父节点,运行 `git stash` 时 `HEAD` 指向的那个的提交 +- `stash@{0}` 的上二个父节点,运行 `git stash` 时表示缓存区的一个新的提交 +- `stash@{0}` 的上三个父节点,运行 `git stash` 时用于存放工作目录中未被追踪文件的一个新的提交的. 这个父节点只有满足这两个条件时才会被创建: + - 工作副本包含未被追踪的文件; + - 执行 `git stash` 时声明了 `--include-untracked` 或 `--all` 选项。 + + `git stash` 将你的工作区和缓存区编码成提交的流程如下: + +- 在储藏前,你的工作区可能包含对于追踪文件、未追踪文件和忽略文件的更改。其中一些更改可能存在于缓存区。 + + ![Before stashing](https://www.atlassian.com/dam/jcr:3a2ede93-1f2d-45ae-9e0b-167cc0362f37/03.2017-12-12-00-32-53.svg) + +- 执行 `git stash` 时,被追踪文件的更改会被编码成有向无环图中两个新的提交,其中一个包含未缓存的提交,另一个包含已缓存的更改。特殊的 `refs/stash` 引用将会更新并指向它们。 + + ![Git stash](https://www.atlassian.com/dam/jcr:35edaf68-e8b1-484e-b5f0-292c532f048a/04.2017-12-12-00-32-53.svg) + +- 使用 `--include-untracked` 选项会将未被追踪文件的更改编码成一个新的提交。 + + ![Git stash --include-untracked](https://www.atlassian.com/dam/jcr:f7dd5493-a98d-449e-ae37-146d6270ccf7/05.2017-12-12-00-32-52.svg) + +- 使用 `--all` 选项会将未追踪和被忽略文件的更改包含在同一个提交中。 + + ![Git Stash --all](https://www.atlassian.com/dam/jcr:446fad60-0ff5-4383-8177-a5fc2813364d/06.2017-12-12-00-32-51.svg) + +在运行 `git stash pop` 时,上述提交中的更改被用于更新你的工作副本和缓存区,而储藏的引用日志会移除该提交。注意,被移出的提交并不会被立即删除,而是在下次垃圾回收时被清理。 + +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> +> 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 +> +> 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。 \ No newline at end of file diff --git a/sources/2.3.5-git_ignore.md b/sources/2.3.5-git_ignore.md new file mode 100644 index 0000000..68f1d13 --- /dev/null +++ b/sources/2.3.5-git_ignore.md @@ -0,0 +1,161 @@ +# .gitignore + +![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) + +***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** + +> ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 24 日 +> +> ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 + +Git 将工作副本中每个文件都视作以下三者之一: + +1. 被追踪的文件——之前被缓存或提交过的文件; +2. 未被追踪的文件——尚未被缓存或提交过的文件; +3. 被忽略的文件——Git 被明确告知要忽略的文件。 + +被忽略的通常是构建过程(build)和机器生成的文件。它们可以根据你的仓库源码生成,或者不应该被提交。下面是一些常见的栗子: + +- 缓存的依赖文件,例如 `/node_modules` 或 `/packages` 里的内容 +- 编译后的代码,例如 `.o`、`.pyc`、`.class` +- 构建输出的目录,例如 `/bin` 、`/out`、`/target` +- 运行时生成的文件,例如`.log`、`.lock`、`.tmp` +- 隐藏的系统文件,例如`.DS_Store`、`Thumbs.db` +- 个人的开发环境(IDE)配置文件,例如`.idea/workspace.xml` + +忽略的文件通过一个名为 `.gitignore` 的文件追踪,该文件位于仓库的根目录下。Git 没有显式的忽略命令。添加新文件时必须手动修改并提交 `.gitignore` 文件来添加忽略的文件。`.gitignore` 文件里的规则用于匹配文件名,从而判断该文件是否需要被忽略。 + +## 目录 + +- [Git 忽略文件的匹配规则](#Git 忽略文件的匹配规则) +- [仓库内的共享 .gitignore 文件](#仓库内的共享 .gitignore 文件) +- [个人的 Git 忽略规则](#个人的 Git 忽略规则) +- [全局的 Git 忽略规则](#全局的 Git 忽略规则) +- [忽略一个被提交过的文件](#忽略一个被提交过的文件) +- [提交被忽略的文件](#提交被忽略的文件) +- [储存被忽略的文件](#储存被忽略的文件) +- [调试 .gitignore 文件](#调试 .gitignore 文件) + +## Git 忽略文件的匹配规则 + +`.gitignore` 使用 [GLOB 规则](http://linux.die.net/man/7/glob) 匹配文件名。你可以使用多种符号创建你的规则: + +| 匹配规则 | 样例 | 解释* | +| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `**/logs` | `logs/debug.log` `logs/monday/foo.bar` `build/logs/debug.log` | 在规则前接两个星号,从而在仓库任何位置匹配该目录。 | +| `**/logs/debug.log` | `logs/debug.log` `build/logs/debug.log` *但不会匹配* `logs/build/debug.log` | 使用两个星号匹配文件名和父目录名称。 | +| `*.log` | `debug.log` `foo.log` `.log` `logs/debug.log` | 单个星号为通配符,匹配零或多个字符。 | +| `*.log` `!important.log` | `debug.log` `trace.log` *但不会匹配* `important.log` `logs/important.log` | 在规则前接一个感叹号作为否定。如果文件同时符合一个先定义的规则和一个后定义的否定规则,它将不会被忽略。 | +| `*.log` `!important/*.log` `trace.*` | `debug.log` `important/trace.log` *但不会匹配* `important/debug.log` | 在否定规则后定义的规则会覆盖之前定义的否定规则。 | +| `/debug.log` | `debug.log` *但不会匹配* `logs/debug.log` | 前接斜杠只匹配仓库根目录下的文件。 | +| `debug.log` | `debug.log` `logs/debug.log` | 规则默认匹配任何目录下的文件。 | +| `debug?.log` | `debug0.log` `debugg.log` *但不会匹配* `debug10.log` | 一个问号用于匹配一个符号。 | +| `debug[0-9].log` | `debug0.log` `debug1.log` *但不会匹配* `debug10.log` | 方括号用于从特定范围中匹配单个字符。 | +| `debug[01].log` | `debug0.log` `debug1.log` *但不会匹配* `debug2.log` `debug01.log` | 方括号用于从特定集合中匹配单个字符。 | +| `debug[!01].log` | `debug2.log` *但不会匹配* `debug0.log` `debug1.log` `debug01.log` | 感叹号用于匹配除去特定集合中的任一字符。 | +| `debug[a-z].log` | `debuga.log` `debugb.log` *但不会匹配* `debug1.log` | 范围可以是数字,也可以是字母。 | +| `logs` | `logs` `logs/debug.log` `logs/latest/foo.bar` `build/logs` `build/logs/debug.log` | 如果你不后接斜杠,规则将会同时匹配相符的文件和目录。在左边的栗子中,名为 *logs* 的目录和文件都被忽略了。 | +| logs/ | `logs/debug.log` `logs/latest/foo.bar` `build/logs/foo.bar` `build/logs/latest/debug.log` | 后接斜杠表示该规则对应的是一个目录。仓库中符合该规则的任何目录中的所有内容(包括文件和子目录)都会被忽略。 | +| `logs/` `!logs/important.log` | `logs/debug.log` `logs/important.log` | 注意,为什么左边栗子中被加上否定的 `logs/important.log` 没有被忽略?由于 Git 的性能优化,你 *不能* 否定一个根据目录匹配规则被忽略的文件。 | +| `logs/**/debug.log` | `logs/debug.log` `logs/monday/debug.log` `logs/monday/pm/debug.log` | 两个星号匹配零个或多个目录。 | +| `logs/*day/debug.log` | `logs/monday/debug.log` `logs/tuesday/debug.log` 但不会匹配* `logs/latest/debug.log` | 通配符也可被用于目录名中。 | +| `logs/debug.log` | `logs/debug.log` 但不会匹配* `debug.log` `build/logs/debug.log` | 一些规则指定特定目录下的文件,这些路径相对于仓库的根目录。你可以在前接一个斜杠,但这不是必须的。 | + +** 这些解释假设你的 .gitignore 文件按照惯例位于仓库根目录。如果仓库中存在多个 .gitignore 文件,你只需要将“包含 .gitignore 文件的目录”想成是“仓库的根目录”。同时,为了便于团队合作,你应该考虑将它们合并* + +除了这些符号之外,你可以使用 # 在 `.gitignore` 文件中添加注释: + +```shell +# ignore all logs +*.log +``` + +你可以在 `.gitignore` 规则字符中使用 \ 转义,如果文件名或目录名中包含特殊字符: + +```shell +# ignore the file literally named foo[01].txt +foo\[01\].txt +``` + +## 仓库内的共享 .gitignore 文件 + +Git 忽略规则通常被定义在仓库根目录的 `.gitignore` 文件中。但是,你也可以在仓库内不同文件夹下定义多个 `.gitignore` 文件。 `.gitignore` 文件中的每个规则都会在所在文件夹进行匹配。不过,我们一般只在根目录定义一个 `.gitignore` 文件,这也是最简单的做法。当 `.gitignore` 文件被检入时,它和仓库内其它所有文件一样也有版本,在你推送后将与他人共享。在 `.gitignore` 中,你应该只添加对其它用户也有用的规则。 + +## 个人的 Git 忽略规则 + +你可以在 `.git/info/exclude` 这个文件中为某个仓库定义个人的忽略规则。它们没有版本,也不会跟随仓库发布,因此它们适合存放只对你有用的规则。例如,如果你有一个自定义的登陆配置,火烧会在仓库的工作目录下生成文件的特殊开发工具,你可以将它们添加到 `.git/info/exclude` 以避免被意外提交到仓库中。 + +## 全局的 Git 忽略规则 + +此外,通过设置 Git 的 `core.excludesFile` 属性,你可以在本地系统中为所有仓库设置全局的 Git 忽略规则。你需要自己创建这个文件。如果你不确定在哪里放置这个全局的 `.gitignore` 文件,home 目录会是一个好的选择,也便于以后的查找。创建这个文件之后,你需要使用 `git config` 配置它的位置: + +```shell +$ touch ~/.gitignore +$ git config --global core.excludesFile ~/.gitignore +``` + +你应该特别注意哪些规则是应该全局忽略的,因为不同的项目会用到不同类型的文件。某些操作系统文件(如 `.DS_Store` 和 `thumbs.db`)和开发者工具生成的临时文件往往可以被全局忽略。 + +## 忽略一个被提交过的文件 + +如果你想要忽略一个已经提交过的文件,你需要将这个文件从仓库中删除,然后在 `.gitignore` 中添加一条规则。使用 `git rm` 的 `--cached` 选项表示这个而文件将从仓库中被删除,但将作为一个被忽略的文件存在于工作目录下。 + +```shell +$ echo debug.log >> .gitignore +$ git rm --cached debug.log +rm 'debug.log' +$ git commit -m "Start ignoring debug.log" +``` + +如果你希望将某个文件同时从仓库和本地文件系统中删除,你可以省略 `--cached` 选项。 + +## 提交被忽略的文件 + +使用 `git add` 的 `-f`(或者`--force`)选项,你可以强制提交一个被忽略的文件: + +```shell +$ cat .gitignore +*.log +$ git add -f debug.log +$ git commit -m "Force adding debug.log" +``` + +使用这个命令的一个场景是,你定义了一个通用的规则(如 `*.log`),但是想要提交某个特定的文件。不过,一个更好的解决方案是为这个通用的规则定义一个例外: + +```shell +$ echo !debug.log >> .gitignore +$ cat .gitignore +*.log +!debug.log +$ git add debug.log +$ git commit -m "Adding debug.log" +``` + +上面的这个方法更加清晰易懂,尤其是对于其它团队成员。 + +## 储存被忽略的文件 + +`git stash` 是一个强大的 Git 功能。它将本地的更改临时储存起来然后撤销,允许你随后再重新应用这些更改。正如你所想的一样,`git stash` 默认跳过被忽略的文件,它只储存被 Git 追踪的文件中的更改。然后,你可以使用 git stash 的 --all 选项来储存被忽略和为追踪的文件。 + +## 调试 .gitignore 文件 + +如果你的 `.gitignore` 规则十分复杂,或者规则分散于多个 `.gitignore` 文件,要理解某个文件为什么被忽略了不是一件容易的事情。你可以使用 `git check-ignore` 命令的 `-v`(或者 `--verbose`)选项来确定是哪条规则让某个文件被忽略了: + +```shell +$ git check-ignore -v debug.log +.gitignore:3:*.log debug.log +``` + +输出的格式如下: + +```shell + : : +``` + +你可以向 `git check-ignore` 传入多个文件名。这些文件名不一定要对应于仓库中已存在的文件。 + +> 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 +> +> 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 +> +> 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。 \ No newline at end of file