前言
本篇最早写于 Heabot,点此处查看 Heabot 版本。
版本控制工具
如果你和四个同事一起开发一个软件(或游戏),要如何保证每个人都能正确地获得另外四个人修改过的代码?如果一段代码出了问题,如何找到是谁最后修改了这段代码?如果软件(或游戏)突然有一天出现了严重的崩溃,要如何回退到上一个正常的版本?
而这就是为什么我们需要版本控制工具。它管理每个人对代码的修改权限,处理不同人代码的合并,跟踪每一行代码的修改历史与修改者,对于合作开发而言是必须的工具。
有人会说:那我从来都是单打独斗,就不需要版本控制工具了吧?并非如此。即使单从代码回退这一个功能上来看,任何项目都应当使用版本控制工具。
中央式版本控制及分布式版本控制
最初,版本控制工具都是中央式系统,即工具只维护一个远程仓库,所有修改记录都储存在服务器上,任何人想要向仓库提交代码,必须先从远程仓库拉取所有修改,任何人想要回退代码,或查看修改记录,都必须保持在能够连接到服务器的环境下才能工作。从 RCS,CVS 到 Subversion 都采取了这种工作方式。
进入21世纪,一些新生版本控制系统,比如 BitKeeper 以及我们的主角 git,选择了另一种方式:分布式版本控制。分布式版本控制的特点是,他维护本地仓库,所有的修改历史都储存在项目目录下一个名为 .git
的文件夹里,这样即使在无网络情况下,程序员仍然能正常使用版本控制工具。
源代码托管服务平台
首先需要明确的是,GitHub 与 git 是两个不同的概念。GitHub 是一个源代码托管服务平台,允许将代码仓库托管在它的服务器上,以方便不同程序员维护同一个仓库,除它之外还有 GitLab,BitBucket,Gitee 等等平台。通常来说,在同一局域网内的项目可以直接使用局域网操作 git,如果没有局域网条件,大部分闭源项目也会选择 GitLab 之类的来托管代码,可以说,GitHub 现在的定位更像是开源代码分享平台。
关于git
说起git就不能不吹一手林纳斯·托瓦兹(Linus Torvalds),他是 Linux 内核最早的开发者,为了管理 Linux 内核的源代码,他发起了 git 这一项目。git 可以在其官网下载到,Windows 端建议手动将它的目录加入到环境变量的 PATH 中,Linux 和 MacOS 端则建议使用包管理器来安装 git 而不是去官网下载。
初次使用 git,需要设置用户名和邮箱:
1
2
git config --global user.name 用户名
git config --global user.email 邮箱
这并非是在注册账号,事实上你可以随便填写,这些信息是显示在代码的修改历史中的。
基础操作
查看 git 帮助文档:
1
git --help
更详细地,查看某个特定命令的帮助:
1
git 命令 --help
想将一个现有项目加入到 git 的控制下很简单,在 cmd/PowerShell 或 Linux 终端里,使用 cd
命令进入项目目录,然后键入:
1
git init
要区分暂存(stash)和提交(commit)的区别,暂存是将修改加入到索引中,提交是将已经暂存的内容记录到历史中。提交一般而言都是实现了一个完整的功能或模块,提交必须要写本次提交的信息(message),版本回退时就根据这些信息决定回退到哪个提交。暂存一般是还没写完整个功能,但是确定这一块已经没啥问题了,就暂时保存一下,如果之后代码出现了错误,通过 git 的功能可以看到哪些代码是已经暂存的,哪些是没有暂存的,方便寻找出问题的代码。
要暂存某个特定的文件/文件夹,使用:
1
git add 文件路径
想暂存所有修改,将文件路径换成点即可:
1
git add .
注意,提交只会提交暂存过的代码,没有暂存的代码是不会被提交的!通常提交使用的命令是:
1
git commit -m "消息"
如果你不喜欢暂存,想提交的时候就全部提交了,可以添加a参数,相当于在此之前执行一次git add .
:
1
git commit -a -m "消息"
要查看当前代码和上一次提交相比哪些文件发生了暂存/修改/添加/删除,使用:
1
git status
如果你想把某个文件/文件夹恢复到上次暂存时的状态,使用:
1
git checkout -- 文件路径
注意两个杠两边有空格。
如果想把所有文件/文件夹都恢复到上次暂存的状态,使用:
1
git checkout .
如果想把某个文件/文件夹恢复到上次提交时的状态,使用:
1
git reset HEAD 文件路径
同样,想恢复所有文件/文件夹到上次提交的状态,使用:
1
git reset HEAD .
要查看提交历史,使用:
1
git log
通常一个提交的格式如下:
1
2
3
4
5
commit 7007c77594a8e37bd26c52a6929f00015ccf8aa5
Author: Nichts Hsu <NichtsVonChaos@gmail.com>
Date: Thu Nov 14 14:40:11 2019 +0800
init
第一行是 commit 句头,紧接着是本次提交的哈希值。第二行是提交者的名字和邮箱,第三行是提交日期,第四行为空,第五行是提交时填写的消息。
想要把代码回退到某个提交,可以先使用 git log
找到想回退的提交,然后将其哈希值复制下来填入到:
1
git reset 提交哈希值
注意它不会把你还未提交的修改删除,如果想要删除未提交的修改,则可以加入 hard 参数:
1
git reset --hard 提交哈希值
分支
对于多人合作而言,每个人使用一个分支几乎是必须的事情,否则很容易干扰到别人。即使是一个人工作,有时候可能也需要为了测试什么东西,或者固定一个版本而新建分支。这时可以使用:
1
git branch 分支名
查看已有分支去掉分支名参数即可:
1
git branch
切换工作分支:
1
git checkout 分支名
对于多人合作而言,你可能会想把别人的分支的修改合并到自己的分支里,则可以使用:
1
git merge 合并来源分支
注意:合并可能会出现冲突,建议使用专门的软件(如 GitHub Desktop,Gitkraken,VSCode 等)来处理冲突。你也可以找到每个冲突的文件自己一个个解决冲突,然后再提交。
冲突的地方 git 会做以下标记:
1
2
3
4
5
<<<<<<< HEAD
当前分支的冲突代码
=======
合并来源分支的冲突代码
>>>>>>> 合并来源的分支名
通过全局搜索 <<<<<
可以快速找到冲突位置。
删除一个不需要的分支:
1
git branch -d 分支名
重命名分支:
1
git branch -m 旧分支名 新分支名
远程仓库
此处我们以 GitHub 为例。如何在 GitHub 申请账号、创建仓库与 git 并无关系,因此不做详细讲解。当你创建好远程仓库后,或者你想把别人的仓库下载到本地,则可以:
1
git clone 远程仓库地址
如果你在本地已经有一个项目了,想要关联到远程仓库,则可以用:
1
git remote add origin 远程仓库地址
注意,origin 只是代表默认服务器主机,你可以为一个项目添加许多服务器主机,不过一般很少用到。
查看本地仓库关联的远程仓库:
1
git remote -v
想要修改本地仓库所关联的远程仓库,可以使用:
1
git remote set-url origin 新的远程仓库地址
想要把本地修改推送到远程仓库,使用:
1
git push origin 分支名
推送所有分支的修改,或者说你没有修改过其他分支,则可以简略为:
1
git push
推送分支前需要先拉取远程仓库的修改:
1
git pull origin 分支名
同样拉取所有分支简略为:
1
git pull
拉取远程仓库会将修改合并到本地,如果你只想看云端有什么新的提交记录,而不想合并修改到本地,则可以使用:
1
git fetch
也就是说,pull 相当于 fetch + merge。
在合并分支时,如果合并来源是远程仓库,则分支名需要使用 origin/分支名的格式。
删除云端的分支使用:
1
git push origin --delete 分支名
由于 git 在 push 之前必须 pull,但是有时候你想覆盖掉远程仓库的修改,只保留本地的修改并推送到远程仓库,则可以使用:
1
git push --force
使用此命令前请务必和你的同事沟通好!否则你很可能让同事的心血毁于一旦。
上游
什么是上游?一个开源项目,他并不属于你,你也没有写入的权限,这时候,你就可以通过 GitHub 上的 fork 功能,将这个项目拷贝到你的账号下,这样你就可以随便修改这个副本了。对于你这份副本而言,原始项目就是上游。同样,对于原始项目而言,你这份副本是下游。
为项目设置上游很简单,就是将主机参数 origin 换成了 upstream:
1
git remote add upstream 上游远程仓库地址
其余操作也是如此,只需要把 origin 换成 upstream 就行。
但是要注意的是,如果你没有上游仓库的写入权限,git push upstream
是必定失败的。
如果你觉得自己写的代码好,想让原作者也接收这份代码,则可以先push到自己 fork 的副本中,然后在 GitHub 里发起一个 Pull Request 给原作者,原作者看到并检查之后,觉得确实好,那么他会接受你的 Pull Request,这时候你可以在上游仓库的 contributors 里看到自己的头像和 id。