前言前言 Git 是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。本教程是史上最 浅显易懂的 Git 教程,能够帮助读者迅速掌握并上手操作 Git 命令。 适用人群适用人群 本教程绝对面向初学者,没有接触过版本控制概念的读者也可以轻松入门,不必担心起步难度;本教程实用性超 强,边学边练,一点也不觉得枯燥。而且,你所学的 Git 命令是“充分且必要”的,掌握了这些东西,你就可以 通过 Git 轻松地完成你的工作。 本教程只会让你成为 Git 用户,不会让你成为 Git 专家。如果你是一个开发人员,想用上这个世界上目前最先进 的分布式版本控制系统,那么,赶快开始学习吧! 鸣谢:廖雪峰的官方网站 (http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b 8067c8c017b000) 目录目录 前言前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 第 1 章第 1 章 Git 简介Git 简介. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Git 的诞生 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 集中式 vs 分布式. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 第 2 章第 2 章 Git 环境搭建Git 环境搭建 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1010 安装 Git. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 创建版本库 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 第 3 章第 3 章 时光机穿梭时光机穿梭 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1818 仓库状态 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 版本回退 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 工作区和暂存区 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 管理修改 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 撤销修改 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 删除文件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 第 4 章第 4 章 远程仓库远程仓库 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4040 生成 SSH key . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 添加远程库 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 从远程库克隆 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 第 5 章第 5 章 分支管理分支管理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5151 分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 创建与合并分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 解决冲突 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 分支管理策略 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Bug 分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Feature 分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 多人协作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 第 6 章第 6 章 标签管理标签管理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7676 创建标签 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 操作标签 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 第 7 章第 7 章 使用 GitHub使用 GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8282 GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 第 8 章第 8 章 自定义 Git自定义 Git. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8585 忽略特殊文件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 配置别名 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 搭建 Git 服务器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 11 Git 简介Git 简介 Git 的诞生Git 的诞生 Git 是什么?Git 是什么? Git 是目前世界上最先进的分布式版本控制系统(没有之一)。 Git 有什么特点?简单来说就是:高端大气上档次! 那什么是版本控制系统? 如果你用 Microsoft Word 写过长篇大论,那你一定有这样的经历: 想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文 件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的 Word 文档变成了这 样: 过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻 烦。 看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。 更要命的是,有些部分需要你的财务同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过 Email 发送一 份给她),然后,你继续修改 Word 文件。一天后,同事再把 Word 文件传给你,此时,你必须想想,发给她之 后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。 第 1 章 Git 简介 | 5 于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管 理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是 很方便? 这个软件用起来就应该像这个样子,能记录每次文件的改动: 版本版本 用户用户 说明说明 日期日期 1 张三 删除了软件服务条款5 7/12 10:38 2 张三 增加了License人数限制 7/12 18:09 3 李四 财务部门调整了合同金额 7/13 9:51 4 张三 延长了免费升级周期 7/14 15:17 这样,你就结束了手动管理多个“版本”的史前时代,进入到版本控制的 21 世纪。 Git 历史Git 历史 很多人都知道,Linus 在 1991 年创建了开源的 Linux,从此,Linux 系统不断发展,已经成为最大的服务器系统 软件了。 Linus 虽然创建了 Linux,但 Linux 的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为 Linux 编写 代码,那 Linux 的代码是如何管理的呢? 事实是,在 2002 年以前,世界各地的志愿者把源代码文件通过 diff 的方式发给 Linus,然后由 Linus 本人通过 手工方式合并代码! 你也许会想,为什么 Linus 不把 Linux 代码放到版本控制系统里呢?不是有 CVS、SVN 这些免费的版本控制系 统吗?因为 Linus 坚定地反对 CVS 和 SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使 用。有一些商用的版本控制系统,虽然比 CVS、SVN 好用,但那是付费的,和 Linux 的开源精神不符。 不过,到了 2002 年,Linux 系统已经发展了十年了,代码库之大让 Linus 很难继续通过手工方式管理了,社区 的弟兄们也对这种方式表达了强烈不满,于是 Linus 选择了一个商业的版本控制系统 BitKeeper,BitKeeper 的 东家 BitMover 公司出于人道主义精神,授权 Linux 社区免费使用这个版本控制系统。 安定团结的大好局面在 2005 年就被打破了,原因是 Linux 社区牛人聚集,不免沾染了一些梁山好汉的江湖习 气。开发 Samba 的 Andrew 试图破解 BitKeeper 的协议(这么干的其实也不只他一个),被 BitMover 公司 发现了(监控工作做得不错!),于是 BitMover 公司怒了,要收回 Linux 社区的免费使用权。 Linus 可以向 BitMover 公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的: 第 1 章 Git 简介 | 6 Linus 花了两周时间自己用 C 写了一个分布式版本控制系统,这就是 Git!一个月之内,Linux 系统的源码已经 由 Git 管理了!牛是怎么定义的呢?大家可以体会一下。 Git 迅速成为最流行的分布式版本控制系统,尤其是 2008 年,GitHub 网站上线了,它为开源项目免费提供 Git 存储,无数开源项目开始迁移至 GitHub,包括 jQuery,PHP,Ruby 等等。 历史就是这么偶然,如果不是当年 BitMover 公司威胁 Linux 社区,可能现在我们就没有免费而超级好用的 Git 了。 第 1 章 Git 简介 | 7 集中式 vs 分布式集中式 vs 分布式 Linus 一直痛恨的 CVS 及 SVN 都是集中式的版本控制系统,而 Git 是分布式版本控制系统,集中式和分布式版 本控制系统有什么区别呢? 先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先 从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比 是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。 集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在 互联网上,遇到网速慢的话,可能提交一个 10M 的文件就需要 5 分钟,这还不得把人给憋死啊。 那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务 器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己 的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件 A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方 的修改了。 和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某 一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出 了问题,所有人都没法干活了。 在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在 一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本 第 1 章 Git 简介 | 8 控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修 改,没有它大家也一样干活,只是交换修改不方便而已。 当然,Git 的优势不单是不必联网这么简单,后面我们还会看到 Git 极其强大的分支管理,把 SVN 等远远抛在了 后面。 CVS 作为最早的开源而且免费的集中式版本控制系统,直到现在还有不少人在用。由于 CVS 自身设计的问 题,会造成提交文件不完整,版本库莫名其妙损坏的情况。同样是开源而且免费的 SVN 修正了 CVS 的一些稳定 性问题,是目前用得最多的集中式版本库控制系统。 除了免费的外,还有收费的集中式版本控制系统,比如 IBM 的 ClearCase(以前是 Rational 公司的,被 IBM 收购了),特点是安装比 Windows 还大,运行比蜗牛还慢,能用 ClearCase 的一般是世界 500 强,他们有个 共同的特点是财大气粗,或者人傻钱多。 微软自己也有一个集中式版本控制系统叫 VSS,集成在 Visual Studio 中。由于其反人类的设计,连微软自己都 不好意思用了。 分布式版本控制系统除了 Git 以及促使 Git 诞生的 BitKeeper 外,还有类似 Git 的 Mercurial 和 Bazaar 等。这 些分布式版本控制系统各有特点,但最快、最简单也最流行的依然是 Git! 第 1 章 Git 简介 | 9 22 Git 环境搭建Git 环境搭建 安装 Git安装 Git 最早 Git 是在 Linux 上开发的,很长一段时间内,Git 也只能在 Linux 和 Unix 系统上跑。不过,慢慢地有人把 它移植到了 Windows 上。现在,Git 可以在 Linux、Unix、Mac 和 Windows 这几大平台上正常运行了。 要使用 Git,第一步当然是安装 Git 了。根据你当前使用的平台来阅读下面的文字: 在 Linux 上安装 Git在 Linux 上安装 Git 首先,你可以试着输入 git,看看系统有没有安装 Git: $ git The program 'git' is currently not installed. You can install it by typing: sudo apt-get install git 像上面的命令,有很多 Linux 会友好地告诉你 Git 没有安装,还会告诉你如何安装 Git。 如果你碰巧用 Debian 或 Ubuntu Linux,通过一条 sudo apt-get install git 就可以直接完成 Git 的安装,非常 简单。 老一点的 Debian 或 Ubuntu Linux,要把命令改为 sudo apt-get install git-core ,因为以前有个软件也叫 GI T(GNU Interactive Tools),结果 Git 就只能叫 git-core 了。由于 Git 名气实在太大,后来就把 GNU Inter active Tools 改成 gnuit,git-core 正式改为 git。 如果是其他 Linux 版本,可以直接通过源码安装。先从 Git 官网下载源码,然后解压,依次输入: ./config,ma ke,sudo make install 这几个命令安装就好了。 第 2 章 Git 环境搭建 | 11 在 Mac OS X 上安装 Git在 Mac OS X 上安装 Git 如果你正在使用 Mac 做开发,有两种安装 Git 的方法。 一是安装 homebrew,然后通过 homebrew 安装 Git,具体方法请参考 homebrew 的文档:http://brew.s h/。 第二种方法更简单,也是推荐的方法,就是直接从 AppStore 安装 Xcode,Xcode 集成了 Git,不过默认没有 安装,你需要运行 Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选 择“Command Line Tools”,点“Install”就可以完成安装了。 Xcode 是 Apple 官方 IDE,功能非常强大,是开发 Mac 和 iOS App 的必选装备,而且是免费的! 在 Windows 上安装 Git在 Windows 上安装 Git msysgit 是 Windows 版的 Git,从 http://msysgit.github.io/ 下载,然后按默认选项安装即可。 安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明 Git 安装成 功! 第 2 章 Git 环境搭建 | 12 安装完成后,还需要最后一步设置,在命令行输入: $ git config --global user.name "Your Name" $ git config --global user.email "email@example.com" 因为 Git 是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和 Email 地址。你也许会担心,如 果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是 有办法可查的。 注意 git config 命令的 --global 参数,用了这个参数,表示你这台机器上所有的 Git 仓库都会使用这个配置,当 然也可以对某个仓库指定不同的用户名和 Email 地址。 第 2 章 Git 环境搭建 | 13 创建版本库创建版本库 什么是版本库呢?版本库又名仓库,英文名 repository,你可以简单理解成一个目录,这个目录里面的所有文件 都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个 时刻可以“还原”。 所以,创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录: $ mkdir learngit $ cd learngit $ pwd /Users/michael/learngit pwd 命令用于显示当前目录。在我的 Mac 上,这个仓库位于 /Users/michael/learngit 。 如果你使用 Windows 系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。 第二步,通过 git init 命令把这个目录变成 Git 可以管理的仓库: $ git init Initialized empty Git repository in /Users/michael/learngit/.git/ 瞬间 Git 就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目 录下多了一个 .git 的目录,这个目录是 Git 来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文 件,不然改乱了,就把 Git 仓库给破坏了。 如果你没有看到 .git 目录,那是因为这个目录默认是隐藏的,用 ls -ah 命令就可以看见。 也不一定必须在空目录下创建 Git 仓库,选择一个已经有东西的目录也是可以的。不过,不建议你使用自己正在 开发的公司项目来学习 Git,否则造成的一切后果概不负责。 第 2 章 Git 环境搭建 | 14 把文件添加到版本库把文件添加到版本库 首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如 TXT 文件,网页,所有的程序 代码等等,Git 也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行 删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的 变化,只能把二进制文件每次改动串起来,也就是只知道图片从 100KB 改成了 120KB,但到底改了啥,版本控 制系统不知道,也没法知道。 不幸的是,Microsoft 的 Word 格式是二进制格式,因此,版本控制系统是没法跟踪 Word 文件的改动的,前面 我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。 因为文本是有编码的,比如中文有常用的 GBK 编码,日文有 Shift_JIS 编码,如果没有历史遗留问题,强烈建 议使用标准的 UTF-8 编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。 使用 Windows 的童鞋要特别注意:使用 Windows 的童鞋要特别注意: 千万不要使用 Windows 自带的记事本编辑任何文本文件。原因是 Microsoft 开发记事本的团队使用了一个不合 理的方法来保存 UTF-8 编码的文件,他们在每个文件开头添加了 0xefbbbf(十六进制)的字符,你会遇到很多 不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由 记事本的弱智行为带来的。建议你下载Notepad++ (http://notepad-plus-plus.org/) 代替记事本,不但功能强 大,而且免费!记得把 Notepad++ 的默认编码设置为 UTF-8 without BOM 即可: 言归正传,现在我们编写一个 readme.txt 文件,内容如下: Git is a version control system. Git is free software. 第 2 章 Git 环境搭建 | 15 一定要放到 learngit 目录下(子目录也行),因为这是一个 Git 仓库,放到其他地方 Git 再厉害也找不到这个文 件。 和把大象放到冰箱需要 3 步相比,把一个文件放到 Git 仓库只需要两步。 第一步,用命令 git add 告诉 Git,把文件添加到仓库: $ git add readme.txt 执行上面的命令,没有任何显示,这就对了,Unix 的哲学是“没有消息就是好消息”,说明添加成功。 第二步,用命令 git commit 告诉 Git,把文件提交到仓库: $ git commit -m "wrote a readme file" [master (root-commit) cb926e7] wrote a readme file 1 file changed, 2 insertions(+) create mode 100644 readme.txt 简单解释一下 git commit 命令, -m 后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义 的,这样你就能从历史记录里方便地找到改动记录。 嫌麻烦不想输入 -m "xxx" 行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对 别人阅读都很重要。实在不想输入说明的童鞋请自行 Google,我不告诉你这个参数。 git commit 命令执行成功后会告诉你,1 个文件被改动(我们新添加的 readme.txt 文件),插入了两行内容(r eadme.txt 有两行内容)。 为什么 Git 添加文件需要 add,commit 一共两步呢?因为 commit 可以一次提交很多文件,所以你可以多次 ad d 不同的文件,比如: 第 2 章 Git 环境搭建 | 16 $ git add file1.txt $ git add file2.txt file3.txt $ git commit -m "add 3 files." 小结小结 现在总结一下今天学的两点内容: 初始化一个 Git 仓库,使用 git init 命令。 添加文件到 Git 仓库,分两步: 第一步,使用命令 git add ,注意,可反复多次使用,添加多个文件; 第二步,使用命令 git commit ,完成。 第 2 章 Git 环境搭建 | 17 33 时光机穿梭时光机穿梭 仓库状态仓库状态 我们已经成功地添加并提交了一个 readme.txt 文件,现在,是时候继续工作了,于是,我们继续修改 readme.t xt 文件,改成如下内容: Git is a distributed version control system. Git is free software. 现在,运行 git status 命令看看结果: $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # no changes added to commit (use "git add" and/or "git commit -a") git status 命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.txt 被修改过了,但还没有 准备提交的修改。 虽然 Git 告诉我们 readme.txt 被修改了,但如果能看看具体修改了什么内容,自然是很好的。比如你休假两周 从国外回来,第一天上班时,已经记不清上次怎么修改的 readme.txt,所以,需要用 git diff 这个命令看看: $ git diff readme.txt diff --git a/readme.txt b/readme.txt index 46d49bf..9247db6 100644 --- a/readme.txt +++ b/readme.txt @@ -1,2 +1,2 @@ -Git is a version control system. +Git is a distributed version control system. Git is free software. git diff 顾名思义就是查看 difference,显示的格式正是 Unix 通用的 diff 格式,可以从上面的命令输出看到,我 们在第一行添加了一个“distributed”单词。 知道了对 readme.txt 作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两 步,第一步是 git add : 第 3 章 时光机穿梭 | 19 $ git add readme.txt 同样没有任何输出。在执行第二步 git commit 之前,我们再运行 git status 看看当前仓库的状态: $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: readme.txt # git status 告诉我们,将要被提交的修改包括 readme.txt,下一步,就可以放心地提交了: $ git commit -m "add distributed" [master ea34578] add distributed 1 file changed, 1 insertion(+), 1 deletion(-) 提交后,我们再用 git status 命令看看仓库的当前状态: $ git status # On branch master nothing to commit (working directory clean) Git 告诉我们当前没有需要提交的修改,而且,工作目录是干净(working directory clean)的。 小结小结 要随时掌握工作区的状态,使用 git status 命令。 如果 git status 告诉你有文件被修改过,用 git diff 可以查看修改内容。 第 3 章 时光机穿梭 | 20 版本回退版本回退 现在,你已经学会了修改文件,然后把修改提交到 Git 版本库,现在,再练习一次,修改 readme.txt 文件如 下: Git is a distributed version control system. Git is free software distributed under the GPL. 然后尝试提交: $ git add readme.txt $ git commit -m "append GPL" [master 3628164] append GPL 1 file changed, 1 insertion(+), 1 deletion(-) 像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩 RPG 游戏时,每通过一关就会自动 把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打 Boss 之前,你会手动 存盘,以便万一打 Boss 失败了,可以从最近的地方重新开始。Git 也是一样,每当你觉得文件修改到一定程度的 时候,就可以“保存一个快照”,这个快照在 Git 中被称为 commit。一旦你把文件改乱了,或者误删了文 件,还可以从最近的一个 commit 恢复,然后继续工作,而不是把几个月的工作成果全部丢失。 现在,我们回顾一下 readme.txt 文件一共有几个版本被提交到 Git 仓库里了: 版本1版本1:wrote a readme file Git is a version control system. Git is free software. 版本2版本2:add distributed Git is a distributed version control system. Git is free software. 版本3版本3:append GPL Git is a distributed version control system. Git is free software distributed under the GPL. 当然了,在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统 干什么。版本控制系统肯定有某个命令可以告诉我们历史记录,在 Git 中,我们用 git log 命令查看: $ git log commit 3628164fb26d48395383f8f31179f24e0882e1e0 第 3 章 时光机穿梭 | 21 Author: Michael Liao Date: Tue Aug 20 15:11:49 2013 +0800 append GPL commit ea34578d5496d7dd233c827ed32a8cd576c5ee85 Author: Michael Liao Date: Tue Aug 20 14:53:12 2013 +0800 add distributed commit cb926e7ea50ad11b8f9e909c05226233bf755030 Author: Michael Liao Date: Mon Aug 19 17:51:55 2013 +0800 wrote a readme file git log 命令显示从最近到最远的提交日志,我们可以看到 3 次提交,最近的一次是 append GPL ,上一次是 a dd distributed ,最早的一次是 wrote a readme file 。 如果嫌输出信息太多,看得眼花缭乱的,可以试试加上 - -pretty=oneline 参数: $ git log --pretty=oneline 3628164fb26d48395383f8f31179f24e0882e1e0 append GPL ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file 需要友情提示的是,你看到的一大串类似 3628164...882e1e0 的是 commit id (版本号),和 SVN 不一样,Gi t 的 commit id 不是 1,2,3……递增的数字,而是一个 SHA1 计算出来的一个非常大的数字,用十六进制表 示,而且你看到的 commit id 和我的肯定不一样,以你自己的为准。为什么 commit id 需要用这么一大串数字表 示呢?因为 Git 是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用 1,2,3……作为版本号,那肯定就冲突了。 每提交一个新版本,实际上 Git 就会把它们自动串成一条时间线。如果使用可视化工具查看 Git 历史,就可以更 清楚地看到提交历史的时间线: 第 3 章 时光机穿梭 | 22 好了,现在我们启动时光穿梭机,准备把 readme.txt 回退到上一个版本,也就是 “add distributed” 的那个版 本,怎么做呢? 首先,Git 必须知道当前版本是哪个版本,在 Git 中,用 HEAD 表示当前版本,也就是最新的提交 3628164...88 2e1e0 (注意我的提交 ID 和你的肯定不一样),上一个版本就是 HEAD^ ,上上一个版本就是 HEAD^^ ,当然 往上 100 个版本写 100 个 ^ 比较容易数不过来,所以写成 HEAD~100。 现在,我们要把当前版本“append GPL”回退到上一个版本“add distributed”,就可以使用 git reset 命 令: $ git reset --hard HEAD^ HEAD is now at ea34578 add distributed --hard 参数有啥意义?这个后面再讲,现在你先放心使用。 看看 readme.txt 的内容是不是版本 add distributed : $ cat readme.txt Git is a distributed version control system. Git is free software. 果然。 还可以继续回退到上一个版本 wrote a readme file ,不过且慢,然我们用 git log 再看看现在版本库的状态: $ git log commit ea34578d5496d7dd233c827ed32a8cd576c5ee85 Author: Michael Liao Date: Tue Aug 20 14:53:12 2013 +0800 add distributed commit cb926e7ea50ad11b8f9e909c05226233bf755030 Author: Michael Liao Date: Mon Aug 19 17:51:55 2013 +0800 wrote a readme file 最新的那个版本 append GPL 已经看不到了!好比你从 21 世纪坐时光穿梭机来到了 19 世纪,想再回去已经回 不去了,肿么办? 办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个 append GPL 的 commit id 是 3628164... ,于是就可以指定回到未来的某个版本: 第 3 章 时光机穿梭 | 23 $ git reset --hard 3628164 HEAD is now at 3628164 append GPL 版本号没必要写全,前几位就可以了,Git 会自动去找。当然也不能只写前一两位,因为 Git 可能会找到多个版本 号,就无法确定是哪一个了。 再小心翼翼地看看 readme.txt 的内容: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. 果然,又回来了。 Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD 指针,当你回退版本的时候,Git 仅仅 是把 HEAD 从指向 append GPL : 改为指向 add distributed: 然后顺便把工作区的文件更新了。所以你让 HEAD 指向哪个版本号,你就把当前版本定位在哪。 第 3 章 时光机穿梭 | 24 现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的 com mit id 怎么办? 在 Git 中,总是有后悔药可以吃的。当你用 $ git reset --hard HEAD^ 回退到 add distributed 版本时,再想恢 复到 append GPL ,就必须找到 append GPL 的 commit id 。Git 提供了一个命令 git reflog 用来记录你的每 一次命令: $ git reflog ea34578 HEAD@{0}: reset: moving to HEAD^ 3628164 HEAD@{1}: commit: append GPL ea34578 HEAD@{2}: commit: add distributed cb926e7 HEAD@{3}: commit (initial): wrote a readme file 终于舒了口气,第二行显示 append GPL 的 commit id 是 3628164 ,现在,你又可以乘坐时光机回到未来了。 第 3 章 时光机穿梭 | 25 小结小结 现在总结一下: HEAD 指向的版本就是当前版本,因此,Git 允许我们在版本的历史之间穿梭,使用命令 git reset --hard com mit_id 。 穿梭前,用 git log 可以查看提交历史,以便确定要回退到哪个版本。 要重返未来,用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。 第 3 章 时光机穿梭 | 26 工作区和暂存区工作区和暂存区 Git 和其他版本控制系统如 SVN 的一个不同之处就是有暂存区的概念。 先来看名词解释。 工作区工作区 就是你在电脑里能看到的目录,比如我的 learngit 文件夹就是一个工作区: 版本库版本库 工作区有一个隐藏目录 .git ,这个不算工作区,而是 Git 的版本库。 Git 的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫 index)的暂存区,还有 Git 为我们自动创 建的第一个分支 master,以及指向 master 的一个指针叫 HEAD。 第 3 章 时光机穿梭 | 27 分支和 HEAD 的概念我们以后再讲。 前面讲了我们把文件往 Git 版本库里添加的时候,是分两步执行的: 第一步是用 git add 把文件添加进去,实际上就是把文件修改添加到暂存区; 第二步是用 git commit 提交更改,实际上就是把暂存区的所有内容提交到当前分支。 因为我们创建 Git 版本库时,Git 自动为我们创建了唯一一个 master 分支,所以,现在, git commit 就是往 m aster 分支上提交更改。 你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。 俗话说,实践出真知。现在,我们再练习一遍,先对 readme.txt 做个修改,比如加上一行内容: Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. 然后,在工作区新增一个 LICENSE 文本文件(内容随便写)。 先用 git status 查看一下状态: $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # # Untracked files: # (use "git add ..." to include in what will be committed) 第 3 章 时光机穿梭 | 28 # # LICENSE no changes added to commit (use "git add" and/or "git commit -a") Git 非常清楚地告诉我们,readme.txt 被修改了,而 LICENSE 还从来没有被添加过,所以它的状态是 Untrack ed。 现在,使用两次命令 git add ,把 readme.txt 和 LICENSE 都添加后,用 git status 再查看一下: $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: LICENSE # modified: readme.txt # 现在,暂存区的状态就变成这样了: 所以, git add 命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行 git commit 就可以一次 性把暂存区的所有修改提交到分支。 $ git commit -m "understand how stage works" [master 27c9860] understand how stage works 2 files changed, 675 insertions(+) create mode 100644 LICENSE 一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的: $ git status # On branch master nothing to commit (working directory clean) 第 3 章 时光机穿梭 | 29 现在版本库变成了这样,暂存区就没有任何内容了: 小结小结 暂存区是 Git 非常重要的概念,弄明白了暂存区,就弄明白了 Git 的很多操作到底干了什么。 没弄明白暂存区是怎么回事的童鞋,请向上滚动页面,再看一次。 第 3 章 时光机穿梭 | 30 管理修改管理修改 现在,假定你已经完全掌握了暂存区的概念。下面,我们要讨论的就是,为什么 Git 比其他版本控制系统设计得 优秀,因为 Git 跟踪并管理的是修改,而非文件。 你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也 是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。 为什么说 Git 管理的是修改,而不是文件呢?我们还是做实验。第一步,对 readme.txt 做一个修改,比如加一 行内容: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes. 然后,添加: $ git add readme.txt $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: readme.txt # 然后,再修改 readme.txt: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. 提交: $ git commit -m "git tracks changes" [master d4f25b6] git tracks changes 1 file changed, 1 insertion(+) 提交后,再看看状态: 第 3 章 时光机穿梭 | 31 $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # no changes added to commit (use "git add" and/or "git commit -a") 咦,怎么第二次的修改没有被提交? 别激动,我们回顾一下操作过程: 第一次修改 -> git add -> 第二次修改 -> git commit 你看,我们前面讲了,Git 管理的是修改,当你用 git add 命令后,在工作区的第一次修改被放入暂存区,准备提 交,但是,在工作区的第二次修改并没有放入暂存区,所以, git commit 只负责把暂存区的修改提交了,也就是 第一次的修改被提交了,第二次的修改不会被提交。 提交后,用 git diff HEAD -- readme.txt 命令可以查看工作区和版本库里面最新版本的区别: $ git diff HEAD -- readme.txt diff --git a/readme.txt b/readme.txt index 76d770f..a9c5755 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. -Git tracks changes. +Git tracks changes of files. 可见,第二次修改确实没有被提交。 第 3 章 时光机穿梭 | 32 那怎么提交第二次修改呢?你可以继续 git add 再 git commit ,也可以别着急提交第一次修改,先 git add 第二 次修改,再 git commit ,就相当于把两次修改合并后一块提交了: 第一次修改 -> git add -> 第二次修改 -> git add -> git commit 好,现在,把第二次修改提交了,然后开始小结。 小结小结 现在,你又理解了 Git 是如何跟踪修改的,每次修改,如果不 add 到暂存区,那就不会加入到 commit 中。 第 3 章 时光机穿梭 | 33 撤销修改撤销修改 自然,你是不会犯错的。不过现在是凌晨两点,你正在赶一份工作报告,你在 readme.txt 中添加了一行: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. My stupid boss still prefers SVN. 在你准备提交前,一杯咖啡起了作用,你猛然发现了“stupid boss”可能会让你丢掉这个月的奖金! 既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状 态。如果用 git status 查看一下: $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # no changes added to commit (use "git add" and/or "git commit -a") 你可以发现,Git 会告诉你, git checkout -- file 可以丢弃工作区的修改: $ git checkout -- readme.txt 命令 git checkout -- readme.txt 意思就是,把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况: • 一种是 readme.txt 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态; • 一种是 readme.txt 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。 总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态。 现在,看看 readme.txt 的文件内容: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. 第 3 章 时光机穿梭 | 34 Git has a mutable index called stage. Git tracks changes of files. 文件内容果然复原了。 git checkout -- file 命令中的--很重要,没有--,就变成了“创建一个新分支”的命令,我们在后面的分支管 理中会再次遇到 git checkout 命令。 现在假定是凌晨 3 点,你不但写了一些胡话,还 git add 到暂存区了: $ cat readme.txt Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. My stupid boss still prefers SVN. $ git add readme.txt 庆幸的是,在 commit 之前,你发现了这个问题。用 git status 查看一下,修改只是添加到了暂存区,还没有提 交: $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: readme.txt # Git 同样告诉我们,用命令 git reset HEAD file 可以把暂存区的修改撤销掉(unstage),重新放回工作区: 第 3 章 时光机穿梭 | 35 $ git reset HEAD readme.txt Unstaged changes after reset: M readme.txt git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用 HEAD 时,表示最新的版本。 再用 git status 查看一下,现在暂存区是干净的,工作区有修改: $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # no changes added to commit (use "git add" and/or "git commit -a") 还记得如何丢弃工作区的修改吗? $ git checkout -- readme.txt $ git status # On branch master nothing to commit (working directory clean) 整个世界终于清静了! 现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退 (页 21)一节吗?可以回 退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得 Git 是分布式版 本控制系统吗?我们后面会讲到远程版本库,一旦你把“stupid boss”提交推送到远程版本库,你就真的惨 了…… 第 3 章 时光机穿梭 | 36 小结小结 又到了小结时间。 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令 git checkout -- file 。 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令 git r eset HEAD file ,就回到了场景 1,第二步按场景 1 操作。 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退 (页 21)一节,不过前提是没有 推送到远程库。 第 3 章 时光机穿梭 | 37 删除文件删除文件 在 Git 中,删除也是一个修改操作,我们实战一下,先添加一个新文件 test.txt 到 Git 并且提交: $ git add test.txt $ git commit -m "add test.txt" [master 94cdc44] add test.txt 1 file changed, 1 insertion(+) create mode 100644 test.txt 一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用 rm 命令删了: $ rm test.txt 这个时候,Git 知道你删除了文件,因此,工作区和版本库就不一致了, git status 命令会立刻告诉你哪些文件被 删除了: $ git status # On branch master # Changes not staged for commit: # (use "git add/rm ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # deleted: test.txt # no changes added to commit (use "git add" and/or "git commit -a") 现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令 git rm 删掉,并且 git commit : $ git rm test.txt rm 'test.txt' $ git commit -m "remove test.txt" [master d17efd8] remove test.txt 1 file changed, 1 deletion(-) delete mode 100644 test.txt 现在,文件就从版本库中被删除了。 另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本: $ git checkout -- test.txt git checkout 其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。 第 3 章 时光机穿梭 | 38 小结小结 命令 git rm 用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你 只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。 第 3 章 时光机穿梭 | 39 44 远程仓库远程仓库 生成 SSH key生成 SSH key 到目前为止,我们已经掌握了如何在 Git 仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的 问题了。 可是有用过集中式版本控制系统 SVN 的童鞋会站出来说,这些功能在 SVN 里早就有了,没看出 Git 有什么特别 的地方。 没错,如果只是在一个仓库里管理文件历史,Git和SVN真没啥区别。为了保证你现在所学的Git物超所值,将来 绝对不会后悔,同时为了打击已经不幸学了 SVN 的童鞋,本章开始介绍 Git 的杀手级功能之一(注意是之 一,也就是后面还有之二,之三……):远程仓库。 Git 是分布式版本控制系统,同一个 Git 仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器 有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并 没有主次之分。 你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,怎么玩? 其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在 一台电脑上搞几个远程库玩,因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了会导致所有库都挂 掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。 实际情况往往是这样,找一台电脑充当服务器的角色,每天 24 小时开机,其他每个人都从这个“服务器”仓库 克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。 完全可以自己搭建一台运行Git的服务器,不过现阶段,为了学 Git 先搭个服务器绝对是小题大作。好在这个世界 上有个叫 GitHub 的神奇的网站,从名字就可以看出,这个网站就是提供 Git 仓库托管服务的,所以,只要注册 一个 GitHub 账号,就可以免费获得 Git 远程仓库。 在继续阅读后续内容前,请自行注册 GitHub 账号。由于你的本地 Git 仓库和 GitHub 仓库之间的传输是通过 SS H 加密的,所以,需要一点设置: 第 1 步:创建 SSH Key。在用户主目录下,看看有没有 .ssh 目录,如果有,再看看这个目录下有没有 id_rsa 和 id_rsa.pub 这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开 Shell(Windows 下打开 Git Bash),创建 SSH Key: $ ssh-keygen -t rsa -C "youremail@example.com" 第 4 章 远程仓库 | 41 你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个 Key 也不是用于军事目 的,所以也无需设置密码。 如果一切顺利的话,可以在用户主目录里找到 .ssh 目录,里面有 id_rsa 和 id_rsa.pub 两个文件,这两个就是 SSH Key 的秘钥对, id_rsa 是私钥,不能泄露出去, id_rsa.pub 是公钥,可以放心地告诉任何人。 第 2 步:登陆 GitHub,打开“Account settings”,“SSH Keys”页面: 然后,点“Add SSH Key”,填上任意 Title,在 Key 文本框里粘贴 id_rsa.pub 文件的内容: 点“Add Key”,你就应该看到已经添加的 Key: 为什么 GitHub 需要 SSH Key 呢?因为 GitHub 需要识别出你推送的提交确实是你推送的,而不是别人冒充 的,而 Git 支持 SSH 协议,所以,GitHub 只要知道了你的公钥,就可以确认只有你自己才能推送。 当然,GitHub 允许你添加多个 Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每 台电脑的 Key 都添加到 GitHub,就可以在每台电脑上往 GitHub 推送了。 第 4 章 远程仓库 | 42 最后友情提示,在 GitHub 上免费托管的 Git 仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要 把敏感信息放进去。 如果你不想让别人看到 Git 库,有两个办法,一个是交点保护费,让 GitHub 把公开的仓库变成私有的,这样别 人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个 Git 服务器,因为是你自己的 Git 服务 器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。 确保你拥有一个 GitHub 账号后,我们就即将开始远程仓库的学习。 小结 “有了远程仓库,妈妈再也不用担心我的硬盘了。”——Git 点读机 第 4 章 远程仓库 | 43 添加远程库添加远程库 现在的情景是,你已经在本地创建了一个 Git 仓库后,又想在 GitHub 创建一个 Git 仓库,并且让这两个仓库进 行远程同步,这样,GitHub 上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。 首先,登陆 GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库: 在 Repository name 填入 learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一 个新的 Git 仓库: 第 4 章 远程仓库 | 44 目前,在 GitHub 上的这个 learngit 仓库还是空的,GitHub 告诉我们,可以从这个仓库克隆出新的仓库,也可 以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到 GitHub 仓库。 现在,我们根据 GitHub 的提示,在本地的 learngit 仓库下运行命令: $ git remote add origin git@github.com:michaelliao/learngit.git 请千万注意,把上面的 michaelliao 替换成你自己的 GitHub 账户名,否则,你在本地关联的就是我的远程 库,关联没有问题,但是你以后推送是推不上去的,因为你的 SSH Key 公钥不在我的账户列表中。 添加后,远程库的名字就是 origin,这是 Git 默认的叫法,也可以改成别的,但是 origin 这个名字一看就知道是 远程库。 下一步,就可以把本地库的所有内容推送到远程库上: $ git push -u origin master Counting objects: 19, done. Delta compression using up to 4 threads. Compressing objects: 100% (19/19), done. Writing objects: 100% (19/19), 13.73 KiB, done. Total 23 (delta 6), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git * [new branch] master -> master Branch master set up to track remote branch master from origin. 把本地库的内容推送到远程,用 git push 命令,实际上是把当前分支 master 推送到远程。 由于远程库是空的,我们第一次推送 master 分支时,加上了 -u 参数,Git 不但会把本地的 master 分支内容推 送的远程新的 master 分支,还会把本地的 master 分支和远程的 master 分支关联起来,在以后的推送或者拉 取时就可以简化命令。 推送成功后,可以立刻在 GitHub 页面中看到远程库的内容已经和本地一模一样: 第 4 章 远程仓库 | 45 从现在起,只要本地作了提交,就可以通过命令: $ git push origin master 把本地master分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本 库! SSH 警告SSH 警告 当你第一次使用 Git 的 clone 或者 push 命令连接 GitHub 时,会得到一个警告: The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established. RSA key fingerprint is xx.xx.xx.xx.xx. Are you sure you want to continue connecting (yes/no)? 这是因为 Git 使用 SSH 连接,而 SSH 连接在第一次验证 GitHub 服务器的 Key 时,需要你确认 GitHub 的 Ke y 的指纹信息是否真的来自 GitHub 的服务器,输入 yes 回车即可。 Git 会输出一个警告,告诉你已经把 GitHub 的 Key 添加到本机的一个信任列表里了: Warning: Permanently added 'github.com' (RSA) to the list of known hosts. 这个警告只会出现一次,后面的操作就不会有任何警告了。 如果你实在担心有人冒充 GitHub 服务器,输入 yes 前可以对照 GitHub 的 RSA Key 的指纹信息 (https://hel p.github.com/articles/what-are-github-s-ssh-key-fingerprints/) 是否与 SSH 连接给出的一致。 第 4 章 远程仓库 | 46 小结小结 要关联一个远程库,使用命令 git remote add origin git@server-name:path/repo-name.git ; 关联后,使用命令 git push -u origin master 第一次推送 master 分支的所有内容; 此后,每次本地提交后,只要有必要,就可以使用命令 git push origin master 推送最新修改; 分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工 作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方 便了! 第 4 章 远程仓库 | 47 从远程库克隆从远程库克隆 上次我们讲了先有本地库,后有远程库的时候,如何关联远程库。 现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。 首先,登陆 GitHub,创建一个新的仓库,名字叫 gitskills: 我们勾选 Initialize this repository with a README ,这样 GitHub 会自动为我们创建一个 README.md 文 件。创建完毕后,可以看到 README.md 文件: 第 4 章 远程仓库 | 48 现在,远程库已经准备好了,下一步是用命令 git clone 克隆一个本地库: $ git clone git@github.com:michaelliao/gitskills.git Cloning into 'gitskills'... remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 0 (delta 0) Receiving objects: 100% (3/3), done. $ cd gitskills $ ls README.md 注意把 Git 库的地址换成你自己的,然后进入 gitskills 目录看看,已经有 README.md 文件了。 如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。 你也许还注意到,GitHub 给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地 址。实际上,Git 支持多种协议,默认的 git:// 使用 ssh,但也可以使用 https 等其他协议。 使用 https 除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放 http 端口的公司 内部就无法使用 ssh 协议而只能用 https。 小结小结 要克隆一个仓库,首先必须知道仓库的地址,然后使用 git clone 命令克隆。 第 4 章 远程仓库 | 49 Git 支持多种协议,包括 https,但通过 ssh 支持的原生 git 协议速度最快。 第 4 章 远程仓库 | 50 55 分支管理分支管理 分支分支 分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习 Git 的时候,另一个你正在另一个平行宇宙里努力 学习 SVN。 如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你 既学会了 Git 又学会了 SVN! 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如 果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又 存在丢失每天进度的巨大风险。 现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工 作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安 全,又不影响别人工作。 其他版本控制系统如 SVN 等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛 还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。 但 Git 的分支是与众不同的,无论创建、切换和删除分支,Git 在 1 秒钟之内就能完成!无论你的版本库是1个文 件还是 1 万个文件。 第 5 章 分支管理 | 52 创建与合并分支创建与合并分支 在版本回退里,你已经知道,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目 前,只有一条时间线,在 Git 里,这个分支叫主分支,即 master 分支。HEAD 严格来说不是指向提交,而是指 向 master,master 才是指向提交的,所以,HEAD指向的就是当前分支。 一开始的时候,master 分支是一条线,Git 用 master 指向最新的提交,再用 HEAD 指向 master,就能确定 当前分支,以及当前分支的提交点: 每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master 分支的线也越来越长: 当我们创建新的分支,例如 dev 时,Git 新建了一个指针叫 dev,指向 master 相同的提交,再把 HEAD 指向 d ev,就表示当前分支在 dev 上: 第 5 章 分支管理 | 53 你看,Git 创建一个分支很快,因为除了增加一个 dev 指针,改改 HEAD 的指向,工作区的文件都没有任何变 化! 不过,从现在开始,对工作区的修改和提交就是针对 dev 分支了,比如新提交一次后,dev 指针往前移动一 步,而 master 指针不变: 假如我们在 dev 上的工作完成了,就可以把 dev 合并到 master 上。Git 怎么合并呢?最简单的方法,就是直接 把 master 指向 dev 的当前提交,就完成了合并: 第 5 章 分支管理 | 54 所以 Git 合并分支也很快!就改改指针,工作区内容也不变! 合并完分支后,甚至可以删除 dev 分支。删除 dev 分支就是把 dev 指针给删掉,删掉后,我们就剩下了一条 m aster 分支: 真是太神奇了,你看得出来有些提交是通过分支完成的吗? 下面开始实战。 首先,我们创建 dev 分支,然后切换到 dev 分支: $ git checkout -b dev Switched to a new branch 'dev' git checkout 命令加上 -b 参数表示创建并切换,相当于以下两条命令: $ git branch dev $ git checkout dev Switched to branch 'dev' 然后,用 git branch 命令查看当前分支: 第 5 章 分支管理 | 55 $ git branch * dev master git branch 命令会列出所有分支,当前分支前面会标一个 * 号。 然后,我们就可以在 dev 分支上正常提交,比如对 readme.txt 做个修改,加上一行: Creating a new branch is quick. 然后提交: $ git add readme.txt $ git commit -m "branch test" [dev fec145a] branch test 1 file changed, 1 insertion(+) 现在,dev 分支的工作完成,我们就可以切换回 master 分支: $ git checkout master Switched to branch 'master' 切换回 master 分支后,再查看一个 readme.txt 文件,刚才添加的内容不见了!因为那个提交是在 dev 分支 上,而 master 分支此刻的提交点并没有变: 现在,我们把 dev 分支的工作成果合并到 master 分支上: $ git merge dev Updating d17efd8..fec145a Fast-forward readme.txt | 1 + 1 file changed, 1 insertion(+) 第 5 章 分支管理 | 56 git merge 命令用于合并指定分支到当前分支。合并后,再查看 readme.txt 的内容,就可以看到,和 dev 分支 的最新提交是完全一样的。 注意到上面的 Fast-forward 信息,Git 告诉我们,这次合并是“快进模式”,也就是直接把 master 指向 dev 的当前提交,所以合并速度非常快。 当然,也不是每次合并都能 Fast-forward,我们后面会将其他方式的合并。 合并完成后,就可以放心地删除 dev 分支了: $ git branch -d dev Deleted branch dev (was fec145a). 删除后,查看 branch,就只剩下 master 分支了: $ git branch * master 因为创建、合并和删除分支非常快,所以 Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在 ma ster 分支上工作效果是一样的,但过程更安全。 小结小结 Git 鼓励大量使用分支: 查看分支:git branch 创建分支:git branch 切换分支:git checkout 第 5 章 分支管理 | 57 创建+切换分支:git checkout -b 合并某分支到当前分支:git merge 删除分支:git branch -d 第 5 章 分支管理 | 58 解决冲突解决冲突 人生不如意之事十之八九,合并分支往往也不是一帆风顺的。 准备新的 feature1 分支,继续我们的新分支开发: $ git checkout -b feature1 Switched to a new branch 'feature1' 修改 readme.txt 最后一行,改为: Creating a new branch is quick AND simple. 在 feature1 分支上提交: $ git add readme.txt $ git commit -m "AND simple" [feature1 75a857c] AND simple 1 file changed, 1 insertion(+), 1 deletion(-) 切换到 master 分支: $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit. Git 还会自动提示我们当前 master 分支比远程的 master 分支要超前 1 个提交。 在 master 分支上把 readme.txt 文件的最后一行改为: Creating a new branch is quick & simple. 提交: $ git add readme.txt $ git commit -m "& simple" [master 400b400] & simple 1 file changed, 1 insertion(+), 1 deletion(-) 现在,master 分支和 feature1 分支各自都分别有新的提交,变成了这样: 第 5 章 分支管理 | 59 这种情况下,Git 无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试 试看: $ git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result. 果然冲突了!Git 告诉我们,readme.txt 文件存在冲突,必须手动解决冲突后再提交。git status 也可以告诉我 们冲突的文件: $ git status # On branch master # Your branch is ahead of 'origin/master' by 2 commits. # # Unmerged paths: # (use "git add/rm ..." as appropriate to mark resolution) # # both modified: readme.txt # no changes added to commit (use "git add" and/or "git commit -a") 我们可以直接查看 readme.txt 的内容: Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. <<<<<<< HEAD # Creating a new branch is quick & simple.Creating a new branch is quick AND simple. >>>>>>> feature1 第 5 章 分支管理 | 60 Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存: Creating a new branch is quick and simple. 再提交: $ git add readme.txt $ git commit -m "conflict fixed" [master 59bc1cb] conflict fixed 现在,master 分支和 feature1 分支变成了下图所示: 用带参数的 git log 也可以看到分支的合并情况: $ git log --graph --pretty=oneline --abbrev-commit * 59bc1cb conflict fixed |\ | * 75a857c AND simple * | 400b400 & simple |/ * fec145a branch test ... 最后,删除 feature1 分支: $ git branch -d feature1 Deleted branch feature1 (was 75a857c). 工作完成。 第 5 章 分支管理 | 61 小结小结 当 Git 无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。 用 git log --graph 命令可以看到分支合并图。 第 5 章 分支管理 | 62 分支管理策略分支管理策略 通常,合并分支时,如果可能,Git 会用 Fast forward 模式,但这种模式下,删除分支后,会丢掉分支信息。 如果要强制禁用 Fast forward 模式,Git 就会在 merge 时生成一个新的 commit,这样,从分支历史上就可以 看出分支信息。 下面我们实战一下 --no-ff 方式的 git merge : 首先,仍然创建并切换 dev 分支: $ git checkout -b dev Switched to a new branch 'dev' 修改 readme.txt 文件,并提交一个新的 commit: $ git add readme.txt $ git commit -m "add merge" [dev 6224937] add merge 1 file changed, 1 insertion(+) 现在,我们切换回 master: $ git checkout master Switched to branch 'master' 准备合并 dev 分支,请注意 --no-ff 参数,表示禁用 Fast forward: $ git merge --no-ff -m "merge with no-ff" dev Merge made by the 'recursive' strategy. readme.txt | 1 + 1 file changed, 1 insertion(+) 因为本次合并要创建一个新的 commit,所以加上 -m 参数,把 commit 描述写进去。 合并后,我们用 git log 看看分支历史: $ git log --graph --pretty=oneline --abbrev-commit * 7825a50 merge with no-ff |\ | * 6224937 add merge |/ * 59bc1cb conflict fixed ... 第 5 章 分支管理 | 63 可以看到,不使用 Fast forward 模式,merge 后就像这样: 分支策略分支策略 在实际开发中,我们应该按照几个基本原则进行分支管理: 首先,master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活; 那在哪干活呢?干活都在 dev 分支上,也就是说,dev 分支是不稳定的,到某个时候,比如 1.0 版本发布时,再 把 dev 分支合并到 master 上,在 master 分支发布 1.0 版本; 你和你的小伙伴们每个人都在 dev 分支上干活,每个人都有自己的分支,时不时地往 dev 分支上合并就可以了。 所以,团队合作的分支看起来就像这样: 第 5 章 分支管理 | 64 小结小结 Git 分支十分强大,在团队开发中应该充分应用。 合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward 合并就看不出来曾经做过合并。 第 5 章 分支管理 | 65 Bug 分支Bug 分支 软件开发中,bug 就像家常便饭一样。有了bug就需要修复,在 Git 中,由于分支是如此的强大,所以,每个 bu g 都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。 当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支 issue-101 来修复它,但是,等 等,当前正在 dev 上进行的工作还没有提交: $ git status # On branch dev # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: hello.py # # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: readme.txt # 并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复 该 bug,怎么办? 幸好,Git 还提供了一个 stash 功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作: $ git stash Saved working directory and index state WIP on dev: 6224937 add merge HEAD is now at 6224937 add merge 现在,用 git status 查看工作区,就是干净的(除非有没有被 Git 管理的文件),因此可以放心地创建分支来修 复 bug。 首先确定要在哪个分支上修复 bug,假定需要在 master 分支上修复,就从 master 创建临时分支: $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. $ git checkout -b issue-101 Switched to a new branch 'issue-101' 现在修复 bug,需要把“Git is free software ...”改为“Git is a free software ...”,然后提交: 第 5 章 分支管理 | 66 $ git add readme.txt $ git commit -m "fix bug 101" [issue-101 cc17032] fix bug 101 1 file changed, 1 insertion(+), 1 deletion(-) 修复完成后,切换到 master 分支,并完成合并,最后删除 issue-101 分支: $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 2 commits. $ git merge --no-ff -m "merged bug fix 101" issue-101 Merge made by the 'recursive' strategy. readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) $ git branch -d issue-101 Deleted branch issue-101 (was cc17032). 太棒了,原计划两个小时的 bug 修复只花了 5 分钟!现在,是时候接着回到 dev 分支干活了! $ git checkout dev Switched to branch 'dev' $ git status # On branch dev nothing to commit (working directory clean) 工作区是干净的,刚才的工作现场存到哪去了?用 git stash list 命令看看: $ git stash list stash@{0}: WIP on dev: 6224937 add merge 工作现场还在,Git 把 stash 内容存在某个地方了,但是需要恢复一下,有两个办法: 一是用 git stash apply 恢复,但是恢复后,stash 内容并不删除,你需要用 git stash drop 来删除; 另一种方式是用 git stash pop ,恢复的同时把 stash 内容也删了: $ git stash pop # On branch dev # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: hello.py # # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # 第 5 章 分支管理 | 67 # modified: readme.txt # Dropped refs/stash@{0} (f624f8e5f082f2df2bed8a4e09c12fd2943bdd40) 再用 git stash list 查看,就看不到任何 stash 内容了: $ git stash list 你可以多次 stash,恢复的时候,先用 git stash list 查看,然后恢复指定的 stash,用命令: $ git stash apply stash@{0} 小结小结 修复 bug 时,我们会通过创建新的 bug 分支进行修复,然后合并,最后删除; 当手头工作没有完成时,先把工作现场 git stash 一下,然后去修复 bug,修复后,再 git stash pop ,回到工作 现场。 第 5 章 分支管理 | 68 Feature 分支Feature 分支 软件开发中,总有无穷无尽的新的功能要不断添加进来。 添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好 新建一个 feature 分支,在上面开发,完成后,合并,最后,删除该 feature 分支。 现在,你终于接到了一个新任务:开发代号为 Vulcan 的新功能,该功能计划用于下一代星际飞船。 于是准备开发: $ git checkout -b feature-vulcan Switched to a new branch 'feature-vulcan' 5 分钟后,开发完毕: $ git add vulcan.py $ git status # On branch feature-vulcan # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: vulcan.py # $ git commit -m "add feature vulcan" [feature-vulcan 756d4af] add feature vulcan 1 file changed, 2 insertions(+) create mode 100644 vulcan.py 切回 dev,准备合并: $ git checkout dev 一切顺利的话,feature 分支和 bug 分支是类似的,合并,然后删除。 但是, 就在此时,接到上级命令,因经费不足,新功能必须取消! 虽然白干了,但是这个分支还是必须就地销毁: $ git branch -d feature-vulcan error: The branch 'feature-vulcan' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature-vulcan'. 第 5 章 分支管理 | 69 销毁失败。Git 友情提醒,feature-vulcan 分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需 要使用命令 git branch -D feature-vulcan 。 现在我们强行删除: $ git branch -D feature-vulcan Deleted branch feature-vulcan (was 756d4af). 终于删除成功! 小结小结 开发一个新 feature,最好新建一个分支; 如果要丢弃一个没有被合并过的分支,可以通过 git branch -D 强行删除。 第 5 章 分支管理 | 70 多人协作多人协作 当你从远程仓库克隆时,实际上 Git 自动把本地的 master 分支和远程的 master 分支对应起来了,并且,远程 仓库的默认名称是 origin。 要查看远程库的信息,用 git remote : $ git remote origin 或者,用 git remote -v 显示更详细的信息: $ git remote -v origin git@github.com:michaelliao/learngit.git (fetch) origin git@github.com:michaelliao/learngit.git (push) 上面显示了可以抓取和推送的 origin 的地址。如果没有推送权限,就看不到 push 的地址。 推送分支推送分支 推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推 送到远程库对应的远程分支上: $ git push origin master 如果要推送其他分支,比如 dev,就改成: $ git push origin dev 但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢? • master 分支是主分支,因此要时刻与远程同步; • dev 分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步; • bug 分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug; • feature 分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。 总之,就是在 Git 中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定! 第 5 章 分支管理 | 71 抓取分支抓取分支 多人协作时,大家都会往 master 和 dev 分支上推送各自的修改。 现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把 SSH Key 添加到 GitHub)或者同一台电脑的另一个 目录下克隆: $ git clone git@github.com:michaelliao/learngit.git Cloning into 'learngit'... remote: Counting objects: 46, done. remote: Compressing objects: 100% (26/26), done. remote: Total 46 (delta 16), reused 45 (delta 15) Receiving objects: 100% (46/46), 15.69 KiB | 6 KiB/s, done. Resolving deltas: 100% (16/16), done. 当你的小伙伴从远程库 clone 时,默认情况下,你的小伙伴只能看到本地的 master 分支。不信可以用 git branc h 命令看看: $ git branch * master 现在,你的小伙伴要在 dev 分支上开发,就必须创建远程 origin 的 dev 分支到本地,于是他用这个命令创建本 地 dev 分支: $ git checkout -b dev origin/dev 现在,他就可以在 dev 上继续修改,然后,时不时地把 dev 分支 push 到远程: $ git commit -m "add /usr/bin/env" [dev 291bea8] add /usr/bin/env 1 file changed, 1 insertion(+) 第 5 章 分支管理 | 72 $ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 349 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git fc38031..291bea8 dev -> dev 你的小伙伴已经向 origin/dev 分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送: $ git add hello.py $ git commit -m "add coding: utf-8" [dev bd6ae48] add coding: utf-8 1 file changed, 1 insertion(+) $ git push origin dev To git@github.com:michaelliao/learngit.git ! [rejected] dev -> dev (non-fast-forward) error: failed to push some refs to 'git@github.com:michaelliao/learngit.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (e.g. 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git 已经提示我们,先用 git pull 把最新的提交从 origin/dev 抓下来,然后,在本地合并,解决冲突,再推送: $ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From github.com:michaelliao/learngit 第 5 章 分支管理 | 73 fc38031..291bea8 dev -> origin/dev There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details git pull If you wish to set tracking information for this branch you can do so with: git branch --set-upstream dev origin/ git pull 也失败了,原因是没有指定本地 dev 分支与远程 origin/dev 分支的链接,根据提示,设置 dev 和 origi n/dev 的链接: $ git branch --set-upstream dev origin/dev Branch dev set up to track remote branch dev from origin. 再 pull: $ git pull Auto-merging hello.py CONFLICT (content): Merge conflict in hello.py Automatic merge failed; fix conflicts and then commit the result. 这回 git pull 成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提 交,再 push: $ git commit -m "merge & fix hello.py" [dev adca45d] merge & fix hello.py $ git push origin dev Counting objects: 10, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (6/6), 747 bytes, done. Total 6 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git 291bea8..adca45d dev -> dev 第 5 章 分支管理 | 74 因此,多人协作的工作模式通常是这样: • 首先,可以试图用 git push origin branch-name 推送自己的修改; • 如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并; • 如果合并有冲突,则解决冲突,并在本地提交; • 没有冲突或者解决掉冲突后,再用 git push origin branch-name 推送就能成功! 如果 git pull 提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令 git br anch --set-upstream branch-name origin/branch-name 。 这就是多人协作的工作模式,一旦熟悉了,就非常简单。 小结小结 • 查看远程库信息,使用 git remote -v ; • 本地新建的分支如果不推送到远程,对其他人就是不可见的; • 从本地推送分支,使用 git push origin branch-name ,如果推送失败,先用 git pull 抓取远程的新提交; • 在本地创建和远程分支对应的分支,使用 git checkout -b branch-name origin/branch-name ,本地和远 程分支的名称最好一致; • 建立本地分支和远程分支的关联,使用 git branch --set-upstream branch-name origin/branch-name ; • 从远程抓取分支,使用 git pull ,如果有冲突,要先处理冲突。 第 5 章 分支管理 | 75 66 标签管理标签管理 创建标签创建标签 发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时 候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。 Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针(跟分支很像对不对?但是分支可以移 动,标签不能移动),所以,创建和删除标签都是瞬间完成的。 在 Git 中打标签非常简单,首先,切换到需要打标签的分支上: $ git branch * dev master $ git checkout master Switched to branch 'master' 然后,敲命令 git tag 就可以打一个新标签: $ git tag v1.0 可以用命令 git tag 查看所有标签: $ git tag v1.0 默认标签是打在最新提交的 commit 上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一 打的标签没有打,怎么办? 方法是找到历史提交的 commit id ,然后打上就可以了: $ git log --pretty=oneline --abbrev-commit 6a5819e merged bug fix 101 cc17032 fix bug 101 7825a50 merge with no-ff 6224937 add merge 59bc1cb conflict fixed 400b400 & simple 75a857c AND simple fec145a branch test d17efd8 remove test.txt ... 比方说要对 add merge 这次提交打标签,它对应的 commit id 是 6224937 ,敲入命令: 第 6 章 标签管理 | 77 $ git tag v0.9 6224937 再用命令 git tag 查看标签: $ git tag v0.9 v1.0 注意,标签不是按时间顺序列出,而是按字母排序的。可以用 git show 查看标签信息: $ git show v0.9 commit 622493706ab447b6bb37e4e2a2f276a20fed2ab4 Author: Michael Liao Date: Thu Aug 22 11:22:08 2013 +0800 add merge ... 可以看到,v0.9 确实打在`add merge 这次提交上。 还可以创建带有说明的标签,用 -a 指定标签名, -m 指定说明文字: $ git tag -a v0.1 -m "version 0.1 released" 3628164 用命令 git show 可以看到说明文字: $ git show v0.1 tag v0.1 Tagger: Michael Liao Date: Mon Aug 26 07:28:11 2013 +0800 version 0.1 released commit 3628164fb26d48395383f8f31179f24e0882e1e0 Author: Michael Liao Date: Tue Aug 20 15:11:49 2013 +0800 append GPL 还可以通过 -s 用私钥签名一个标签: $ git tag -s v0.2 -m "signed version 0.2 released" fec145a 签名采用 PGP 签名,因此,必须首先安装 gpg(GnuPG),如果没有找到 gpg,或者没有 gpg 密钥对,就会 报错: 第 6 章 标签管理 | 78 gpg: signing failed: secret key not available error: gpg failed to sign the data error: unable to sign the tag 如果报错,请参考 GnuPG 帮助文档配置 Key。 用命令 git show 可以看到 PGP 签名信息: $ git show v0.2 tag v0.2 Tagger: Michael Liao Date: Mon Aug 26 07:28:33 2013 +0800 signed version 0.2 released -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (Darwin) iQEcBAABAgAGBQJSGpMhAAoJEPUxHyDAhBpT4QQIAKeHfR3bo... -----END PGP SIGNATURE----- commit fec145accd63cdc9ed95a2f557ea0658a2a6537f Author: Michael Liao Date: Thu Aug 22 10:37:30 2013 +0800 branch test 用 PGP 签名的标签是不可伪造的,因为可以验证 PGP 签名。验证签名的方法比较复杂,这里就不介绍了。 小结小结 • 命令 git tag 用于新建一个标签,默认为 HEAD,也可以指定一个 commit id ; • git tag -a -m "blablabla..." 可以指定标签信息; • git tag -s -m "blablabla..." 可以用 PGP 签名标签; • 命令 git tag 可以查看所有标签。 第 6 章 标签管理 | 79 操作标签操作标签 如果标签打错了,也可以删除: $ git tag -d v0.1 Deleted tag 'v0.1' (was e078af9) 因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。 如果要推送某个标签到远程,使用命令 git push origin : $ git push origin v1.0 Total 0 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git * [new tag] v1.0 -> v1.0 或者,一次性推送全部尚未推送到远程的本地标签: $ git push origin --tags Counting objects: 1, done. Writing objects: 100% (1/1), 554 bytes, done. Total 1 (delta 0), reused 0 (delta 0) To git@github.com:michaelliao/learngit.git * [new tag] v0.2 -> v0.2 * [new tag] v0.9 -> v0.9 如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除: $ git tag -d v0.9 Deleted tag 'v0.9' (was 6224937) 然后,从远程删除。删除命令也是 push,但是格式如下: $ git push origin :refs/tags/v0.9 To git@github.com:michaelliao/learngit.git - [deleted] v0.9 要看看是否真的从远程库删除了标签,可以登陆 GitHub 查看。 第 6 章 标签管理 | 80 小结小结 • 命令 git push origin 可以推送一个本地标签; • 命令 git push origin --tags 可以推送全部未推送过的本地标签; • 命令 git tag -d 可以删除一个本地标签; • 命令 git push origin :refs/tags/ 可以删除一个远程标签。 第 6 章 标签管理 | 81 77 使用 GitHub使用 GitHub GitHubGitHub 我们一直用 GitHub 作为免费的远程仓库,如果是个人的开源项目,放到 GitHub 上是完全没有问题的。其实 Git Hub 还是一个开源协作社区,通过 GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。 在 GitHub 出现以前,开源项目开源容易,但让广大人民群众参与进来比较困难,因为要参与,就要提交代 码,而给每个想提交代码的群众都开一个账号那是不现实的,因此,群众也仅限于报个 bug,即使能改掉 bu g,也只能把 diff 文件用邮件发过去,很不方便。 但是在 GitHub 上,利用 Git 极其强大的克隆和分支功能,广大人民群众真正可以第一次自由参与各种开源项目 了。 如何参与一个开源项目呢?比如人气极高的 bootstrap 项目,这是一个非常强大的 CSS 框架,你可以访问它的 项目主页https://github.com/twbs/bootstrap,点“Fork”就在自己的账号下克隆了一个 bootstrap 仓库,然 后,从自己的账号下clone: git clone git@github.com:michaelliao/bootstrap.git 一定要从自己的账号下 clone 仓库,这样你才能推送修改。如果从 bootstrap 的作者的仓库地址 git@github.co m:twbs/bootstrap.git 克隆,因为没有权限,你将不能推送修改。 Bootstrap 的官方仓库 twbs/bootstrap、你在 GitHub 上克隆的仓库 my/bootstrap,以及你自己克隆到本地电 脑的仓库,他们的关系就像下图显示的那样: 如果你想修复 bootstrap 的一个 bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。 如果你希望 bootstrap 的官方库能接受你的修改,你就可以在 GitHub 上发起一个 pull request。当然,对方是 否接受你的 pull request 就不一定了。 第 7 章 使用 GitHub | 83 如果你没能力修改 bootstrap,但又想要试一把 pull request,那就 Fork 一下我的仓库:https://github.com/ michaelliao/learngit,创建一个 your-github-id.txt 的文本文件,写点自己学习 Git 的心得,然后推送一个 pull request 给我。 小结小结 • 在 GitHub 上,可以任意 Fork 开源仓库; • 自己拥有 Fork 后的仓库的读写权限; • 可以推送 pull request 给官方仓库来贡献代码。 第 7 章 使用 GitHub | 84 88 自定义 Git自定义 Git 忽略特殊文件忽略特殊文件 在 安装 Git一节中,我们已经配置了 user.name 和 user.email,实际上,Git 还有很多可配置项。 比如,让 Git 显示颜色,会让命令输出看起来更醒目: $ git config --global color.ui true 这样,Git 会适当地显示不同的颜色,比如 git status 命令: 文件名就会标上颜色。 我们在后面还会介绍如何更好地配置 Git,以便让你的工作更高效。 有些时候,你必须把某些文件放到 Git 工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件 啦,等等,每次 git status 都会显示 Untracked files ...,有强迫症的童鞋心里肯定不爽。 好在 Git 考虑到了大家的感受,这个问题解决起来也很简单,在 Git 工作区的根目录下创建一个特殊的 .gitignor e 文件,然后把要忽略的文件名填进去,Git 就会自动忽略这些文件。 不需要从头写 .gitignore 文件,GitHub 已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配 置文件可以直接在线浏览:https://github.com/github/gitignore 忽略文件的原则是: • 忽略操作系统自动生成的文件,比如缩略图等; 第 8 章 自定义 Git | 86 • 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成 的文件就没必要放进版本库,比如Java编译产生的.class文件; • 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。 举个例子: 假设你在 Windows 下进行 Python 开发,Windows 会自动在有图片的目录下生成隐藏的缩略图文件,如果有 自定义目录,目录下就会有 Desktop.ini 文件,因此你需要忽略 Windows 自动生成的垃圾文件: # Windows: Thumbs.db ehthumbs.db Desktop.ini 然后,继续忽略 Python 编译产生的 .pyc 、 .pyo 、 dist 等文件或目录: # Python: *.py[cod] *.so *.egg *.egg-info dist build 加上你自己定义的文件,最终得到一个完整的 .gitignore 文件,内容如下: # Windows: Thumbs.db ehthumbs.db Desktop.ini # Python: *.py[cod] *.so *.egg *.egg-info dist build # My configurations: db.ini deploy_key_rsa 第 8 章 自定义 Git | 87 最后一步就是把 .gitignore 也提交到 Git,就完成了!当然检验 .gitignore 的标准是 git status 命令是不是说 wo rking directory clean。 使用 Windows 的童鞋注意了,如果你在资源管理器里新建一个 .gitignore 文件,它会非常弱智地提示你必须输 入文件名,但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为 .gitignore 了。 小结小结 忽略某些文件时,需要编写 .gitignore ; .gitignore 文件本身要放到版本库里,并且可以对 .gitignore 做版本管理! 第 8 章 自定义 Git | 88 配置别名配置别名 有没有经常敲错命令?比如 git status?status 这个单词真心不好记。 如果敲 git st 就表示 git status 那就简单多了,当然这种偷懒的办法我们是极力赞成的。 我们只需要敲一行命令,告诉 Git,以后 st 就表示 status: $ git config --global alias.st status 好了,现在敲 git st 看看效果。 当然还有别的命令可以简写,很多人都用 co 表示 checkout,ci 表示 commit,br 表示 branch: $ git config --global alias.co checkout $ git config --global alias.ci commit $ git config --global alias.br branch 以后提交就可以简写成: $ git ci -m "bala bala bala..." --global 参数是全局参数,也就是这些命令在这台电脑的所有 Git 仓库下都有用。 在撤销修改一节中,我们知道,命令 git reset HEAD file 可以把暂存区的修改撤销掉(unstage),重新放回工 作区。既然是一个 unstag e操作,就可以配置一个 unstage 别名: $ git config --global alias.unstage 'reset HEAD' 当你敲入命令: $ git unstage test.py 实际上 Git 执行的是: $ git reset HEAD test.py 配置一个 git last ,让其显示最后一次提交信息: $ git config --global alias.last 'log -1' 这样,用 git last 就能显示最近一次的提交: $ git last commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2 第 8 章 自定义 Git | 89 Merge: bd6ae48 291bea8 Author: Michael Liao Date: Thu Aug 22 22:49:22 2013 +0800 merge & fix hello.py 甚至还有人把 lg 配置成了: git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" 来看看 git lg 的效果: 配置文件配置文件 配置 Git 的时候,加上 --global 是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。 配置文件放哪了?每个仓库的 Git 配置文件都放在 .git/config 文件中: $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = git@github.com:michaelliao/learngit.git fetch = +refs/heads/*:refs/remotes/origin/* 第 8 章 自定义 Git | 90 [branch "master"] remote = origin merge = refs/heads/master [alias] last = log -1 别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。 而当前用户的 Git 配置文件放在用户主目录下的一个隐藏文件 .gitconfig 中: $ cat .gitconfig [alias] co = checkout ci = commit br = branch st = status [user] name = Your Name email = your@email.com 配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。 第 8 章 自定义 Git | 91 搭建 Git 服务器搭建 Git 服务器 在远程仓库 (页 41)一节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了 7x24 小时开机并交换大家 的修改。 GitHub 就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代 码,又舍不得给 GitHub 交保护费,那就只能自己搭建一台 Git 服务器作为私有仓库使用。 搭建 Git 服务器需要准备一台运行 Linux 的机器,强烈推荐用 Ubuntu 或 Debian,这样,通过几条简单的 apt 命令就可以完成安装。 假设你已经有 sudo 权限的用户账号,下面,正式开始安装。 第一步,安装 git: $ sudo apt-get install git 第二步,创建一个 git 用户,用来运行 git 服务: $ sudo adduser git 第三步,创建证书登录: 收集所有需要登录的用户的公钥,就是他们自己的 id_rsa.pub 文件,把所有公钥导入到 /home/git/.ssh/authoriz ed_keys 文件里,一行一个。 第四步,初始化 Git 仓库: 先选定一个目录作为 Git 仓库,假定是 /srv/sample.git ,在 /srv 目录下输入命令: $ sudo git init --bare sample.git Git 就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录 到服务器上去改工作区,并且服务器上的 Git 仓库通常都以 .git 结尾。然后,把 owner 改为 git: $ sudo chown -R git:git sample.git 第五步,禁用 shell 登录: 出于安全考虑,第二步创建的git用户不允许登录 shell,这可以通过编辑 /etc/passwd 文件完成。找到类似下面 的一行: git:x:1001:1001:,,,:/home/git:/bin/bash 第 8 章 自定义 Git | 92 改为: git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell 这样,Git 用户可以正常通过 ssh 使用 git,但无法登录 shell,因为我们为 git 用户指定的 git-shell 每次一登录 就自动退出。 第六步,克隆远程仓库: 现在,可以通过 git clone 命令克隆远程仓库了,在各自的电脑上运行: $ git clone git@server:/srv/sample.git Cloning into 'sample'... warning: You appear to have cloned an empty repository. 剩下的推送就简单了。 管理公钥管理公钥 如果团队很小,把每个人的公钥收集起来放到服务器的 /home/git/.ssh/authorized_keys 文件里就是可行的。如 果团队有几百号人,就没法这么玩了,这时,可以用 Gitosis 来管理公钥。 这里我们不介绍怎么玩 Gitosis 了,几百号人的团队基本都在 500 强了,相信找个高水平的 Linux 管理员问题不 大。 管理权限管理权限 有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人 是否有读写权限会精确到每个分支甚至每个目录下。因为 Git 是为 Linux 源代码托管而开发的,所以 Git 也继承 了开源社区的精神,不支持权限控制。不过,因为 Git 支持钩子(hook),所以,可以在服务器端编写一系列脚 本来控制提交等操作,达到权限控制的目的。Gitolite 就是这个工具。 这里我们也不介绍 Gitolite 了,不要把有限的生命浪费到权限斗争中。 小结小结 • 搭建 Git 服务器非常简单,通常 10 分钟即可完成; • 要方便管理公钥,用 Gitosis; 第 8 章 自定义 Git | 93 • 要像 SVN 那样的控制权限,用 Gitolite。 第 8 章 自定义 Git | 94 更多信息请访问 http://wiki.jikexueyuan.com/project/git-tutorial/
还剩95页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

zhikai

贡献于2017-02-20

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf