文章摘要
GPT 4
此内容根据文章生成,并经过人工审核,仅用于文章内容的解释与总结

Git入门

什么是Git

Git是一种分布式版本控制系统。假设有一个文件夹,随之时间的变化文件夹中文件的内容随时间变化,无论是是删除或增加文件夹的内容还是修改文件夹中的某个文件的内容,都能称为一个“版本”,而Git可以记录每个版本之间的区别并快速回滚到某个版本。简单来说,Git就像是是游戏的进度存档,可以快速回档
Git可以清楚地记录每个文件是谁在什么时候加进来的,什么时候修改的,修改的内容有哪些
无论做什么工作,如果有Git帮你保留这些历史记录和证据,那么发生意外情况的时候你就能知道是从什么时候开始有问题,以及该找谁负责了

Git的优点

速度块,占用体积小

如果你也有按照时间顺序备份文件的习惯,或许一个两个小文件还好,但是一旦涉及到大型文件夹,一次备份就会占用大量的空间,并且没有办法记录各个版本之间的差异

分布式系统

Git通常会有一服共同的服务器,所有文件都保存在这台服务器之上,即使在没有网络的环境下,在个人电脑上依然可以使用Git进行版本控制,待网络恢复后,再将个人电脑的数据同步(Push)到这台服务器上去。而个人电脑的数量可以有很多,每个人都可以将自己的修改后的文件同步到这台服务器上,大大提高了工作效率。这台服务器最著名的就有免费的Github,或者国内的Gitee,还可以是自建的Gitlab

Git的缺点

易学难精

Git的指令有非常多,但常用的却不多。约20%的指令就可以胜任80%的工作。但好在有许多实用的图形界面供新手学习使用(如SourceTree)

二进制文件的无奈

Git只关心文件的内容,每次版本的更新都会逐行对比文本文件(如md、txt)的差异并予以记录。但像是Photoshop的psd、Office的docx、ppt等文件是二进制文件,并不能像常规文本一样一行行查询,也就无法精确地查看不同版本的差异。但总体来说Git还是可以帮的上忙的,版本回溯的功能依然可以使用,至少当文件不小心被覆盖或者被删除的时候依然可以找回旧版本的文件

环境安装

Windows

Git官方下载地址
里面提供的32位和64位、安装版和便携版共4种组合供大家选择,选择适合的版本就好

Mac

Git官方下载地址
在macOS上安装Git有几个选项:Homebrew、MacPorts、Xcode、Binary installer、Building from Source、Installing git-gui

Linux

在Linux上安装是最简单的,只需要在终端中输入apt-get命令安装即可

1
2
3
$ apt-get install git
# 如果提示权限不足可以尝试以下命令
# sudo apt-get install git

测试Git是否成功安装

打开终端,输入命令

1
$ git -v

如果返回版本号则代表Git已经正确安装

图像界面工具

一开始接触Git,特别是没有Linux基础的人比较习惯图形界面工具,对终端及Git指令操作相对不熟悉,可以使用GUI工具辅助学习使用
Git官方网站中提供了多款GUI工具,有的是商业付费软件,有的是免费软件
其中SourceTree和Github Desktop这款两工具使用于Windows和macOS,并且功能都较为完善,也可以免费使用
如果在Linux上使用,SourceTree没有Linux的版本,但是可以使用gitk,可以使用下面命令安装

1
$ sudo apt-get install gitk

设置Git

用户设置

使用Git首先先要设置用户的邮箱和用户名

1
2
$ git config --global user.name "Akimio"
$ git config --global user.email "akimio2333@gmail.com"

完成后可以输入以下命令检查当前配

1
$ git config --list

单个项目用户设置

前面在设置用户时多加了一个--global的参数,其含义是进行全局(Global)设置,为默认的使用的账户,但要是在某一项目使用不同的作者则可以在该目录的终端中进行设置

1
2
$ git config --local user.name "Akimio"
$ git config --local user.email "akimio2333@gmail.com"

其他方便设置

更换编辑器

Git默认使用Vim编辑器,这里以更换为Emacs编辑器为例

1
$ git config --global core.editor emacs

除Emacs之外,Git还可以使用Sublime Text、Atom、VS Code等比较现代的文字编辑器,只需了解如果从终端启用这些编辑器,然后就可以利用同样的方法进行替换

设置缩写

虽然Git的命令并不长,但是有时候懒得打太多字,或者是经常打错,就可以设置缩写

1
2
3
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.st status

设置之后输入git co就和输入git checkout的效果一致,其他同理
Git中有些命令需要填入一些参数,同样也可以通过Alisa设置缩写

1
$ git config --global alias.l "log --oneline -- graph"

设置之后输入git l就和输入git log --oneline -- graph的效果一致
甚至还可以设置得更复杂一些

1
2
$ git config --global alias.ls 'log -- graph --pretty=format:"%h <%an> %ar %s"'
# 输入日志时显示提交Commit的人与时间

除了在终端中修改,也可以直接在配置文件中修改,在~/.gitconfig中可以查看之前的修改:

1
2
3
4
co = checkout br = branch
st = status
l = log --oneline --graph
ls = log --graph --pretty=format:"%h <%an> %ar %s"

你也可以仿照这种格式直接修改
虽然只是少打了几个字母,但是长期积累,减少的输入量还是十分惊人的

开始使用Git

初始化仓库(Repository)

如果是第一次使用Git,那么就从建立一个全新的文件夹开始吧

1
2
3
4
cd /tep # 切换至绝对路径/tmp,Windows用户可以直接进入文件夹中右键空白处选择打开终端
mkdir test # 在/tmp文件夹中创建一个名为test的文件夹
cd test # 进入test文件夹
git init # 初始化这个文件夹,并让Git对这个文件夹开始版本控制

把文件交给Git管控

创建文件交给Git

创建文件

现在在这个test文件夹中只有一个.git的隐藏文件夹,并没有其他的文件,所以我们创建一个文件

1
$ echo "Hello,Git" > welcome.html # 将Hello,Git添加到welcome.html中,因为我们文件夹中并没有welcome.html,所以系统会为我们创建一个welcome.html

输入以下命令查看Git状态

1
$ git status

在终端就会显示(井号后面为含义):

1
2
3
4
5
6
On branch master # 在master分支(branch)中
No commits yet # 没有任何提交(commit)
Untracked files: # 未追踪文件(刚刚添加到文件夹中,还未commit)
  (use "git add <file>..." to include in what will be committed) # 使用git命令进行提交(后面会讲到)
        welcome.html   # 列出未被追踪的文件, 这里我们就是新建的welcome.html
nothing added to commit but untracked files present (use "git add" to track)

把文件交给Git

使用的命令是git add后面加上空格和文件名:

1
$ git add welcome.html

再次查看Git状态:

1
2
3
4
5
On branch master
No commits yet
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   welcome.html

从以上的信息可以看出,welcome.html的状态已经从untracked变成new file,表明该文件已经被提交到缓存区中
如果觉得一个一个提交git太麻烦,也可以尝试使用以下命令

1
2
$ git add *.html # *代表通配符,意思是提交所有后缀名为html的文件
$ git add . # 将所有文件提交至缓存区,在Git 1.x中,git add --all和git add .有区别,但在Git 2.x中,两者含义一致

git add后又对文件内容进行改动

设想一下这种情况:

  1. 新增了一个文件abc.txt
  2. 执行git add abc.txt命令,将文件提交到缓存区,但是并没有提交(commit)
  3. 编辑abc.txt文件
    此时查看一下Git状态
1
2
3
4
5
6
7
8
9
10
11
$ git status
On branch master
No commits yet
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   abc.txt
        new file:   welcome.html
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   abc.txt

可以发现,abc.txt变成了两个。因为步骤2中把txt提交到缓存区后,步骤3又编辑了该文件,对于Git来说,编辑的新内容并没有提交到缓存区中,所以缓存区的内容还是步骤2中加进来的那个文件。如果你确定步骤3的这个改动是你想要的,就可以再次执行git add abc.txt,将abc.txt添加到缓存区中

把暂存区内容提交到仓库存档

如果仅仅是通过gti add命令把异动文件添加到缓存区,还不算是完成整个流程,还需要将缓存区的文件提交到仓库中永久保存:

1
$ git commit -m"第一次提交"

这里双引号中的内容代表提交的说明,最主要的目的就是告诉自建和别人这次改动做了什么,下面右键建议:

  1. 中文、英文都可以
  2. 用简洁的语言表达
  3. 尽量不要用Fix bug等模糊的表述,因为没人知道你修复了什么BUG
    在提交(commit)后会显示有什么做了修改
1
2
3
4
[master (root-commit) 5df35a7] 第一次提交
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 abc.txt
 create mode 100644 welcome.html

这里很清楚地可以看到新增了abc.txtwelcome.html两个文件

查看记录

使用Git命令查看记录

因为目前只提交(Commit)了一次,不好比较,所以我们再创建一个新的文件提交一次

1
2
3
$ echo "test2.1" > test2.txt
$ git add .
$ git commit -m"第二次提交"

然后输入以下命令查看提交记录

1
2
3
4
5
6
7
8
9
10
$ git log
commit a291b22f6fed953f2cd869fa6baacf301934a027 (HEAD -> master)
Author: akimio <akimio2333@gmail.com>
Date:   Sat Jul 1 17:25:48 2023 +0800
    第二次提交

commit 5df35a76b35bd217655a6df64375b6c47b7542ef
Author: akimio <akimio2333@gmail.com>
Date:   Sat Jul 1 17:18:38 2023 +0800
    第一次提交

越在上面说明内容约新,通过这个命令大致就可以看出是谁在什么时候提交的内容,提交了什么内容,其中第一次提交第二次提交都是commit的说明,所以大家要清楚的写下自己修改了说明内容,方便日后查询记录。其中a291b22f6fed953f2cd869fa6baacf301934a027是一个哈希值,其重复的概率非常低,每一次提交(Commit)都有这么一个哈希值,可以当作提交(Commit)的唯一身份识别码

使用Git命令查看记录时常见问题

查找某个人或某些人的提交记录

例如查找一位叫Akimio的提交记录可以使用以下命令:

1
$ git log --oneline --author="Akimio"

如果需要查找Akimio和zuilang的提交记录,就可以使用:

1
$ git log --oneline --author="Akimio\|zuilang"

其中\是转义字符,如果输入git log --oneline --author="Akimio|zuilang",则会查找一个名字为Akimio|zuilang的提交记录

通过提交说明查找提交记录

使用--grep参数可以在所以提交的记录中查找关键字,例如搜索fix bugs

1
$ git log --oneline --grep="fix bugs"

在提交文件中找到Ruby

使用-S参数可以在所以提交的记录中进行搜索,找到那些符合特定条件的内容

1
$ git log --oneline -S "Ruby"

查找某一时间段内的提交记录

在查看历史记录时,可以搭配--since--until这两个参数使用
查找今天早上9点到下午6点所以的提交记录:

1
$ git log --oneline --since="9am" --until="6pm"

还可以搭配--after这个参数
查找自2023年6月往后每天早上9点到下午6点所以的提交记录:

1
$ git log --oneline --since="9am" --until="6pm" --after="2023-06"

在Git中删除文件或更改文件名

无论删除还是更改文件名,对Git来说都是一种改动

删除文件

直接删除

可以使用系统命令或者是直接在资源管理器等图形化界面工具直接删除文件:

1
$ rm welcome.html

此时在文件夹中已经找不到welcome.html这个文件了
查看一下Git的状态

1
2
3
4
5
6
7
8
$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    welcome.html

no changes added to commit (use "git add" and/or "git commit -a")

可以看到welcome.html这个文件当前状态是deleted,如果你确定是你想做的,就可以把改动添加到缓冲区:

1
2
3
4
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    welcome.html

welcome.html当前状态是deleted而且已经被添加到缓冲区,接下来就可以提交(Commit)了:

1
$ git commit -m "删除welcome.html"

然后可以查看一下提交记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git log

commit b1165419ec336243647c003723e5d7d089337006 (HEAD -> master)
Author: akimio <akimio2333@gmail.com>
Date:   Sat Jul 1 18:03:26 2023 +0800
    删除welcome.html

commit a291b22f6fed953f2cd869fa6baacf301934a027
Author: akimio <akimio2333@gmail.com>
Date:   Sat Jul 1 17:25:48 2023 +0800
    第二次提交

commit 5df35a76b35bd217655a6df64375b6c47b7542ef
Author: akimio <akimio2333@gmail.com>
Date:   Sat Jul 1 17:18:38 2023 +0800
    第一次提交

使用Git删除

输入命令:

1
2
$ git rm test2.txt
$ rm 'test2.txt'

这时候查看状态:

1
2
3
4
5
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    test2.txt

他就直接在缓冲区,就不再需要git add了,可以少一个步骤

加上–cached参数

无论是执行rm还是git rm命令都会真的把这个文件从文件夹中删除,如果只是像让这个文件不再被Git控制则可以加上--cached这个参数

1
2
$ git rm abc.txt --cached
rm 'abc.txt'

在文件夹中查看,abc.txt依然在文件夹中
此时查看Git状态

1
2
3
4
5
6
7
8
9
10
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    abc.txt
        deleted:    test2.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        abc.txt

可以看到文件abc.txt的状态是deleted且文件夹中有一个文件abc.txt未被追踪,由于之前删除test2.txt未提交,所以我们看到test2.txt的状态是deleted

更改文件名

更改文件名和删除文件类似,这里不再赘述其中过程,只给出命令

直接改名

1
$ mv welcome.html hello.html # 把welcome.htm改成hello.html

使用Git改名

1
$ git mv welcome.html hello.html # 把welcome.htm改成hello.html

文件的名称并不重要

Git是根据文件的内容来计算“SHA-1”的值,所以文件的名称并不重要,因此在更改文件名时,Git并不好为此做出有个新的Blob对像,而是指向原来的那个Blob对线,但因为文件名变了,所以Git会为此做出一个新的Tree对象

修改Commit记录

改动最后一次的Commit信息

只需要增加参数--amend即可

1
$ git commit --amend -m "新的Commit信息"

改动更早的Commit信息

参数--amend只能出来最后一次的Commit,想要改动更早的记录需要使用到命令:Rebase,详细使用方法可以参考后面的内容。

追加文件到最后一次Commit

刚刚提交了Commit但是突然发现有一个文件忘记加上了,虽然可以为了这个文件单独加送一次Commit,但是看上去没那么优雅,可以采用下面两种方式来完成:

  1. 先使用git reset删除最后一次Commit,将文件加入后再重新提交Commit
  2. 使用--amend参数进行Commit
    这里先介绍第二种方式,第一种方式会在后续章节讲解
    例如,这里有一个名为cinderella.html的文件要把它加入最近一次的Commit。先使用git status命令查看cinderella.html的状态是Untracked,先将其加入缓冲区:git add cinderella.html,然后再提交Commit:git commit --amend --no-edit其中--no-edit参数的意思是不编辑Commit信息,这样就把文件添加到最近一次的Commit了

新增文件夹

刚刚添加了一个新的的文件夹image,但是查看Git状态却提示没有任何改动,这是因为Git在计算时是根据”文件内容“进行计算的,所以新增一个空文件夹是无法直接添加到git中的
解决方法其实也很简单,可以在文件夹中添加一个.kepp.gitkeep的空文件让Git监测到这个文件夹,可以通过以下命令创建空文件:

1
$ touch image/.keep

然后再依次提交到缓冲区、提交Commit就好了

有些文件夹不想放在Git中

有些比较机密的文件不想放在Git文件夹中一起备份或者是一些程序编译产生的中间文件抑或是缓存文件

添加.gitignore

使用命令在Git命令下创建一个.gitignore文件

1
$ touch .gitignore

然后编辑文件内容

1
2
3
4
5
6
7
# 文件名.gitingore
secret.yml  // 忽略secret.yml文件
config/database.yml // 忽略config文件夹下的database.yml文件
db/*.sqlite3  // 忽略db文件夹下所有后缀名是sqlite3的文件
*.tmp  // 忽略所有后缀名是tmp的文件
# 也可以忽略.gitignore这个文件,但一般不会这么做
# .gitignore

如果不清楚自己所用的工具或程序语语言通常会忽略哪些文件,可以在这里查看:传送门

忽略这个忽略

虽然.gitignore列出了一些忽略规则,但其实还是可以忽略这些忽略规则的,只需要在将文件提交到缓冲区时添加一个参数:

1
$ git add -f 文件名称

忽略失效

添加忽略规则后发现Git依然在追踪一些应被忽视的文件,是因为这些文件在创建.gitignore的时候就已经存在了,如果依然想要这些文件套用忽视规则可以使用以下命令:

1
$ git rm --cached

清除忽略的文件

如果像清除那些已经被忽略的文件,可以使用git clean命令配合-X参数:

1
$ git clean -fX

其中那个额外加上的参数-f是指强制删除

查看特定文件的Commit记录

使用git log就可以查看整个Git项目所有的Commit,如果想查看某个文件所有的Commit可以使用-p参数:

1
$ git log -p welcome.html # 查看welcome.html所有的Commit

+++表示某次commit中新增加的行;---表示删除的行

查看某个文件中某行代码是谁写的

如果想查看某个文件中某行的代码是是谁写的,可以使用git blame:

1
$ git blame index.html

然后git就可以详细地列出index.html这个文件每一行代码是谁在什么时候写的
如果文件很大,可以使用-L这个参数:

1
$ git blame -L 5,10 index.html # 这样Git只会列出5-10行的信息

恢复删除的文件

无论是有意还是无意将文件删除,Git都能将文件恢复,为了掩饰,我这里用rm删除所有的后缀为html文件:

1
$ rm *.html

再使用git status查看当前Git状态,可以看到删除的html文件的状态是deleted。假设我想恢复其中的a.html,可以使用以下命令:

1
$ git checkout a.html

然后a.html就恢复了;如果想恢复所有的文件,可以使用:

1
$ git checkout .

然后就可以恢复所有删除的文件了

将提交的Commit拆掉重做

拆掉重做

这种情况很常见,虽然使用的命令git reset很简单,但是不少人误解了Reset的意义,所以导致很多学习Git的人卡在这里

退一步海阔天空

先查看以下Git的状态:

1
2
3
4
5
6
7
$ git status
e12d8ef  (HEAD -> master ) add database.yml in config folder
85e7e30 add hello.html
657fce7 add container
abb4f43 update index page
ccef6e40 create index page
cc797cd init commit

拆掉最后一次Commit分为“相对”和“绝对”的做法,下面是“相对”的做法:

1
$ git reset e12d8ef^

^代表的是“前一次”,所以e12d8ef^指的就是e12d8ef的前一次Commit也就是85e7e30这个Commit。如果是前两次就是e12d8ef^^,以此类推,但是如果要退回到前五此一般不会用e12d8ef^^^^^而是用e12d8ef~5
因为正好HEAD与master当前都是指向e12d8ef这个Commit,而且一般这些哈希值不好记,所以会用以下这两个命令替代:

1
2
$ git reset master^
$ git reset HEAD^

以上是“相对的”方式,如果你很清楚要回到那个Commit,可以直接指明:

1
$ git reset 85e7e30

然后就可以直接切换到85e7e30这个Commit的状态,因为85e7e30刚好就是e12d8ef的前一次Commit,所以也能达到“拆掉最后一次的Commit”这个效果

Reset模式

git reset这个命令可以搭配三个参数使用,分别是—mixd—soft—hard。搭配不同的参数,执行结果会有些许差别

mixed模式

—mixd是默认参数,如果没有另外加参数,git reset将会使用—mixd模式
—mixd模式下,会把缓冲区的文件删除,但不会影响工作目录的文件。也就是说,Commit拆出来的文件会留在工作目录,但不会留在暂缓区

soft模式

在这种模式下,其工作目录与缓冲区的文件都不会被删除,所以看起来就只有HEAD的移动而已。因此,Commit拆出来的文件会直接放在缓冲区

HARD模式

在这种模式下,无论是工作目录还是缓冲区的文件,都会直接被删除

三种模式的比较
模式 mixed soft hard
工作目录 不变 不变 被删除
缓冲区 被删除 不变 被删除
模式 mixed soft hard
Commit拆出来的文件 放回工作目录 放回缓冲区 直接删除

恢复不小心被hard reset的文件

退回到Reset前

还是使用之前的例子:

1
2
3
4
5
6
7
$ git status
e12d8ef  (HEAD -> master ) add database.yml in config folder
85e7e30 add hello.html
657fce7 add container
abb4f43 update index page
ccef6e40 create index page
cc797cd init commit

这里一共有6个Commit,我们假设倒退两步:

1
$ git reset HEAD~2

然后再次查看Git状态:

1
2
3
4
5
$ git status
657fce7 (HEAD -> master ) add container
abb4f43 update index page
ccef6e40 create index page
cc797cd init commit

会发现最后两次Commit都不见了,但最后两次的Commit其实是依旧存在的,只是没有列出,假设我想要恢复到执行执行git reset HEAD~2命令之前的状态,只需要执行这个命令:

1
$ git reset e12d8ef --hard # e12d8ef为执行reset前的那个Commit的SHA-1值

这里使用了--hard这个参数可以强迫放弃Reset之后改动的文件

使用Reflog

如果一开始没有记录Commit的SHA-1值也没有关系,可以使用下面的方法恢复
方法演示前,我们先reset一下我们的git仓库:

1
$ git reset HEAD~2 --hard # 与上面的例子不同的是这里使用了hard模式进行reset

这样不仅Commit不见了,文件也消失了。然后可以使用Reflog命令来查看一下记录:

1
2
3
4
$ git reflog
657fce7 (HEAD -> master) HEAD@{0}: moving to HEAD~2
e12d8ef (origin/master,origin/HEAD,cat) HEAD@{1}:checkout : moving from cat to master
e12d8ef (origin/master,origin/HEAD,cat) HEAD@{2}:checkout : moving from master to cat

当HEAD移动时(如切换分支或者Reset都会造成HEAD移动),Git就会在Reflog中留下一条记录。从上面的三条记录中,可以大致猜出最近3次HEAD的移动,最后一次的动作就是Reset。如果想要取消这次Reset,只需要Reset到它Reset前的那个Commit即可:

1
$ git reset e12d8ef --hard

HEAD是什么

HEAD简介

HEAD是一个指标,指向了某个分支,通常可以把他当作当前所在分支来看待。在.git目录中有一个名为HEAD的文件,其中记录的就是HEAD的内容,可以来看看他长什么样:

1
2
$ cat .git/HEAD
ref: ref/heads/master

从这个文件就看也看出,HEAD当前正指向master分支,可以在深入看一下master:

1
2
$ cat ./refs/heads/master
e12d8ef0eb9deae8bf1115c5ce51dbc2e09c8904

可以看到所谓的master分支其实也只是一个40个字节的文件罢了

切换分支

假设当前项目一共有三个分支,当前在master分支上:

1
2
3
4
$ git branch
main
dev
* master

接下来尝试切换到dev分支上:

1
2
$ git checkout dev
Switch to branch 'dev'

这时再看一下HEAD文件:

1
2
$ cat .git/HEAD
ref: refs/heads/dev

就可以看到HEAD现在指向dev分支了,我们再尝试切换到main分支:

1
2
$ git checkout main
Switch to branch 'main'

这时再确认一下HEAD:

1
2
$ cat .git/HEAD
ref: refs/heads/main

它又指向了main分支。也就是说,HEAD通常会指向当前所在的分支。不过HEAD也不一定总所指向某个分支,当HEAD没有指向某个分支时便会造成detached HEAD状态

版本分支

使用版本分支的原因

在开发的过程中,一路往前Commit迭代版本似乎也并没有问题,但是当越来越多的伙伴加入同一个项目中的时候,就不能像前面那么随意的Commit了。这是分支的作用就派上用场了。例如,想要新增功能或者修复Bug,都可以新创建一个分支,在新分支上做完并确认没有问题之后再合并回到原本的分支,这样就不会影响正在运行的产品线

开始使用分支

查看分支

1
2
3
$ git branch
* master
main

可以看到当前项目有两个分支,并且当前在master前面有个*,说明当前在这个分支上

新增分支

假设我需要新增一个名为dev的分支,只需要输入下面这段命令

1
$ git branch de

然后在查看一下当前项目的分支情况

1
2
3
4
$ git branch
dev
* master
main

可以看到多出了一个分支,但当前的项目依旧在master分支上

更改分支名称

假设我们需要将分支dev修改成test

1
$ git branch -m dev test

再查看一下分支:

1
2
3
4
$ git branch
test
* master
main

删除分支

假设test已经不再需要了,就可以使用下面的命令删除:

1
$ git branch -d test

如果test分支中还有内容未被合并,Git就会给出提示,并且test分支也不会被删除,如果想要强制删除,可以使用下面的命令:

1
$ git branch -D test

需要注意的是,“当前所在分支”是无法被删除的,想要删除该分支,必须先要切换到其他的分支上

切换分支

假设当前在master上,现在需要切换到main分支去

1
$ git checkout main

这个命令只能切换到已经存在的分支上,如果需要创建一个分支并切换过去可以使用下面这个命令:

1
$ git checkout -b test2

合并分支

假设我们新创建了一个dev分支,并且在这个分支上提交了两次Commit,现在需要将这两次提交的内容合并回主线分支master,我们需要先回到主线分支master,在将dev分支的内容合并回主线分支:

1
2
$ git checkout master
$ git merge dev

然后就可以看到哪些文件有改动

A合并B与B合并A

假设有两个分支dev1dev2都来自master的同一个Commit。现在这两个分支都提交了不同的Commit,现在需要将这两个分支合并。
假设目前HEADdev1分支上,先执行合并dev2

1
$ git merge dev2

然后Git会创建一个额外的Commit,这个Commit同时指向了dev1dev2两个分支,然后HEAD会随dev1向前指向这个新的Commit,而dev2分支会停留在原地
值得注意的是,如果dev1dev2修改同一文件,合并时就会有冲突,需要打开这个文件手动修改冲突位置

恢复不小心被删除的分支

恢复已被删除但还未合并过的分支

如果某个还未合并过的分支被用-D强制删除了:

1
2
$ git branch -D dev
Deleted branch dev (was b174a5a)

找到这个分支最新Commit的HASH值,也就是b174a5a,只要创建一个新的分支从这个Commit开始就好了:

1
$ git branch new_dev b174a5a

然后检查一下分支,切换过去,查看了一下文件列表就会发现文件都回来了

1
2
3
$ git branch
$ git checkout new_dev1
$ ls -al total 16

删除分支时没有记录HASH值

上面介绍了如果记录了删除分支时没有记录HASH值的方法,如果没有记录删除分支时的HASH值还可以通过git reflog命令通过查看HEAD移动推断出是哪个Commit。值得注意的是Reflog默认保留30天,所以30天内删除的分支还是可以找回来的

使用Rebase合并分支

前面一节介绍了 git merge 命令来合并分支,现在介绍另一种方式
假设目前的状态还是有两个分支基于 master 的分支 dev1dev2 并分别提交了自己的Commit,现在在 dev1 分支上,现在需要合并分支:

1
$ git rebase dev2

dev1dev2 原本都是基于 master 分支的。执行这段 命令后,就将 dev1 的参考基准指向了 dev2
假设 dev1master 分支的基础上提交了3个Commit,dev2master 分支的基础上提交了5个Commit,再执行了上述命令后,dev1 分支就领先了 master 3+5共8个Commit,前5个Commit依旧是 dev2 原本提交的Commit,后三个Commit来自于 之前的 dev1,但是Commit的HASH值于 dev1 原本提交的Commit的HASH值不同,效果上相当于在 dev2 上额外提交了三个Commit,并将 dev1HEAD 移动到最新的Commit上

合并时发生冲突

发生冲突

只要不同的分支检测到同一文件同一行受到改动时就会出现冲突,假设在 dev1 分支上改动了 index.html 文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 首页 </title>
</head>
<body>
<div class="container">
<div> 我是dev1分支 </div>
</div>
</body>
</html>

然后在 dev2 分支上也改动了 index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 首页 </title>
</head>
<body>
<div class="container">
<div> 我是dev2分支 </div>
</div>
</body>
</html>

这时不管是使用Merge和Rebase合并都会出现冲天

1
2
3
4
$ git merge dev2
Auto-merging index.html
COFLICT (content): Merge conflict in index.html
Automatic merge faild;fix conflicts and then commit the result.

这时Git发现那个 index.html 出现问题了,这时查看一下当前的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git status
On branch dev1
You have unmerge paths
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Change to be committed:
new file : dev2-1.html
new file : dev2-2.html

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modofied:index.html

这里有两点需要说明:

  1. 对于 dev1 分支来说,dev2-1.htmldev2-2.html 都是加入的新文件,但已被放入暂存区
  2. 因为在 dev1dev2 都对 index.html 的 同一行进行了改动,所以Git将其标记为 both modified 的状态

解决冲突

Merge

查看 index.html 的文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 首页 </title>
</head>
<body>
<div class="container">
<<<<<<< HEAD
<div> 我是dev1分支 </div>
=======
<div> 我是dev2分支 </div>
>>>>>>> dev2
</div>
</body>
</html>

可以看到有两个分支的内容,现在决定使用dev1的内容,最后内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 首页 </title>
</head>
<body>
<div class="container">
<div> 我是dev1分支 </div>
</div>
</body>
</html>

改完后记得将内容提交回暂存区,并提交Commit:

1
2
$ git add index.html
$ git commit -m "conflict fixed"

Rebase

使用Rebase进行合并:

1
git rebase dev2

如果遇到冲突,合并就会暂停,并且HEAD不会指向任何一个Commit,现在需要修复冲突的文件,然后提交文件到暂存区后继续合并:

1
2
3
$ git add index.html
$ git rebase --continue
Applying: update indexd

至此,就完成了Rebase

如果冲突的文件不是文本文件

因为上述的冲突文件 index.html 文件是文本文件,所以Git可以标记出冲突的位置,但如果冲突的文件是二进制文件又该如何解决?
假设 dev1dev2 都同时加入了一张叫 background.png 的图片,则合并时的冲突信息为:

1
2
3
4
5
$ git merge dev2
warning: Cannot merge binary files: cute_animal.jpg (HEAD vs. dog)
Auto-merging background.jpg
CONFLICT (add/add): Merge conflict in background.jpg
Automatic merge failded;fix conflicts and then commit thee result.

如果决定采用 HEADbackground.jpg 的话:

1
$ git checkout --our background.jpg

如果决定采用 dev2background.jpg 的话:

1
$ git checkout --theirs background.jpg

决定之后再像前面一样把内容加入到暂存区并提交Commit:

1
2
$ git add background.jpg
$ git commit -m "conflicts fixed"

从过去的某一Commit创建新分支

回到过去重新开始

先通过git记录找到需要回去的Commit,切换过去并创建分支:

1
2
3
4
$ git log
...
$ git checkout '657fce7'
$ git checkout -b past_commit

然后就在 657fce7 这个Commit上创建了一个名为 past_commit 的分支

一步到位

如果你实现就知道是Commit的HASH值就可以用下面的命令一步到位:

1
$ git branch past_commit 657fce7

修改历史记录

修改历史Commit信息

在之前历史信息,在5.6节讲过可以使用--amend参数来修改最后Commit的信息,但仅限于最后一次,如果要改动其他更早的形象,就需要使用其他方式
之间介绍过通过git rebase来合并分支,其实git rebase是一个强大的互动模式,接下来的几节内容都是介绍怎么使用这种模式改动历史记录

启动互动模式

1
$ git rebase -i bb0c9c2

这段命令种的-i参数是指进入Rebase指令的“互动模式”,后面的bb0c9c2指的是从现在这个Commit回到bb0c9c2这个Commit,可以修改中间若干个Commit的信息
此时会弹出一个Vim编辑器,中间有若干个Commit的形象,前面的pick表示“保留这次Commit,不做改动”,找到需要改动的Commit,把前面的pick改成reword,表示要改动这几次Commit的信息,保存修改后会立即弹出另一个Vim,在这里就可以修改之前的Commit信息了

修改Commit信息后的其他影响

虽然只是修改了Commit信息,但只要调出Git日志就可以发现,修改过信息Commit的HASH值都改变了,已经是一个全新的Commit对象了

取消这次Rebase

如果想放弃这次对Commit的修改,只需要使用之前介绍过的ORIG_HEAD就好:

1
$ git reset ORIG_HEAD --hard

将多个Commit合并

有时候Cimmit太过“琐碎”,例如:

1
2
3
4
5
6
7
8
$ git log --oneline
37646cd (HEAD -> master) add dog 2
c90b00f add dog 1
0d40e75 add 2 cats
507780e add cat 2
5d52302 add cat 1
d943a01 add database settings
5944ffd init commit

5d52302507780e两个Commit上各添加了一个文件(分别是cat1.htmlcat2.html)类似的c90b00f37646cd也各添加了两个Commit,也可也使用git rebase将这几个Commit合并为一起,可以让Commit看起来更加简洁:

1
2
3
4
5
6
7
$ git rebase -i 5944ffd
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
pick c90b00f add dog 1
pick 37646cd add dog 2

将需要合并的Commit的pick修改成squash

1
2
3
4
5
6
pick d943a01 add database settings
pick 5d52302 add cat 1
squash 507780e add cat 2
squash 0d40e75 add 2 cats
pick 3c90b00f add dog 1
squash 37646cd add dog 2

上面的改动代表了下面这两件事:

  • add dog 2会与上一个前一个Commit合并,即3c90b00f37646cd合并
  • add 2 catsadd cat 2add cat 1这三个Commit会合并
    简单来说,squash会和前一个Commit合并
    保存Vim的编辑后,会弹出另一个Vim编辑窗口,重新编辑一下新合并的Commit信息

将一个Commit拆成多个Commit

与上节相反,有时觉得单词Commit的文件太多了,可能就会想这拆解得更细点,下面是当前的历史记录:

1
2
3
4
5
6
7
8
$ git log --oneline
37646cd (HEAD -> master) add dog 2
c90b00f add dog 1
0d40e75 add 2 cats
507780e add cat 2
5d52302 add cat 1
d943a01 add database settings
5944ffd init commit

0d40e75这个Commit提交了两个文件
还是使用rebase

1
2
3
4
5
6
7
$ git rebase -i 5944ffd
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
pick c90b00f add dog 1
pick 37646cd add dog 2

我们需要把0d40e75拆成多个Commit,只需要把pick改成edit
然后Rebase在执行到0d40e75这个Commit后会在暂停下来,因为需要将当前这个Commit拆成两个Commit,所以先要回到上一个Commit:

1
$ git reset HEAD^

然后就可以看到原本0d40e75添加的两个文件cat3.htmlcat4.html都放在了工作目录中,并且处于Untracked未追踪的状态,所以现在只需要分别提交Commit即可:

1
2
3
4
$ git add cat3.html
$ git commit -m "add cat 3"
$ git add cat4.html
$ git commit -m "add cat 4"

添加完后只需要让Rebase继续执行即可:

1
$ git rebase --continue

插入Commit

有些时候可能需要在某些Commit中间插入其他的Commit,这个技巧其实和上一个拆分Commit的方法很像。假设当前的历史记录如下:

1
2
3
4
5
6
7
8
$ git log --oneline
37646cd (HEAD -> master) add dog 2
c90b00f add dog 1
0d40e75 add 2 cats
507780e add cat 2
5d52302 add cat 1
d943a01 add database settings
5944ffd init commit

然后Rebase:

1
2
3
4
5
6
7
$ git rebase -i 5944ffd
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
pick c90b00f add dog 1
pick 37646cd add dog 2

假设需要在add 2 catsadd dog 1之间插入其他的Commit,则将add 2 catspick改成edit,保存后等待Rebase运行到0d40e75后插入Commit:

1
2
3
$ touch bird1.html
$ git add bird1.html
$ git commit -m "add bird 1"

然后继续刚刚中断的Rebase:

1
$ git rebase --continue

调整Commit顺序

假设当前Commit记录如下:

1
2
3
4
5
6
7
8
$ git log --oneline
37646cd (HEAD -> master) add dog 2
c90b00f add dog 1
0d40e75 add 2 cats
507780e add cat 2
5d52302 add cat 1
d943a01 add database settings
5944ffd init commit

我们需要把所有cat的记录都移动到dog之后,还是使用Rebase:

1
2
3
4
5
6
7
$ git rebase -i 5944ffd
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
pick c90b00f add dog 1
pick 37646cd add dog 2

然后修改Commit顺序,在Rebase状态下越新的Commit记录越在下方

1
2
3
4
5
6
pick d943a01 add database settings
pick c90b00f add dog 1
pick 37646cd add dog 2
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats

保存离开后就调整好Commit的顺序了

删除Commit

假设当前Commit记录如下:

1
2
3
4
5
6
7
8
$ git log --oneline
37646cd (HEAD -> master) add dog 2
c90b00f add dog 1
0d40e75 add 2 cats
507780e add cat 2
5d52302 add cat 1
d943a01 add database settings
5944ffd init commit

我们需要把所有dog的Commit删除,还是使用Rebase:

1
2
3
4
5
6
7
$ git rebase -i 5944ffd
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
pick c90b00f add dog 1
pick 37646cd add dog 2

然后把dog前面的pick修改成drop或者也可也直接删掉

1
2
3
4
5
6
pick d943a01 add database settings
pick 5d52302 add cat 1
pick 507780e add cat 2
pick 0d40e75 add 2 cats
drop c90b00f add dog 1
drop 37646cd add dog 2

保存离开后就已经删除dog

标签

开始使用标签

标签是什么

在Git中,标签(Tag)是一个指向某个Commit的指示标,这看起来好像与分支(Branch)有些类似

什么时候使用标签

通常在软件开发时会完成特定的“里程碑”,如软件版本号1.0.0beta-release之类的,在这种时候就很适合用标签做标记

标签的分类

标签有轻量标签(lightweight tag)附注标签(annotated tag)两种标签。无论是哪一种标签,都是可以把它当作贴纸贴在某一个Commit上

轻量标签(lightweight tag)

轻量标签的使用方法想当简单,只需要指定要贴上去的那个Commit即可。
假设当前Commit记录如下:

1
2
3
4
5
6
7
8
9
10
11
$ git log --oneline
c01c8f9 (HEAD -> master) add fish
77699f9 add pig
7b6d9d0 add lion and tiger
94ac80e add dog 2
104f368 add dog 1
41fa9bd add 2 cats
4794059 add cat 2
b7a4276 add cat 1
7ee7098 add database settings
a9d6d5f init commit

如果需要在add lion and tiger这个Commit上贴一个big_cats的标签,可以使用如下命令

1
$ git  tag big_cats 7b6d9d0

这样标签就贴好了,再次查看一下记录:

1
2
3
4
5
6
7
8
9
10
11
$ git log --oneline
c01c8f9 (HEAD -> master) add fish
77699f9 add pig
7b6d9d0 (tag: big_cats) add lion and tiger
94ac80e add dog 2
104f368 add dog 1
41fa9bd add 2 cats
4794059 add cat 2
b7a4276 add cat 1
7ee7098 add database settings
a9d6d5f init commit

可以看到big_cats这个标签指向了7b6d9d0这个Commit

附注标签(annotated tag)

如果需要为add lion and tiger这个Commit添加一个附注标签,则可以使用下面这段命令:

1
$ git tag big_cats 7b6d9d0 -a -m "Big Cats are coming"

其中参数-a代表让Git创建一个附注标签,后面的参数-m类似于提交Commit时的-m,如果没有-m则会弹出一个Vim编辑器去修改信息

两种标签的区别

在Git官方文档中对这两种标签也有相应说明,Git官方推荐将轻量标签用于个人使用或是暂时标记;而附注标签主要用作软件版本号。
此外两种标签的信息也不同,可以通过以下命令查看信息:

1
$ git show big_cats

可以发现附注标签比轻量标签多一些信息,可以清楚地看到是谁在什么时候贴了这张标签

删除标签

不管是哪一种标签,其本质都类似于一张帖子,撕掉一张贴纸并不会删除Commit或者文档,只需要一个参数-d:

1
$ git tag -d big_cats

标签与分支有什么区别

标签和分支的区别是,分支会随着Commit移动,但标签不会。在之前的章节介绍过,当Git往前推进一个Commit时,它所在的分支会跟着向前移动;但标签一旦贴上去,无论Commit怎么前进,标签都不会移动。因此分支可以看成是可以移动的标签

其他一些冷知识

临时切换到其他任务

Commit当前进度

假设当前在dev分支开发项目,但是main分支出现问题,需要修复,可以先在dev分支上提交一个Commit然后切换回main分支:

1
2
3
$ git add .
$ git commit - "还未完成"
$ git checkout main

main分支的问题修复完后,需要继续回到dev分支继续开发未完成的项目:

1
2
$ git checkout dev
$ git reset HEAD^

然后即可以看到之前未完成的文件了

使用Stash

保存Stash

刚刚这种情况除了提交Commit还可以用Stash。
先查看一下当前的状态:

1
$ git status

就可看到哪些文件有修改
然后利用stash将修改的地方暂存起来:

1
2
3
$ git stash
# PS:未追踪Untrack状态的文件无法被默认的stash保存,需要添加参数-u
$ git stash -u

然后可以查看一下刚刚保存起来的文件:

1
2
$ git stash list
stash@(0): WIP on dev: b174a5a add 2 files

最前面的stash@(0)就是这个Stash的代名词,你也可以像其中添加多个Stash;后面的WIP就是Work In Progress的缩写,即工作进行中。
然后就可以切换回主分支修复问题了。

应用并删除Stash

问题修复完成后,现在需要继续进行刚刚的工作:

1
$ git stash pop stash@(0)

需要注意一下三点:

  • 使用pop指令相当于把Stash拿出来并套在了当前的分支上,所以需要考虑是否切换回原本的dev分支。
  • 如果pop没有指定哪一个Stash,则会选择数值最小的Stash
  • 套用Stash成功后,那个被套用的Stash就会自动删除

删除Stash

如果某些Stash确定不需要了,可以使用drop

1
$ git stash drop stash@(1)

应用Stash

把Stash捡回来,除了pop还有一个就是apply

1
$ git stash apply stash@(2)

applypop类似,还是把Stash套在当前的分支上,不同的是,apply并不会删除已经被套用的Stash。
可以把pop理解成apply+drop

使用Github

Github概述

Github是什么

Github是一个商业网站,是当前全球最大的的Git服务器,在这里可以和许多国内外开发者交朋友,既可以为他人项目贡献自己的力量,也可也为自己的项目寻求他人帮助。
同时,他也是开发者最好的履历展示平台,你曾经做过哪些专案和贡献,做出什么的贡献都一目了然,想要造假也非常难

Github和Git的区别

Github是网站,而Git是工具。Github本体是一个基于Ruby on Rails开发的Git服务器

将内容Push到Github上

在Github上创建新仓库

想要将自己的文件上传到Github中,先需要创建一个自己的仓库存放自己的文件,打开Github官网,点击右上角的+选择New repository,接着就是填写仓库名称,有两点需要说明:

  1. 仓库名称可以任意填写,但不能重复
  2. 仓库可以选择公开(Public)或者私人(Private)

创建完成后就会跳转到刚刚创建的空仓库以及Gitub给出的提示。假设我们需要开启一个新的项目,首先需要创建一个README.md文件用于Github
项目的默认说明页面,后缀名md代表了采用MarkDown语法,感兴趣的朋友可以阅读我这一篇博客快速上手MarkDown入门教程

1
$ echo "# My First Github Repository" > README.md

然后就是初始化Git并提交Commit:

1
2
3
$ git init
$ git add README.md
$ git commit -m "First Commit"

接下来就是链接我们在Github的远程仓库

1
$ git remote add origin git@github.com:Akimio521/MyGit

下面是几个参数说明:

  • git remote主要进行与远端有关的操作
  • add代表添加一个远端节点
  • origin是一个代名词,指代后面的服务器地址,按照惯例,远端节点默认使用origin这个名称,如果是从服务器Clone下来的仓库,其默认的名称是origin。当然,要是不喜欢的话也可也改成其他的名字
  • Akimio521/MyGit是我刚刚在Github上新创建的仓库,其中Akimio521是我Github的名称,MyGit是我的仓库名
    接下来就准备把刚刚提交的Commit推送(Push)到Github中:
1
$ git push -u origin master

简答的Push命令其实主要做了下面几件事:

  • master分支的内容推送(Push)到origin这个远程节点(服务器)上
  • 如果在origin``master这个分支,就会新建一个名为master的分支的仓库中不存在
  • 如果在origin的仓库中存在master这个分支,就会将master的分支标签移动到最新的Commit上
  • -u就是upstream,关于这个参数的含义会在后续说明
    如果你理解了上面这个命令,可以尝试将dev这个分支推送到MyGitlab这个远程节点上:
1
git push MyGitlab dev

upstream是什么意思

简单来说,upstream就是设置一个默认的推送位置,只要第一次使用了git push -u origin master后,之后想要将master推送到origin节点只需要使用git push这个命令

如果想要不同的分支名

之前介绍的命令git push origin master其实也是一个缩写的命令,完整的写法是git push origin master:master,含义为将本地的master分支推送到origin中的master分支,如果想要推送到Github的main分支,就可以用下面的命令:

1
$ git push origin master:main

Pull下载更新

在上一节介绍了如何将本地更新的内容推送(Push)到远程节点;这一节将介绍如何将远程的更新拉取(Pull)回本地。但在介绍Pull前,还需要了解一下Petch

什么是Petch

假设目前本地仓库是目前最新的Commit的是8c3a0a5,远程的仓库已领先一个Commit85e848b执行git fetch后就可以看到Commit85e848b的内容被拉取下来了,但多了两个分支,origin/masterorigin/HEAED,这两个分支都指向85e848b这个Commit,而masterHEAD都指向原本的8c3a0a5这个Commit。
但无论是master还是origin/master都是从同一个分支发展的,,所以可以将origin/master合并到master上:

1
$ git merge origin/master

Pull指令

看完上面对git fetch的解释,那么理解git pull就很简单了:

1
$ git pull = git fetch + git merge

Pull+Rebase

现在知道了Pull = Fetch + Merge,在之前的章节提到,合并不仅只有Merge这一个方法,还可以使用Rebase,在git pull也可以使用Reabse这个方法合并:

1
$ git pull -rebase

Push失败

在多人开发时这个问题挺常见的,原因就是当前仓库的版本领先于本地的内容,所以需要先更新本地内容再推送:

1
2
$ git pull -rebase
$ git push

提交PullRequest

什么是PR

在Github上有许多有趣的开源项目,许多热心的人会前来帮忙,但是一般项目的原作者不会直接开放原仓库的权限给一个陌生人,因此引申出PR这个机制:

  1. 先复制(Fork)原项目的仓库到自己的账号下
  2. 因为已经复制到自己的账号下,所以拥有完整的权限,可以对仓库内容进行修改
  3. 修改完成后,将自己账号下的仓库推送(Push)上去
  4. 发个通知给原作者,让原作者知道你做了修改
  5. 如果原作者觉得你的改动可以,就会合并(Merge)到原仓库中

跟上项目进度

如果在提交PR前,有其他人也抢先一步提交了PR并且原作者也接受了,那么该仓库的进度就会领先于自己账号下的仓库进度,想要提交PR的话就先要让自己的仓库跟上进度,目前Github上没有提供相应的功能,但是这里有两个方法达成这个目的

强制同步

如果是Fork别人的仓库,在Fork过来的仓库中会有一个Sync Fork的按钮,这个按钮是强制同步,原理就算删掉仓库再重写Fork一次,这样保证的是版本是最新版,但是会丢失原本仓库中自己提交的内容

跟上游同步

这个算是比较正统的做法,将作者的项目设为上游项目,Fetch下来后再手动合并
假设我有一个仓库是Fork其他人的,并且我将其Pull到本地,现在需要添加远端节点:

1
$ git remote add dummy-kao https://github.com/kaochenlong/dummy-kao

这样就新增了一个名为dummy-kao的远程节点指向作者的原仓库,然后获取原仓库最新的内容:

1
$ git fetch dummy-kao

然后将最新的版本合并进自己的版本(Merge和Rebase都行)

1
$ git merge dummy-kao/master

这样,本机进度就和原项目的进度一致了,如果你希望Github上Fork的那个仓库也保持最新的进度话只需要推送上去就行了:

1
$ git push origin master

尾声

至此,Git大部分内容已经介绍完毕,剩下的内容需要在实践中亲自探索了。若文章中有疏漏,欢迎指出。