[洛谷日报#199]Git 简单上手指南

StudyingFather

2019-05-10 20:51:22

Personal

## Part 1 关于分布式版本控制 Git 是目前使用最广泛的分布式版本控制系统,在很多大型项目的背后都能见到 Git 的身影。 那么,什么是**分布式版本控制系统**?Git 又有什么用呢? 版本控制,顾名思义,就是对一个或一些文件的变化进行记录,并在以后查阅具体修订情况的系统。 最简单的版本控制就是存储同一个文件的不同版本,像这样: ```bash document-2019-2-15.md document-2019-2-16-1.md document-2019-2-16-2.md ... ``` 这样做的弊端是很明显的,进行修改的时候容易错误修改之前的版本,而且在查询具体的某一次更改的时候也非常麻烦。 而成熟的版本控制系统,则拥有了更完善的版本控制功能:你可以方便地查阅对代码库的某一次修改,比对任意两个版本之间的差别,并在出现 bug 的时候,及时找到 bug 出现的原因,或是回退到之前的版本。在版本控制系统的帮助下,这些操作都将变得非常容易。 那么,相比于**集中式**版本控制,**分布式**版本控制有什么优势呢? 集中式版本控制,顾名思义,就是在一个集中式的服务器上进行版本控制,这个服务器保存了所有版本信息,用户只需连接到服务器,就可以读取文件,推送更新。 ![](https://cdn.luogu.com.cn/upload/pic/58409.png) 对于管理员而言,集中式版本控制方便了项目的维护,他们只需控制好服务器就可以了;而对于开发者,他们可以从服务器里获得其他人的工作信息,大大提升了开发效率。 然而,正是因为它集中性,它也就变得脆弱了。一旦服务器宕机,因为所有开发者都没有版本信息,也就无法再继续协作,更糟糕的是,一旦数据丢失,诸如版本信息之类的,就再也找不到了——开发者所拥有的,只是项目的一个快照而已。 于是,分布式版本控制系统就应运而生了。 分布式版本控制系统之下,不仅仅服务器存储了版本信息,每个客户端也都存储了版本信息。 ![](https://cdn.luogu.com.cn/upload/pic/58411.png) 在这种情况下,即使服务器宕机,开发者也可以从其他开发者那里获得版本信息,继续协作。 你会发现我们甚至并不需要专门的服务器来维护版本信息,是的,每个客户端都可以视为一个服务器。 正是因为分布式版本控制的灵活性,我们可以实现很多在集中式管理系统里实现不了的功能,例如同时与多个小组的人协作,进行多分支开发。 我们接下来介绍的 Git ,正是一个非常易用的分布式版本管理系统。 ## Part 2 Git 的安装 对于 Debian/Ubuntu 用户,只需在命令行执行 `sudo apt install git` 即可安装 Git。 其他 Linux 发行版用户可以在 [这里](https://git-scm.com/download/linux) 查看安装方式。 对于 Windows 和 Mac 用户,请前往 [Git 官网](https://git-scm.com/downloads) 下载安装包。 Git 在 Windows 下的安装过程比较繁琐,这里做些必要的说明: 1. 在选择安装路径后,会提示您选择偏好的编辑器(用于编辑 commit 信息等),这时您可以根据喜好,选择您愿意使用的编辑器。(笔者采用的是 Notepad++) ![](https://cdn.luogu.com.cn/upload/pic/58414.png) 2. 剩下的选项,如果您对具体含义不太熟悉的话,建议按照默认选项安装。 在安装结束后,您就获得了 Git Bash, Git cmd, Git GUI 三个应用。 Git cmd 属于已经弃用的应用,这里不再叙述。 Git GUI 是 Git 的图形化版本,支持很多较为简单的操作,适合初学者使用。但它对很多高级操作并不支持。 Git bash 是 Git 的命令行模式,你可以执行所有 Git 的操作。当然,所有命令行操作你也可以在 Windows 下的 cmd 或是 Powershell 下进行,不过使用 Git bash 更为方便。 **接下来的内容将基于 Git 的命令行模式(也就是 Windows 下的 Git bash )展开。** ## Part 3 配置信息 在安装 Git 后,你需要先设置用户名和邮箱,这些信息将作为提交者的身份标识。 ``` $ git config --global user.name StudyingFather # 设置用户名 $ git config --global user.email [email protected] # 设置邮箱 ``` 命令中的 `--global` 代表了配置对系统上的所有仓库均有效。假如只是对单个仓库的配置,只需要在你想要配置的仓库下执行去掉 `--global` 的命令即可。 注意:如果用户名中间有空格,记得用引号将用户名包起来。 接下来是设置文本编辑器: ``` $ git config --global core.editor vim ``` 当然,如果你愿意,也可以采用 Emacs 等编辑器。 ## Part 4 仓库操作基础 ### Part 4.1 新建 Git 仓库 新建一个 Git 仓库非常简单,只需在你想要建立仓库的文件夹输入如下命令: ``` $ git init ``` Git 将在当前文件夹新建一个 `.git` 文件夹,一个仓库就这样建好了。 如果别人已经建立了一个仓库,你想要把这个仓库拷贝到自己的电脑上,采用 `git clone` 命令即可。像这样: ``` $ git clone https://github.com/SFOI-Team/luogu-problem-list ``` 这样在当前文件夹下,就会新建一个名为 `luogu-problem-list` 的仓库,里面存放着原来仓库的所有信息。 ### Part 4.2 跟踪文件 在我们对仓库做出了一些修改后,我们需要将这些修改纳入版本管理当中去。 使用 `git status` 命令可以查看当前仓库文件的状态。 假设我们在一个空仓库中新增了一个 `README.md` 文件,执行 `git status` 命令的效果如下: ``` $ mkdir test $ cd test $ git init # 在 test 文件夹下建立一个新仓库 $ vi README.md # 写点东西 $ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) README.md nothing added to commit but untracked files present (use "git add" to track) ``` 这里的 Untracked files 指的是 Git 之前没有纳入版本跟踪的文件。 **如果文件没有纳入版本跟踪,我们对该文件的修改不会被 Git 记录。** 让我们把这个新文件纳入版本跟踪。 ``` $ git add README.md $ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.md ``` 这时 README.md 已经纳入了版本跟踪,放入了暂存区。 我们接下来只需执行 `git commit` 命令就可以提交这次更改(也就是让 Git 把我们这一次更改的内容记录下来)。 但在我们干这个之前,先让我们再对 README.md 做点小修改。 ``` $ vi README.md # 再随便修改点东西 $ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.md 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: README.md ``` 你会发现 README.md 同时处于暂存区和非暂存区。这时如果我们提交更改,会发生什么呢? 我们可以试一试: ``` $ git commit # 接下来会弹出编辑器页面,你需要写下 commit 信息 [master (root-commit) b13c84e] test 1 file changed, 2 insertions(+) create mode 100644 README.md $ git status On branch master 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: README.md no changes added to commit (use "git add" and/or "git commit -a") ``` **我们会发现:处于暂存区的修改,在运行 `git commit` 命令后会被提交,而非暂存区的修改,则不会被提交。** 正如 Git 提示的那样,假如想要提交非暂存区的更改的话,只需输入 `git add MEADME.md` 就可以将这次更改放入暂存区,然后执行 `git commit` 就可以提交更改。 ### Part 4.3 提交更新 上文提到的 `git commit` 命令可以将**暂存区内的更改**提交。 让我们回顾一下刚才进行的那次 commit 操作。 ``` [master (root-commit) b13c84e] test 1 file changed, 2 insertions(+) create mode 100644 README.md ``` `master` 表示当前位于 `master` 分支(关于分支的问题,下文将会详细介绍),`b13c84e` 表示本次提交的 SHA-1 校验和的前几位。后面则是本次提交的信息。 接下来两行则详细说明了本次更新涉及的文件修改。 另外,在 commit 的时候采用 `-m` 选项可以在输入命令的时候直接输入本次提交的信息,从而避免了再打开编辑器的麻烦。 ``` $ git add README.md # 先把之前还没有纳入暂存区的更改放入暂存区 $ git commit -m "update README.md" # 引号里的就是本次commit的信息 [master cf78ab3] update README.md 1 file changed, 1 insertion(+) ``` 现在我们再执行一下 `git status` 命令,查看一下仓库状态。 ``` $ git status On branch master nothing to commit, working tree clean ``` 这时候 Git 提醒我们,我们没有任何未提交的更改了。 ### Part 4.4 查看提交记录 使用 `git log` 命令可以查看我们的提交历史记录。 可以看到,提交历史里记录了每次提交时的 SHA-1 校验和,提交的作出者,提交时间和 commit 信息。 ``` $ git log commit cf78ab3c198337acf508d90924328d476c2e5783 (HEAD -> master) Author: StudyingFather <[email protected]> Date: Fri May 10 22:39:27 2019 +0800 update README.md commit b13c84ee799c0fc4c3420814d95c03ea1823ebe3 Author: StudyingFather <[email protected]> Date: Fri May 10 22:25:56 2019 +0800 test ``` 当然,`git log` 命令有很多非常不错的用法,可以让我们看到更多详细的信息,因为篇幅原因这里不再赘述,想要了解的可以参考 [这里](https://git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E6%9F%A5%E7%9C%8B%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2)。 ### Part 4.5 分支管理 试想我们需要开发一个新功能,直接在主线上进行开发是非常危险的举动。这时我们就需要新开一个分支进行修改了。 让我们在原来的 test 仓库下开一个新的开发分支: ``` $ git branch dev # 创建一个叫做 dev 的新分支 $ git checkout dev # 切换当前分支到 dev Switched to branch 'dev' $ git branch # 查看所有分支信息 master * dev ``` dev 前面的星号代表我们当前的分支为 dev,我们接下来的修改都将记录在这个分支上。 我们可以在这个开发分支上做点小修改: ``` $ vi test.cpp $ git add test.cpp $ git commit -m "QAQ" [dev 4fe4923] QAQ 1 file changed, 8 insertions(+) create mode 100644 test.cpp ``` 在新分支上修改似乎和在 master 分支上修改没什么太大区别,事实上也确实如此,每个分支都是平等的。 让我们回到 master 分支,再新建一个文件: ``` $ git checkout master $ vi qwq.cpp $ git add qwq.cpp $ git commit -m "QwQ" [master f793578] QwQ 1 file changed, 6 insertions(+) create mode 100644 qwq.cpp ``` 你也许会奇怪,我们作出的这两次更改究竟有什么区别呢?让我们结合下面这幅图来理解: ![](https://cdn.luogu.com.cn/upload/pic/58420.png) 我们事实上是在两个不同的分支进行开发,分支之间互不干扰。 当另外一个分支已经完成工作时,我们可以通过`git merge`命令合并两个分支。 ``` $ git merge dev Merge made by the 'recursive' strategy. test.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test.cpp ``` 可以看到,我们在 dev 分支上的更改被完美地合并到了 master 分支上。 ![](https://cdn.luogu.com.cn/upload/pic/58422.png) 合并后,两个分支指向了同一个提交。 当然,合并工作也并不总是这么顺利,例如我们在两个分支上同时修改一个文件的时候。 ``` $ git checkout dev # 切换到dev分支 Switched to branch 'dev' $ vi test.cpp # 在dev分支上修改test.cpp文件 $ git add test.cpp $ git commit -m "add notes" [dev 45cbf3d] add notes 1 file changed, 1 insertion(+), 1 deletion(-) $ git checkout master $ vi test.cpp # 在master分支上修改test.cpp文件 $ git add test.cpp $ git commit -m "some small changes" [master 7beb56a] some small changes 1 file changed, 2 insertions(+), 2 deletions(-) $ git merge dev # 尝试合并master分支和dev分支 Auto-merging test.cpp CONFLICT (content): Merge conflict in test.cpp Automatic merge failed; fix conflicts and then commit the result. ``` 这时 Git 发出警告,提示出现了冲突。 **当两个分支对同一文件进行不同的修改的时候,自动合并就无法正常进行。** 这时 `git status` 会告诉我们冲突文件的情况: ``` $ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: test.cpp ``` 接下来我们的工作就是解决冲突。 我们打开出现冲突的文件 test.cpp ,会发现一些奇怪的现象: ```cpp #include <cstdio> int main() { int a,b; <<<<<<< HEAD scanf("%d%d",&a,&b);//input two nums printf("%d\n",a+b);//output the sum return 0; ======= scanf("%d%d",&a,&b); printf("%d\n",a+b); return 0;//end the program >>>>>>> dev } ``` `=======`上面的部分代表了 master 分支该文件的状态,下面的部分代表了 dev 分支该文件的状态,我们需要手动修改这个文件来解决冲突,像这样: ```cpp #include <cstdio> int main() { int a,b; scanf("%d%d",&a,&b);//input two nums printf("%d\n",a+b);//output the sum return 0;//end the program } ``` 保存后退出,将刚刚的修改纳入暂存区,并完成合并: ``` $ git add test.cpp On branch master All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: test.cpp $ git commit [master 14fcf92] Merge branch 'dev' ``` 好了,我们就这样解决了合并冲突的问题。 当一个分支的工作已经合并到其他分支上,不再需要该分支的时候,使用`git branch -d`就可以安全删除该分支了。 ``` $ git branch -d dev # 删除已经没用的 dev 分支 ``` ### Part 4.6 远程仓库 我们可以将我们对仓库的修改提交到远程仓库(例如托管在 Github 上的仓库)。 先让我们添加一个远程仓库: ``` $ git remote add origin https://github.com/StudyingFather/my-code $ git remote # 显示当前所有远程仓库 origin ``` 移除远程仓库也很容易: ``` $ git remote rm origin ``` 当我们在远程仓库作出一些更改时,需要将这些更改拉取到本地时,请使用 `git fetch` 命令。 ``` $ git fetch origin # 将 origin 上的修改拉取到本地 ``` **需要注意,执行完 `git fetch` 命令后,只会抓取数据而不会合并,合并工作需要自己完成。** 要想在抓取的同时将远程仓库的进度和本地合并,可以使用 `git pull` 命令。 ``` $ git pull origin master # 抓取 origin 的数据并自动和本地的 master 分支合并 ``` 当我们完成了修改,使用`git push`命令可以将修改推送至远程仓库。 ``` $ git push origin master # 将 master 分支的数据推送至 origin ``` **在推送更改时,需要注意下面几点:** 1. **如果是第一次向远程仓库推送修改,Git 会要求输入远程仓库的账号和密码。** 2. **和分支合并类似,当远程分支有新更改而当前分支没有时,推送操作会失败。这时请执行 `git pull` 命令完成合并再提交。** ## Part 5 Github 入门使用指南 Github 是全世界规模最大的 Git 仓库托管平台,因此学会使用 Github 非常重要。 ### Part 5.1 认识 Github 中的仓库 限于篇幅,这里只讲述一些 Github 上远程仓库的基本操作,更高级的操作可以在 Github 里的帮助页看到。 点击右上角的加号,选择 new repository 就可以新建一个仓库。 Github 上的仓库分为两种:公开仓库和私有仓库。私有仓库对外不可见,您可以根据需要自己设置(后期可以更改)。 如果本地已经建好仓库,需要导入到 Github ,下面的初始化步骤(包括创建 README.md 文件等)请跳过。 一个公开仓库大概是长这样的: ![](https://cdn.luogu.com.cn/upload/pic/62932.png) 右上角三个按钮的含义如下: * watch:设置关于该仓库的更新通知。 * star:给这个仓库加星。 * fork:clone 一份该仓库的副本,你可以对该副本进行编辑。 如果您需要获取该仓库的源代码,点击 clone or download 按钮,可以获取该仓库的链接,您可以按照上文介绍的方法 clone ,当然也可以选择 Download zip 下载源代码压缩包。 在 Github 上可以直接对文件进行编辑,但过程相较于本地编辑更加繁琐,因此还是推荐大家在本地编辑后将更改提交至远程仓库。 ### Part 5.2 协作开发 如果你不拥有对一个仓库的所有权,则不能对该仓库进行直接编辑,在这种情况下,你可以通过如下几种方式参与协作开发。 1. Issue:在使用中遇到的 bug 和想要提出的建议都可以在这里提出。 2. Pull Request:你可以对 fork 后得到的仓库的副本进行更改,并通过 PR 的方式将该更改合并到主仓库上。 前者因为细节不多,这里不再赘述。 我们重点介绍一下 Pull Request(PR) 的操作方法。 首先,请点击 fork 按钮,在你的账户下创建一份当前仓库的副本。 接下来你可以对这个副本进行一些修改。(如果在本地修改,别忘了推送到远程仓库) 在修改完后,进入到你自己的仓库页面,会发现有一个 Pull request 按钮。点击它就可以创建一个合并请求了。 像这样: ![](https://cdn.luogu.com.cn/upload/pic/66271.png) **为了方便他人了解你的 PR 的内容,请给你的 PR 起一个有意义的标题,并在内容框里填写你这一次 PR 的主要更改。** 接下来项目的维护者会根据情况决定是否接受你的合并请求。在此期间,你可以利用评论区与开发者(以及其他协作者)进行交流。 如果没有合并冲突,而且你的更改确实有帮助的话,你的更改就有可能被合并到主仓库上,这样你就完成了一次协作开发。 如果在创建 PR 后,你又想作出一些更改,只需要在原来的分支上继续推送更改即可,这些更改会自动追加进合并请求当中。 ## Part 6 总结 Git 作为分布式代码管理的强力工具,能帮我们解决项目开发与版本管理的诸多问题,配合 Github 这一全球最大的代码仓库托管网站,更为开源软件社区的发展提供了无限可能。 限于篇幅,Git 的很多高级操作本文都没有介绍到,感兴趣的读者可以自行查找资料。 ## Reference * [Pro Git book(v2电子版)](https://git-scm.com/book/zh/v2)