Git 学习笔记(二)

1 远程仓库

1.1 在本地添加远程仓库

现在最为流行的 Git 应用是 GitHub,因此这里以 GitHub 为例。

上传至 GitHub 有两种协议,一种是 HTTP,一种是 SSH。两者最大的区别在于,每次从本地上传至 GitHub 时,HTTP 需要输入密码,而 SSH 在经过一系列配置后是不需要的,很方便。

关于 SSH 的配置如下:

  • ssh-keygen -t rsa -C "youremail@example.com" 你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可。
  • 如果一切顺利的话,可以在用户主目录里找到 .ssh 目录,里面有 id_rsa 和 id_rsa.pub 两个文件,这两个就是 SSH Key 的秘钥对,id_rsa 是私钥,不能泄露出去,id_rsa.pub 是公钥,可以放心地告诉任何人。
  • 登陆 GitHub,打开 “Account settings”,进入 “SSH Keys” 页面,然后点击 “Add SSH Key”,填上任意 Title,在 Key 文本框里粘贴 id_rsa.pub 文件的内容。

如果之前已将 SSH 配置好,则可以忽略上面的步骤。

接下来打开 GitHub 主页,登陆后新建一个 Repository。假设远程仓库名为 Test,我的用户名为 username。

然后在本地的某个 .git 目录所在的父目录里,执行下面的语句:

对于 HTTP: git remote add origin https://github.com/username/Test.git

对于 SSH: git remote add origin git@github.com:username/Test.git

注意:这里将远程仓库名用 origin 表示,这是 Git 的默认表示,也可以改成别的。

1.2 克隆远程仓库到本地

git clone git://github.com/username/repositoryName.git

上面的命令会默认在当前目录下添加一个和远端仓库名相同名字的目录。如果想重新取一个名字,可以在上面命令的后面加上自定义的目录名: git clone git://github.com/username/repositoryName.git customName

git clone 命令除了克隆远程仓库的所有内容到本地外,还会:

  • 自动将远程仓库表示为 origin
  • 根据远程仓库中的每个分支指针,在本地仓库中分别建立本地分支指针(名字相同),使其指向远程仓库中分支指针所指向的 commit 节点在本地仓库的拷贝。
  • 并且在每个本地分支指针(还包括 HEAD 指针)的基础上分别建立一个对应的远程分支指针。

    设本地任意一个分支指针名为 branch,则对应的远程分支指针名为 origin/branch
  • 自动建立 跟踪 关系。

    设本地任意一个分支指针名为 branch,则会自动将其变成跟踪分支指针,使其跟踪 origin/branch 分支指针。

1.3 在本地删除以及重命名远程仓库

删除远程仓库: git remote rm 远程仓库名

重命名远程仓库: git remote rename 原远程仓库名 更新后的远程仓库名

1.4 查看所有的远程仓库的简略信息

git remote -v

1.5 查看某个远程仓库的详细信息

git remote show 远程仓库名

2 远程分支与跟踪分支

2.1 远程分支

远程分支实质上是本地仓库中的一个指针(并非本地分支指针),可以将它当作是最近一次连接到远程服务器时远程仓库中的某个分支指针被保存到本地仓库的一个书签。

当执行 clone, fetch, pull 操作时,都会在本地仓库自动建立或者更新远程仓库中每个分支的远程分支指针,使其指向远程仓库中分支指针所指向的 commit 节点在本地仓库的拷贝。

设远程仓库名为 origin,其中的一个分支指针名为 remoteBranch,则所建立的远程分支名为 origin/remoteBranch

.git/refs/remotes 中的每个目录下的所有文件保存了所有远程分支指针的信息。

2.2 跟踪分支与 upstream

跟踪分支 (tracking branch) 也是一个本地仓库中的分支指针,它与某个本地远程分支指针相对应(跟踪这个本地远程分支指针)。一旦建立了跟踪关系,被跟踪的分支指针就叫做跟踪分支的 upstream,而跟踪分支叫做这个被跟踪分支的 downstream

跟踪关系是执行 pull 和 push 操作的必需条件,但并不是 fetch 操作的必需条件。 当执行 pull 的时候,需要知道 downstream;当执行 push 的时候,需要知道 upstream。

当执行 clone 操作时,会在本地仓库自动建立远程仓库中每个分支的远程分支指针的同时,还会自动建立一个跟踪分支,使其指向远程仓库中分支指针所指向的 commit 节点在本地仓库的拷贝。

除此之外,还可以通过如下命令自动建立一个跟踪分支: git checkout -b 本地分支名 远程分支名

2.3 针对本地已存在的分支建立与移除跟踪关系

git clone 与 git checkout 操作是建立一个新的分支,并将其作为跟踪某个远程分支的跟踪分支。如果要建立一个本地已存在的分支与某个远程分支的跟踪关系,则可以执行: git branch --set-upstream-to=某个本地远程分支 本地分支 或者 git branch -u 某个本地远程分支 本地分支

移除某个本地分支指针的所有跟踪关系(将其变为一个普通的本地分支指针): git branch --unset-upstream 本地分支

2.4 查看所有跟踪分支的相关信息

git branch -vv 该命令除了可以查看分支信息外,还可以查看跟踪信息。

2.5 删除远程仓库中不存在对应分支的本地远程分支

有时候本地的远程分支在远程仓库中已经不存在对应的分支了,这个时候可以用如下命令进行删除: git remote prune origin

3 FETCH_HEAD

它是一个 .git 目录下的文件,每一行保存着一个上一次进行 fetch 或者 pull 操作时远程仓库中的分支指针所指向的 commit 节点的信息。

它的用处在于,当在本地仓库中的某个分支上提交了多次 commit 节点或者进一步进行多次 push 后,想要回到该分支上一次 fetch 或者 pull 时的情形,此时可执行: git reset --hard FETCH_HEAD

如果想查看该分支上一次 fetch 或者 pull 时的情形,此时可执行: git checkout FETCH_HEAD

4 同步远程仓库的分支到本地仓库

获取 origin 远程仓库中的 remoteBranch 分支到本地仓库并更新 origin/remoteBranch 的命令为: git fetch origin remoteBranch

该命令执行步骤如下:

  • 进行同步。
    • 如果存在远程分支指针 origin/remoteBranch:

      先读取其所指向的 commit 节点的 commitID(记为 last),然后将远程仓库中 remoteBranch 分支指针所指向的 commit 节点向前到 commitID 为 last 的 commit 节点同步到本地仓库中 origin/remoteBranch 分支指针的后面。
    • 如果不存在:

      将远程仓库中 remoteBranch 分支指针所指 commit 节点及其之前的所有 commit 节点全部同步到本地仓库。
  • 同步完成后建立或者更新 origin/remoteBranch 分支指针,使其指向远程仓库中 remoteBranch 分支指针所指向的 commit 节点在本地仓库的拷贝。
  • 除此之外,还要建立或者更新 FETCH_HEAD 文件中 remoteBranch 分支相应的一行,使其记录远程仓库中 remoteBranch 分支指针所指向的 commit 节点的 commitID 等信息。

该命令不需要提前知道本地分支的信息,更不会与本地分支进行任何 merge 操作。 如果之前没有建立 remoteBranch 的本地跟踪分支(比如是其他人上传到仓库的分支,且本地没有该分支),则在执行 git fetch origin remoteBranch 之后,可以直接切换到同名的分支 git checkout remoteBranch git 会自动建立同名的本地分支,并与 remoteBranch 建立跟踪关系。

如果想要同步远程仓库中的所有分支,则可以使用: git fetch origin

进行 fetch 操作前后对比如下图所示:

在执行上面的操作后,就可以将远程分支与本地的其他分支进行合并了。操作如下: git merge origin/remoteBranch 本地的其他分支

5 git pull 操作

该操作相当于是先执行 fetch,然后执行 merge。

使用如下: git pull origin remoteBranch:localBranch 表示先同步 origin 远程仓库上的 remoteBranch 分支到本地的远程分支 origin/remoteBranch,然后将远程分支合并到本地的 localBranch 分支。

如果将远程分支合并到本地当前操作的分支上,则可以省略 localBranch 对应的参数: git pull origin remoteBranch

如果当前操作的分支 localBranch 是一个跟踪分支,它跟踪了远程分支 origin/remoteBranch(此时 localBranch 为 origin/remoteBranch 的 downstream),则可以省略 remoteBranch 对应的参数: git pull origin 甚至是 git pull

6 将本地仓库同步至远程仓库

git push origin localBranch:remoteBranch 表示将本地的 localBranch 分支同步到远程仓库 origin 下的 remoteBranch 分支。

该操作执行步骤如下:

  • 首先会判断 localBranch 是否与本地远程分支 origin/remoteBranch 之间建立了追踪关系。若没有,则操作失败;否则继续向下执行。
  • 将 localBranch 所指 commit 节点向前到 origin/remoteBranch 所指 commit 节点间的所有 commit 节点同步到远程仓库中的 remoteBranch 分支指针后面。

    如果远程仓库中没有名为 remoteBranch 的分支,则会新建这么一个分支。
  • 然后更新远程仓库中的 remoteBranch 分支指针,使其指向本地仓库中 localBranch 分支指针所指向的 commit 节点在远程仓库的拷贝。
  • 还会更新 origin/remoteBranch,使其指向本地仓库中 localBranch 分支指针所指向的 commit 节点。

若 localBranch 对应的参数为空,即: git push origin remoteBranch 则表示将当前正在操作的分支同步到远程仓库 origin 上的 remoteBranch 分支。

如果远程仓库中没有名为 remoteBranch 的分支,则会新建这么一个分支。

如果本地分支 localBranch 是一个跟踪分支,它跟踪了远程分支 origin/remoteBranch(此时 origin/remoteBranch 为 localBranch 的 upstream),则可以省略 remoteBranch 对应的参数: git push origin 甚至是 git push

git push 还提供了一个 -u 参数,表示在将某个本地分支同步至远程仓库中的相应分支后,建立本地分支到远程分支的跟踪关系: git push -u origin localBranch:remoteBranch

此外,如果本地版本回退后,想重新 push 到仓库,则可能会发生冲突。此时需要加上 -f 参数,表示强制 push: git push -uf origin localBranch:remoteBranch

7 删除远程仓库中的某个分支及其对应的本地远程分支

若 git push 中 localBranch 对应的参数为空,即: git push origin :remoteBranch 则表示删除远程仓库 origin 上的 remoteBranch 分支,因为这等同于推送一个空的本地分支到远程分支。除此之外,还会删除对应的本地远程分支。其作用等同于(常用这种写法): git push origin --delete remoteBranch

不能通过这种方法删除远程仓库中的默认分支 (也就是 origin/HEAD 所指的本地远程分支对应的远程仓库中的分支)。

远程主机的默认分支可以在远程主机对应网站中查看。

8 保护工作现场

当你在处于某个分支下进行工作的时候,突然发现需要去其他分支处理紧急任务(比如 bug 修复)。但是当前工作还没有完成,来不及提交。这个时候可以通过保护工作现场的方法进行处理。

保护工作现场的命令如下: git stash save "保护说明"

该命令的实质,就是将当前暂存区和工作区中的所有内容(已被跟踪的文件)保存到 Stash 区中。

原则上,可以嵌套地对工作现场进行多次的保护,但是 强烈建议不要这么做,首先是因为保护次数太多会引发混乱,其次是对同一个文件进行的操作在多次恢复后可能引发一些列的问题。因此,建议至多进行一次保护即可。

查看 stash 中的工作现场保护情况,包括编号等: git stash list

只恢复工作区: git stash pop stash@{n}

恢复工作区以及暂存区: git stash pop --index stash@{n}

有以下几点值得注意的地方:

  • git stash 保护现场,只适用于保护目前的分支上操作的情况,然后去处理其他分支的任务。如图所示:

    Screen Shot 2015-12-03 at 12.08.53 PM.png-450.8kB
    Screen Shot 2015-12-03 at 12.08.53 PM.png-450.8kB
    切不可在同一个分支上进行操作,否则在恢复时可能会引发合并冲突。
  • 由于只是对已被跟踪且还来不及 commit 的文件的修改情况进行保护,因此,若之前新建了一个文件,是不会对其内容进行保护的。表现为,当保护现场后,对这个新建文件进行的任何操作,这个文件都不会有效。

    因此,在进行工作现场保护之前,一定要将新建的文件 add 至暂存区

9 标签管理

9.1 建立标签

标签可以是版本号,如 v1.0,也可以是其他名字。

git tag tagName 该命令是对本地当前所操作的分支指向的 commit 创建标签。

若要对某个指定的 commit 节点创建标签,则运行如下命令: git tag tagName commitID

创建带有说明的标签: git tag -a tagName -m "标签说明" commitID

创建私钥签名且带有说明的标签: git tag -s tagName -m "标签说明" commitID

注:签名采用 PGP 签名,因此,必须首先安装 gpg(GnuPG)

9.2 显示标签

有了标签之后,就可以取代某个特定的 commitID 而执行一些操作了,比如: git checkout v3.0

显示所有的标签: git tag

根据标签显示对应 commit 节点的具体信息: git show tagName

9.3 删除标签

删除本地的标签: git tag -d tagName 表示删除当前操作的分支指向的 commit 节点的名为 tagName 的标签。

9.4 标签与远程仓库

默认情况下,push 时是不会将标签信息同步到远端的。如果想要将标签信息同步到远端,则可以执行以下命令: git push origin remoteBranch tagName 该命令表示,将当前操作的分支指向的 commit 节点的名为 tagName 的标签,推送到远端的 remoteBranch 分支指向的 commit 节点上。如果当前操作的分支指向的 commit 节点没有这个名字的 tagName 的标签,则操作失败。

一次性推送全部尚未推送到远程的本地标签: git push origin --tags

删除远端的标签: git push origin --delete tag tagName 表示删除远程仓库 origin 上与本地当前操作的分支相关联的分支所指向的 commit 节点的名为 tagName 的标签。

获取远端的标签到本地: git fetch origin tag tagName 表示获取远程仓库 origin 上的与本地当前操作分支相关联的分支所指向的 commit 节点的名为 tagName 的标签,并指定给本地当前操作分支所指向的 commit 节点。

10 使用 GitHub 参与开源项目

  • 首先在 GitHub 上找到一个开源项目,然后 fork 到自己的远端仓库里,自己拥有这个仓库的所有读写权限。
  • Clone 到本地。
  • 接下来,就可以在对这个开源项目根据自己的喜好进行修改。如果你希望这个开源项目的官方能接受你的修改,可以在 GitHub 上发起一个 pull request。当然,对方是否接受你的 pull request 就不一定了。

11 忽略特殊文件

在 Git 工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,Git 就会自动忽略项目中所有指定的文件。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 忽略任意位置的指定名字的文件。
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# 忽略任意位置的以某个字符串为后缀的所有文件。
# Python:
*.py[cod]
*.so
*.egg
*.egg-info

# 忽略任意位置的 foo 目录,相当于 **/foo/(见下面)。
foo/

还可以忽略某个指定位置的文件。此时,以 .gitignore 所在目录为根目录。:

1
2
3
4
5
6
# 忽略根目录下的这个子目录。
/.idea/
# 忽略根目录下的这个文件。
/test.py
# 忽略以 /foo 为路径前缀下的所有名为 dir 的子目录(中间可以由任意多个子目录)。
# /foo/**/dir/

注意:.gitignore 文件本身要放到版本库里且提交,并且可以对 .gitignore 做版本管理!

默认情况下 Git 不会追踪空目录(即使你 add 了它)。如果在特殊情况下需要追踪空目录,则可以在该空目录里新建一个 .gitignore 文件,并编辑如下:

1
2
3
4
# Ignore everything in this directory
*
# Except this file
!.gitignore

表示会忽略该空目录中的所有文件除了该目录下的 .gitignore 文件。这样在 add 该目录时,就可以成功追踪该目录。

12 搭建 Git 服务器

这在很多公司都有自己的 Git 服务器,用于版本控制。具体操作方法,等以后再学。


Reference