构建高质量软件:持续集成与持续交付系统实践
上QQ阅读APP看书,第一时间看更新

3.4 Git与远程仓库

正是由于远程仓库(GitHub)的存在,Git才能具有分布式特质。在Git中,一个本地仓库可以同时绑定一个以上的远程仓库。对远程仓库而言,除了提供对外的网络服务之外,其本质上只是本地仓库的一个副本,本节将基于GitHub创建远程仓库,并实现与本地仓库的协同工作。

3.4.1 远程仓库的管理

在GitHub上创建Git仓库是一件很简单的事情,限于篇幅这里就不展开讲解了,假设我们在GitHub上有一个远程仓库,它的地址为git@github.com:wangwenjun/git_section.git。

有两种做法可以让本地仓库和远程仓库建立绑定关系,第一种做法是使用clone命令获取远程仓库的所有数据对象,包括分支、Tag等,这样的话,本地也会有一个与远程仓库一模一样的仓库。今后所有的提交操作都将在本地直接进行,然后定期将本地仓库提交的变更推送至远程仓库。第二种做法是为本地仓库增加远程仓库的配置,然后将本地仓库中已提交的所有变更全部推送至远程仓库。下面通过示例代码分别演示这两种做法。

(1)使用git clone命令

示例代码如下:

> git clone git@github.com:wangwenjun/git_section.git
Cloning into 'git_section'...
The authenticity of host 'github.com (13.250.177.223)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,13.250.177.223' (RSA) to the list of known hosts.
warning: You appear to have cloned an empty repository.
Checking connectivity... done.
> cd git_section/
> ls -a
#发现多了一个“.git”目录,通过clone命令即可创建一个与远程仓库一模一样的本地仓库,并建立绑定。
.  ..  .git

(2)手动添加远程仓库

示例代码如下:

# 首先在本地工作目录中执行git init命令。
> git init
# 手动添加远程仓库,其中,origin是远程仓库名。
> git remote add origin git@github.com:wangwenjun/git_section.git
# 还可以添加多个远程仓库,其中,https是远程仓库名。
> git remote add https https://github.com/wangwenjun/git_section.git
# 在本地列出所有的远程仓库。
> git remote -v
https   https://github.com/wangwenjun/git_section.git (fetch)
https   https://github.com/wangwenjun/git_section.git (push)
origin  git@github.com:wangwenjun/git_section.git (fetch)
origin  git@github.com:wangwenjun/git_section.git (push)
# 当然,我们也可以删除某个已经添加的远程仓库。
> git remote remove https
> git remote -v
origin  git@github.com:wangwenjun/git_section.git (fetch)
origin  git@github.com:wangwenjun/git_section.git (push)

本地仓库与某个远程仓库建立了绑定关系之后,就可以将本地仓库中的变更提交推送至远程仓库了,由于远程仓库的存在,即使因本地磁盘损坏而导致本地仓库中的所有数据丢失也不用担心,因为远程仓库中存储了一份与本地仓库一样的数据备份,只需要重新clone一次即可。

3.4.2 远程仓库的操作

为本地仓库添加远程仓库后,本地已提交的变更就可以推送至远程仓库中了,其他开发人员也可以从远程仓库中将变更拉取至本地,如图3-13所示。

086-01

图3-13 本地仓库与远程仓库的协同工作

下面的示例代码演示了如何将本地已提交的变更推送至远程仓库,以及如何从远程仓库拉取最新的变更等命令。

# 程序员 A将本地提交的变更推送到远程仓库的操作步骤如下:
> echo "Example">README.md
> git add README.md
> git commit -m "initial commit"
# 将本地变更推送至远程仓库,“--set-upstream”命令可以简写为“-u”。
# origin是配置在本地中的唯一一个表示远程仓库的名字,master是远程仓库的分支。
> git push --set-upstream origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 221 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:wangwenjun/git_section.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.
# 程序员A在本地创建一个新的分支,并在本地进行一些提交。
> git checkout -b dev
> echo "test">test.txt
> git add test.txt
> git commit -m "add the new file test.txt"
#将本地的dev分支推送至远程仓库
> git push -u origin dev
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 289 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'dev' on GitHub by visiting:
remote:      https://github.com/wangwenjun/git_section/pull/new/dev
remote:
To git@github.com:wangwenjun/git_section.git
 * [new branch]      dev -> dev
Branch dev set up to track remote branch dev from origin.
# 目前,本地仓库和远程仓库分别拥有两个分支。
> git branch --all --list
* dev
  master
  remotes/origin/dev
  remotes/origin/master

在上面的示例代码中,程序员A将本地提交的变更推送至远程仓库,并且创建了dev分支,现在,程序员B就可以通过远程仓库拉取到程序员A推送的提交了。

# 程序员 B从远程仓库拉取变更的操作步骤如下:
> git branch --list --all #空无一物。
> git log --oneline #也是空无一物。
# 获取(fetch)远程仓库的提交,但是不会直接应用于本地,执行了fetch命令后,远程仓库的分支、tag等信息会同步到本地仓库(不会直接创建这些分支,需要通过checkout命令创建)。
> git fetch origin
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
From github.com:wangwenjun/git_section
 * [new branch]      master     -> origin/master
 * [new branch]      dev        -> origin/dev
> git branch --list --all
  remotes/origin/dev
  remotes/origin/master
# 如果想要直接拉取并应用远程仓库中的提交,则可以使用pull命令;如果本地仓库中还没有对应的分支,则需要先执行checkout操作。
> git checkout -b master 'origin/master'
> git checkout -b dev 'origin/dev'
# 然后就可以在某个分支中进行拉取操作了。
> git pull origin master

这里需要特别说明的是,在团队协作时,push和pull命令是会经常用到的两个命令,每次在对本地仓库进行变更之前,最好先执行pull操作,拉取远程仓库最新的提交;在完成了本地提交之后,应尽快将提交push至远程仓库,这样做的好处是,能够最大程度地避免因多个人修改同一个文件而引起冲突。

3.4.3 本地仓库与远程仓库的其他协同操作

在了解了本地仓库与远程仓库如何进行交互之后,下面就来讲解如何删除远程仓库的分支和标签(Tag),以及如何回退(reset)已推送至远程仓库的提交等操作。

(1)本地仓库Tag与远程仓库Tag间的协同操作

下面的示例代码展示了如何将本地创建的Tag推送至远程仓库,如何将远程仓库的Tag拉取至本地,如何删除本地Tag并将Tag的删除动作同步至远程仓库。

# 在本地仓库中创建 Tag。
> git tag 'v0.0.1' ed7caa7
# 将本地仓库中的Tag推送至远程仓库。
> git push origin v0.0.1(只推送一个tag。)
> git push origin --tags(将本地仓库的所有Tag推送至远程仓库。)
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:wangwenjun/git_section.git
 * [new tag]         v0.0.1 -> v0.0.1
#将远程仓库中的Tag拉取至本地仓库。
> git tag -l #看不到v0.0.1。
# 执行拉取远程Tag的操作。
> git pull origin tag 'v0.0.1'
From github.com:wangwenjun/git_section
 * [new tag]         v0.0.1     -> v0.0.1
Already up-to-date.
> git show v0.0.1
commit ed7caa74e8734612eee9006d334df24d6e752861
Author: Alex Wang <alex@wangwenjun.com>
Date:   Sat Feb 20 19:28:19 2021 -0800

    initial commit

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..12a719a
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Example

# 删除本地仓库的Tag。
> git tag -d v0.0.1
# 将对本地仓库Tag的删除推送至远程仓库origin。
> git push origin :refs/tags/v0.0.1
# 与上面的命令等价。
> git push origin --delete v0.0.1
# 这样一来,远程仓库的v0.0.1 Tag也会被删除。

(2)本地仓库分支与远程仓库分支间的协同操作

下面的示例代码展示了如何将本地创建的分支推送至远程仓库,如何从远程仓库拉取分支至本地仓库,如何删除本地分支并将分支的删除动作同步至远程仓库。

# 在本地创建新的分支 test。
> git checkout -b test
#将变更提交至本地仓库。
> touch a && git add a && git commit -m "add new file a"
# 推送(push)本地变更和新的分支test。
> git push --set-upstream origin test
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 304 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'test' on GitHub by visiting:
remote:      https://github.com/wangwenjun/git_section/pull/new/test
remote:
To git@github.com:wangwenjun/git_section.git
 * [new branch]      test -> test
Branch test set up to track remote branch test from origin.
# 查看远程仓库的分支。
> git branch -r
  origin/dev
  origin/master
  origin/test

# 删除本地分支。
> git branch -d test
# 删除远程仓库的分支。
> git push origin --delete test
To git@github.com:wangwenjun/git_section.git
 - [deleted]         test
# 再次查看远程仓库的分支。
> git branch -r
  origin/dev
  origin/master

(3)回退远程仓库的提交

如果不小心把错误的变更提交到了本地仓库,并且推送到了远程仓库,那么是否可以借助于reset命令回退到某个正确的版本呢?答案是可以的,下面的示例代码展示了如何使用reset命令删除推送至远程仓库的提交。

# 将错误的变更提交到本地仓库。
> echo "error"> error.txt && git add error.txt && git commit -m "error commit"
# 将错误的提交推送至远程仓库。
> git push -u origin dev
> git log --oneline
8822b3a error commit
3eb4cc6 add the new file test.txt
ed7caa7 initial commit
# 由于8822b3a 是一次错误的提交,因此现在要让HEAD指向3eb4cc6。
> git reset --hard origin/dev^
HEAD is now at 3eb4cc6 add the new file test.txt
# 使用“--force”参数强制push至远程仓库。
> git push --force origin dev
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:wangwenjun/git_section.git
 + 8822b3a...3eb4cc6 dev -> dev (forced update)
> git log --graph --decorate --pretty=oneline --abbrev-commit
* 3eb4cc6 (HEAD -> dev, origin/dev) add the new file test.txt
* ed7caa7 (tag: v0.0.1, origin/master, master) initial commit

经过上面的操作,本地仓库和远程仓库中都删除了8822b3a提交,但是通常情况下,我们不建议使用reset命令进行undo(回退)操作,虽然reset命令能够减少历史提交记录,但是这种做法毕竟是有一定风险性的(很容易出现数据丢失的问题)。这里还是推荐大家基于最新的提交进行修改,比如,新增数据文件、修改数据文件,或者删除某个文件,然后再次提交,这样的话所有的操作都会记录在案,即使出现了操作失误的情况,也可以基于历史提交记录进行恢复。