Git

什么是 Git?

什么是 Git? #

✍️ houkensjtu | 童仲毅

©️ 本文演绎自 Atlassian 编写的 What is Git。页面上所有内容采用知识共享-署名( CC BY 2.5 AU)许可协议。

到目前为止,Git 是世界上使用最为广泛的现代化版本控制系统。Git 最初由 Linux 系统内核的作者 Linus Torvalds 在 2005 年开始开发,目前已经是一个持续维护的成熟开源项目。如今,大量软件项目依赖 Git 进行版本管理,其中既有开源软件,也有商业软件。Git 在很多操作系统和集成开发环境(IDE)上都表现良好。绝大多数软件开发者或多或少都使用过 Git。

Git 是分布式版本管理(DVCS)的一种。CVS 和 Subversion(SVN)等集中式的版本管理软件将完整的版本历史存放在同一个地方。而在 Git 中,每个开发者的代码仓库都包含了所有变更历史。

除了分布式之外,Git 在设计之初也考虑了性能、安全性和灵活性。

高性能 #

Git 的底层性能相较于其他版本管理软件有强大的优势。提交修改、创建分支、合并分支和比较版本都针对性能进行了优化。Git 中实现的算法利用了现实中代码树的特点以及它们被修改和访问的常见模式。

不同于某些版本管理软件,Git 在决定文件树的储存和版本历史时,不会被文件名的变化所愚弄——Git 关注的是文件的内容本身。毕竟,代码文件经常会被重命名、拆分和重新编排。Git 仓库中的文件对象通过差分编码(delta encoding,仅保存代码修改的差分)和压缩技术储存,并且直接保存文件夹中的内容和版本控制元数据。

分布式架构也给 Git 带来了巨大的性能优势。

比如说,有一名开发成员 Alice 修改了代码,添加了一些准备在 2.0 版本中发布的功能,然后提交了这些修改及其描述。随后,她又编写并提交了另一个新功能。很自然地,这两次修改是版本历史中两份独立的工作。Alice 又切换到了 1.3 版本的分支,修复了一个只影响这个旧版本的 bug。这次修复的目的是为了让团队在 2.0 版本还没有完成之前,发布一个 1.3.1 版本来解决旧版本中的一些 bug。Alice 可以立刻回到 2.0 版本分支,继续新功能开发。这一切都不需要网络连接,非常快速可靠,甚至可以在飞机中完成。当她准备好将这些单独提交的更改发送到远程仓库时,她只需要一个“推送”(push)命令。

安全 #

Git 设计时就把托管代码的完好性作为重中之重。文件内容以及文件、目录、版本、标签和提交的关联,都通过安全的加密哈希校验算法(SHA1)保护。这可以避免代码和修改历史被不小心或者恶意改变,并且保证修改历史完全可追迹。

...

Git简易指南

Git简易指南 #

BY 童仲毅( geeeeeeeeek@github

除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。 git-guide 项目对本文亦有贡献。

这节是完全面向入门者的,我假设你从零开始创建一个项目并且想用 Git 来进行版本控制,因此本文会避开分支这些相对复杂的概念。

在这节中,我会介绍如何在你的个人项目中使用 Git,我们会讨论 Git 最基本的操作——如何初始化你的项目,如何管理新的或者已有的文件,如何在远端仓库中储存你的代码。

安装 Git #

  • Mac 用户:Xcode Command Line Tools 自带 Git(xcode-select --install

  • Linux 用户:sudo apt-get install git

  • Windows 用户:下载 Git SCM

    - 对于 Windows 用户,安装后如果希望在全局的 cmd 中使用 Git,需要把 git.exe 加入 PATH 环境变量中,或在 Git Bash 中使用 Git。
    

检出仓库 #

执行如下命令以创建一个本地仓库的克隆版本:

git clone /path/to/repository

如果是远端服务器上的仓库,你的命令会是这个样子:

git clone username@host:/path/to/repository (通过 SSH)

或者:

git clone https:/path/to/repository.git (通过 https)

...

Git创建代码仓库

Git创建代码仓库 #

BY 童仲毅( geeeeeeeeek@github)

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

这一章简要地带你了解一些最重要的 Git 命令。在这节中,我会向你介绍开始一个新的版本控制项目需要的所有工具,后面的几节包含了你每天都会用到的Git操作。

在这节之后,你应该能够创建一个新的 Git 仓库,缓存你的项目以免丢失,以及查看你项目的历史。

git init #

git init 命令创建一个新的 Git 仓库。它用来将已存在但还没有版本控制的项目转换成一个 Git 仓库,或者创建一个空的新仓库。大多数Git命令在未初始化的仓库中都是无法使用的,所以这就是你运行新项目的第一个命令了。

运行 git init 命令会在你项目的根目录下创建一个新的 .git 目录,其中包含了你项目必需的所有元数据。除了 .git 目录之外,已经存在的项目不会被改变(就像 SVN 一样,Git 不强制每个子目录中都有一个 .git 目录)。

git init用法 #

git init

将当前的目录转换成一个 Git 仓库。它在当前的目录下增加了一个 .git 目录,于是就可以开始记录项目版本了。

git init <directory>

在指定目录创建一个空的 Git 仓库。运行这个命令会创建一个名为 directory,只包含 .git 子目录的空目录。

git init --bare <directory>

初始化一个裸的 Git 仓库,但是忽略工作目录。共享的仓库应该总是用 --bare 标记创建(见下面的讨论)。一般来说,用 —bare 标记初始化的仓库以 .git 结尾。比如,一个叫my-project的仓库,它的空版本应该保存在 my-project.git 目录下。

...

Git保存更改

Git保存更改 #

Saving changes

git add / git commit / git diff / git stash / .gitignore

✍️ 童仲毅 | ⏳ 2018 年 10 月 26 日(部分章节未更新)

©️ 本文演绎自 Atlassian 编写的 Saving Changes。页面上所有内容采用知识共享-署名( CC BY 2.5 AU)许可协议。

“保存”这个概念在 Git 等版本控制系统和 Word 等文本编辑应用中不太一样。传统软件里的“保存”在 Git 里被叫做“提交”(commit)。 我们常说的的保存可以理解成在文件系统中覆盖一个已有的文件或者创建一个新的文件。而在 Git 中,提交这个操作作用于若干个文件和目录。

在 Git 和 SVN 里保存更改也不一样。SVN 提交或检入(check-in)将会推送到远端的中央服务器。也就是说 SVN 的提交需要联网才能完全“保存”项目更改。Git 提交可以在本地完成,然后再使用git push -u origin master命令推送到远端服务器。这两种方法的区别体现了两种架构设计的本质区别。Git 是一个分布式的应用,而 SVN 是一个中心化的应用。分布式应用一般来说更可靠,因为它们不存在中央服务器这样的单点故障。

git addgit statusgit commit这三个命令通常一起使用,将 Git 项目当前的状态保存成一份快照。

Git 还有另一个保存机制:“储藏”(stash)。储藏是一个临时的储存区域,保存还没准备好提交的更改。储藏操作作用于工作目录,三个文件树中的第一棵。它有很多用法,访问 git stash 页面了解更多。

...

Git检查仓库状态

Git检查仓库状态 #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

git status #

git status 命令显示工作目录和缓存区的状态。你可以看到哪些更改被缓存了,哪些还没有,以及哪些还未被 Git 追踪。status 的输出 不会 告诉你任何已提交到项目历史的信息。如果你想看的话,应该使用 git log 命令。

git status用法 #

git status

列出已缓存、未缓存、未追踪的文件。

讨论 #

git status 是一个相对简单的命令。 它告诉你 git addgit commit 的进展。status 信息还包括了添加缓存和移除缓存的相关指令。样例输出显示了三类主要的 git status 输出:

# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
#modified: hello.py
#
# 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: main.py
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
#hello.pyc

忽略文件 #

未追踪的文件通常有两类。它们要么是项目新增但还未提交的文件,要么是像 .pyc.obj.exe 等编译后的二进制文件。显然前者应该出现在 git status 的输出中,而后者会让我们困惑究竟发生了什么。

...

Git检出之前的提交

Git检出之前的提交 #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

git checkout #

git checkout 这个命令有三个不同的作用:检出文件、检出提交和检出分支。在这一章中,我们只关心前两种用法。

检出提交会使工作目录和这个提交完全匹配。你可以用它来查看项目之前的状态,而不改变当前的状态。检出文件使你能够查看某个特定文件的旧版本,而工作目录中剩下的文件不变。

git checkout用法 #

git checkout master

回到 master 分支。分支会在下一节中讲到,而现在,你只需要将它视为回到项目「当前」状态的一种方式。

git checkout <commit> <file>

查看文件之前的版本。它将工作目录中的 <file> 文件变成 <commit> 中那个文件的拷贝,并将它加入缓存区。

git checkout <commit>

更新工作目录中的所有文件,使得和某个特定提交中的文件一致。你可以将提交的哈希字串,或是标签作为 <commit> 参数。这会使你处在分离 HEAD 的状态。

讨论 #

版本控制系统背后的思想就是「安全」地储存项目的拷贝,这样你永远不用担心什么时候不可复原地破坏了你的代码库。当你建立了项目历史之后,git checkout 是一种便捷的方式,来将保存的快照「加载」到你的开发机器上去。

检出之前的提交是一个只读操作。在查看旧版本的时候绝不会损坏你的仓库。你项目「当前」的状态在 master 上不会变化。在开发的正常阶段,HEAD 一般指向 master 或是其他的本地分支,但当你检出之前提交的时候,HEAD 就不再指向一个分支了——它直接指向一个提交。这被称为「分离 HEAD」状态 ,可以用下图可视化:

Git Tutorial: Checking out a previous commit

在另一方面,检出旧文件不影响你仓库的当前状态。你可以在新的快照中像其他文件一样重新提交旧版本。所以,在效果上,git checkout 的这个用法可以用来将单个文件回滚到旧版本 。

...

Git回滚错误的修改

Git回滚错误的修改 #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

这章教程提供了和项目旧版本打交道所需要的所有技巧。首先,你会知道如何浏览旧的提交,然后了解回滚项目历史中的公有提交和回滚本地机器上的私有更改之间的区别。

git checkout #

见上一章 「2.5 检出之前的提交」

git revert #

git revert 命令用来撤销一个已经提交的快照。但是,它是通过搞清楚如何撤销这个提交引入的更改,然后在最后加上一个撤销了更改的 提交,而不是从项目历史中移除这个提交。这避免了Git丢失项目历史,这一点对于你的版本历史和协作的可靠性来说是很重要的。

Git Tutorial: git revert

git revert用法 #

git revert <commit>

生成一个撤消了 <commit> 引入的修改的新提交,然后应用到当前分支。

讨论 #

撤销(revert)应该用在你想要在项目历史中移除一整个提交的时候。比如说,你在追踪一个 bug,然后你发现它是由一个提交造成的,这时候撤销就很有用。与其说自己去修复它,然后提交一个新的快照,不如用 git revert,它帮你做了所有的事情。

撤销(revert)和重设(reset)对比 #

理解这一点很重要。git revert 回滚了「单独一个提交」,它没有移除后面的提交,然后回到项目之前的状态。在 Git 中,后者实际上被称为 reset,而不是 revert

Git Tutorial: Revert vs Reset

撤销和重设相比有两个重要的优点。首先,它不会改变项目历史,对那些已经发布到共享仓库的提交来说这是一个安全的操作。至于为什么改变共享的历史是危险的,请参阅 git reset 一节。

其次,git revert 可以针对历史中任何一个提交,而 git reset 只能从当前提交向前回溯。比如,你想用 git reset 重设一个旧的提交,你不得不移除那个提交后的所有提交,再移除那个提交,然后重新提交后面的所有提交。不用说,这并不是一个优雅的回滚方案。

...

Git重写项目历史

Git重写项目历史 #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

概述 #

Git 的主要职责是保证你不会丢失提交的修改。但是,它同样被设计成让你完全掌控开发工作流。这包括了让你自定义你的项目历史,而这也创造了丢失提交的可能性。Git 提供了可以重写项目历史的命令,但也警告你这些命令可能会让你丢失内容。

这份教程讨论了重写提交快照的一些常见原因,并告诉你如何避免不好的影响。

git commit –amend #

git commit --amend 命令是修复最新提交的便捷方式。它允许你将缓存的修改和之前的提交合并到一起,而不是提交一个全新的快照。它还可以用来简单地编辑上一次提交的信息而不改变快照。

Git Tutorial: git commit –amend

但是,amend 不只是修改了最新的提交——它进行了一次替换。对于 Git 来说,这看上去像一个全新的提交,即上图中用星号表示的那一个。在公共仓库工作时一定要牢记这一点。

git commit --amend用法 #

git commit --amend

合并缓存的修改和上一次的提交,用新的快照替换上一个提交。缓存区没有文件时运行这个命令可以用来编辑上次提交的提交信息,而不会更改快照。

讨论 #

仓促的提交在你日常开发过程中时常会发生。很容易就忘记了缓存一个文件或者弄错了提交信息的格式。--amend 标记是修复这些小意外的便捷方式。

不要修复公共提交 #

git reset这节中,我们说过永远不要重设和其他开发者共享的提交。对于修复也是一样:永远不要修复一个已经推送到公共仓库中的提交。

修复过的提交事实上是全新的提交,之前的提交会被移除出项目历史。这和重设公共快照的后果是一样的。如果你修复了其他开发者在之后继续开发的一个提交,看上去他们的工作基础从项目历史中消失了一样。对于在这上面的开发者来说这是很困惑的,而且很难恢复。

例子 #

下面这个🌰展示了 Git 开发工作流中的一个常见情形。我们编辑了一些希望在同一个快照中提交的文件,但我们忘记添加了其中的一个。修复错误只需要缓存那个文件并且用 --amend 标记提交:

# 编辑 hello.py 和 main.py
git add hello.py
git commit

# 意识到你忘记添加 main.py 的更改
git add main.py
git commit --amend --no-edit

编辑器会弹出上一次提交的信息,加入 --no-edit 标记会修复提交但不修改提交信息。需要的话你可以修改,不然的话就像往常一样保存并关闭文件。完整的提交会替换之前不完整的提交,看上去就像我们在同一个快照中提交了 hello.pymain.py

...

Git保持同步

Git保持同步 #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

SVN 使用唯一的中央仓库作为开发者之间沟通的桥梁,在开发者的工作拷贝和中央仓库之间传递变更集合(changeset),协作得以发生。这和Git的协作模型有所不同,Git 给予每个开发者一份自己的仓库拷贝,拥有自己完整的本地历史和分支结构。用户通常共享一系列的提交而不是单个变更集合。Git 允许你在仓库间共享整个分支,而不是从工作副本提交一个差异集合到中央仓库。

下面的命令让你管理仓库之间的连接,将分支「推送」到其他仓库来发布本地历史,或是将分支「拉取」到本地仓库来查看其它开发者的贡献。

git remote #

git remote 命令允许你创建、查看和删除和其它仓库之间的连接。远程连接更像是书签,而不是直接跳转到其他仓库的链接。它用方便记住的别名引用不那么方便记住的 URL,而不是提供其他仓库的实时连接。

例如,下图显示了你的仓库和中央仓库以及另一个开发者仓库之间的远程连接。你可以向 Git 命令传递 origin 和 john 的别名来引用这些仓库,替代完整的 URL。

Git Tutorial: git remote

git remote用法 #

git remote

列出你和其他仓库之间的远程连接。

git remote -v

和上个命令相同,但同时显示每个连接的 URL。

git remote add <name> <url>

创建一个新的远程仓库连接。在添加之后,你可以将 <name> 作为 <url> 便捷的别名在其他 Git 命令中使用。

git remote rm <name>

移除名为的远程仓库的连接。

git remote rename <old-name> <new-name>

将远程连接从 <old-name> 重命名为 <new-name>

...

Git创建Pull Request

Git创建Pull Request #

BY 童仲毅( geeeeeeeeek@github

这是一篇在 原文(BY atlassian)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名( CC BY 2.5 AU)协议共享。

原文以 Bitbucket 为例,考虑到 git-recipes主要面向 GitHub 用户,因此例子替换成了 GitHub。Pull Request 在 GitLab 等平台上也有,用法和本教程基本一致。

Pull Request 是开发者使用 GitHub 进行协作的利器。这个功能为用户提供了友好的页面,让提议的更改在并入官方项目之前,可以得到充分的讨论。

qq20160127-0

最简单地来说,Pull Request 是一种机制,让开发者告诉项目成员一个功能已经完成。一旦 feature 分支开发完毕,开发者使用 GitHub 账号提交一个 Pull Request。它告诉所有参与者,他们需要审查代码,并将代码并入 master 分支。

但是,Pull Request 不只是一个通知,还是一个专注于某个提议功能的讨论版。 如果更改导致了任何问题,团队成员可以在 Pull Request 下发布反馈,甚至推送后续提交来修改这个 Pull Request。所有的活动都在这个 Pull Request里之间追踪。

Git Workflows: Activity inside a Pull Request

和其他协作模型相比,这种共享提交的解决方案形成了更加线性的工作流。SVN 和 Git 都能通过一个简单的脚本发送通知邮件;但是,如果要讨论更改,开发者不得不在邮件里回复。这会变得愈发杂乱无章,尤其是后续提交出现时。Pull Request 将所有这些功能放入了一个友好的网页,在每个 GitHub 仓库上方都能找到。

剖析一个 Pull Request #

当你提交一个 Pull Request 的时候,你做的事情是 请求(request) 另一个开发者(比如项目维护者)来 拉取(pull) 你仓库中的一个分支到他们的仓库。也就是说你需要提供 4 个信息来完成一个 Pull Request:源仓库、源分支、目标仓库、目标分支。

...