#取消某一暂存文件、
$ git reset HEAD~n #回溯n个提交
$ git reset --hard HEAD~n #强制回溯n个提交
$ git revert HEAD~n #新的提交与HEAD~(n+1)的内容一模一样
```
使用`git reset`命令回溯到某个历史提交,不会保留后面的提交历史,而`git revert`命令则是创建与回溯历史一样的提交。
有一个小技巧可以使你的 Git 体验更简单、容易、熟悉:别名。可以通过 git config文件来轻松地为每一个命令设置一个别名。以下是一些实例。
```bash
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
```
这样,就可以用`git co`代表`git commit`,`git br`代表`git branch`等等。取消别名使可使用如下命令:
```bash
$ git config --global alias.unstage 'reset HEAD --'
```
Git 可以给历史中的某一个提交打上标签,以示重要。比较有代表性的是人们会使用这个功能来标记发布结点。经常可以看某些软件库经常发x.x.x版本。
```bash
$ git log --pretty=format:"%h %s"
b976c0a delete README.md
b4d7987 add some info
2ae267c fix one error
405cd1b add hello everyone
edb1d60 first commit
$ git tag v1 b976c0a
$ git push origin v1
通过以上命令,就可以在远程仓库tag下有v1版本,如图所示(ps:我多tag了一个v2):
![image_1cld851m736l1ugok25183j1fvcaq.png-21.5kB][15]
<h4>
<a id="E39">
5.3.9 .gitignore文件
</a>
</h4>
一般我们总会有些文件无需纳入Git的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,我们可以创建一个名为.gitignore的文件,列出要忽略的文件模式。
文件.gitignore 的格式规范如下:
• 所有空行或者以 # 开头的行都会被 Git 忽略。
• 可以使用标准的 glob 模式匹配。
• 匹配模式可以以(/)开头防止递归。
• 匹配模式可以以(/)结尾指定目录。
• 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(\*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个a,要么匹配一个b,要么匹配一个c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。使用两个星号(*) 表示匹配任意中间目录,比如a/\*\*/z可以匹配a/z, a/b/z 或 a/b/c/z等。
.gitignore 文件的例子:
```bash
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf
TIP: GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在https://github.com/github/gitignore 找到它.
这一小节是最后加上的,可能会与后面的章节的LICENSE冲突,这点注意一下就好。
LICENSE文件是一种开源许可证,即授权条款。开源软件并非完全没有限制。最基本的限制,就是开源软件强迫任何使用和修改该软件的人承认发起人的著作权和所有参与人的贡献。任何人拥有可以自由复制、修改、使用这些源代码的权利,不得设置针对任何人或团体领域的限制;不得限制开源软件的商业使用等。而许可证就是这样一个保证这些限制的法律文件。
开源许可证有上百种,这里说说最流行的六种:GPL、BSD、MIT、Mozilla、Apache和LGPL如何做选择,阮一峰在如何选择开源许可证一文中给出了一张图,直观精确,就是下图:
那么如何给自己的仓库加上LICENSE呢?很简单,点击仓库中的creat new file,然后写LICENSE.md,选择choose a license template
然后选择MIT License,再点击Review and Submit,最后点击commit change就可以了。
我们就可以在自己仓库中看到MIT协议了
有人称分支是git的必杀技,正是因为这一特性,git从众多版本管理系统中脱颖而出。git鼓励多次使用分支和合并。精通分支,将对你的版本管理十分便捷和高效。进行分支之前,先讲git是如何保存数据的。
>Note: 为了节省工作量,分支中有的图是截的书上的图,图中的校验和会与实际的校验和不同,这点注意一下就好。
与一些版本控制软件不同,Git保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。如下图所示
![8.png-1754.4kB][20]
在进行提交操作时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,我们可以很自然的想到,该提交对象会包含一个指向暂存内容快照的指针。但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。
为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件(文件需要自己动手在playground目录新建)。暂存操作会为每一个文件计算校验和,然后会把当前版本的文件快照保存到Git仓库中(Git使用blob对象来保存它们),最终将校验和加入到暂存区域等待提交:
$ git add README test.rb LICENSE
$ git commit -m 'branch note begin'
现在,Git仓库中有五个对象(忽略dd文件夹):三个blob对象(保存着文件快照)、一个树对象(记录着目录结构和blob对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。具体结构如下图:
做些修改后再次提交(两次):
$ echo "print("1")" >> README
$ git commit -am "add print("1") into README"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[master 326dd0b] add print(1) into README
1 file changed, 1 insertion(+)
$ echo "print("2")" >> README
$ git commit -am "add print("2") into README"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[master ebc9b45] add print(2) into README
1 file changed, 1 insertion(+)
两次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。见下图:
Git的分支,其实本质上仅仅是指向提交对象的可变指针。Git的默认分支名字是master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支。它会在每次的提交操作中自动向前移动。
- Git的“master”分支并不是一个特殊分支。它就跟其它分支完全没有区别。之所以几乎每一个仓库都有 master 分支,是因为git init命令默认创建它,并且大多数人都懒得去改动它。
使用`git branch`命令可以很简单地创建分支,比如创建一个testing分支。
```bash
$ git branch testing
```
这会在当前所在的提交对象上创建一个指针。如下图所示
![13.png-266.7kB][23]
那么,Git又是怎么知道当前在哪一个分支上呢?很简单,它有一个名为HEAD的特殊指针。这个特殊指针就是用来指定当前所在的分支。
![image_1clfp6pp11e0v8l71g391cok17s76k.png-8.4kB][24]
可以使用`git log`命令查看各个分支当前所指的对象。
```bash
$ git log --oneline --decorate -3 #只看倒数3个
ebc9b45 (HEAD -> master, testing) add print(2) into README
326dd0b add print(1) into README
b861d60 branch note begin
```
可以看到testing,master和现在指向master的HEAD都指向最后的提交。
创建好了分支,可以用`git checkout`命令进行切换
```bash
$ git checkout testing
Switched to branch 'testing'
$ git branch #可以使用git branch查看当前分支(前面带*号)
master
* testing
```
有一个简单的命令可以快速创建新的分支并切换到新的分支:
```bash
$ git checkout -b new_branch
#等效于
$ git branch new_branch
$ git checkout new_branch
```
于是HEAD指针也发生了变化,如下图
![image_1clfp9vofjk9t6hrb81ma0fg771.png-8.1kB][25]
在testing分支进行一次提交:
```bash
$ echo "print("3")" >> README
$ git commit -am "add print("3") into README"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[testing 8b6bbb7] add print(3) into README
1 file changed, 1 insertion(+)
git的提交记录以及分支指向如下:
![image_1clfpdg7a10ic1l7mbvpd03e3t7e.png-9.6kB][26]
可以看到testing和HEAD都移动到前面了,而master没有移动。现在我们切换到master分支,对master进行一次提交。
```bash
$ echo "MIT LICENSE" >> LICENSE
$ git commit -am "add MIT LICENSE"
warning: LF will be replaced by CRLF in LICENSE.
The file will have its original line endings in your working directory.
[master ce53a90] add MIT LICENSE
1 file changed, 1 insertion(+)
提交后,上图就变成了下图
可以使用git log
命令查看提交历史
$ git log --oneline --decorate --graph --all -5 #只看倒数五个
* ce53a90 (HEAD -> master) add MIT LICENSE
| * 8b6bbb7 (testing) add print(3) into README
|/
* ebc9b45 add print(2) into README
* 326dd0b add print(1) into README
* b861d60 branch note begin
可以看到图中的看到结构与上图相同。
由于Git的分支实质上仅是包含所指对象校验和(长度为40的SHA-1值字符串)的文件,所以它的创建和销毁都异常高效,也就是添加或者删除41个字节的速度,能做到这一点的原因是因为git是以文件快照的形式保存文件,所以创建分支只需创建一个新的指针指向快照即可,而其他的一些版本管理软件往往需要将整个项目复制到另一个目录,这就比较慢了。
学了上面的东西,可能会想,为什么要使用分支?可以通过现实场景的问题来回答这个问题。
从个人角度,你正在开发一个网站,网站已经处于正常运行状态,我们想在网站中加入新的功能,这时候你有两种选择:一是直接在当前master上进行修改和测试,二是创建一个分支。选择一可能会产生影响正常工作的代码,这是我们不想看到的。而选择二创建分支可以很方便地解决这个问题,分支不会影响当前工作的分支,可以很放心地进行开发和测试,最后对原工作分支进行合并即可。
从多人协作角度上看,这个更加直接,在一条流水线上不仅效率低,而且会产生很多混乱,比如不同人代码水平有限,有的还会编写错误的代码,使用分支可以让软件维护者很方便地查看不同的分支情况,选择合适地分支进行合并。
另外,分支的创建是非常快的,只需创建一个新的指针即可,切换分支也非常地块,这可以让我们很灵活而不受干扰地工作。
分支整合可以通过两种命令:一种是基于`git merge`命令,另一种是基于`git rebase`命令。
`git merge`是一种保存分支结构的合并,并且是**三方合并**,通过实例来看吧。
```bash
$ git merge testing #会跳出commit记录文件,默认退出即可
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)
$ git log --oneline --decorate --graph --all -6
- f23a940 (HEAD -> master) Merge branch 'testing'
|
| * 8b6bbb7 (testing) add print(3) into README
- | ce53a90 add MIT LICENSE
|/
- ebc9b45 add print(2) into README
- 326dd0b add print(1) into README
- b861d60 branch note begin
上面可以很直观地看出提交历史,用图形表示如下:
![image_1clfpn074131g1o1tt9l63nbb188.png-16.1kB][28]
既然叫三方合并,是那三方呢?见下图
![image_1clfq8dr31dgt12fjc7hmfgkkm8l.png-16.2kB][29]
上图中浅蓝色方块就是三方,分别是当前分支,要合并的分支,以及这两者的共同祖先(这个由git自己决定),merge合并会根据当前分支与祖先的差异和要合并的分支与祖先的差异进行共同合并。
<h4>
<a id="F52">
6.5.2 git rebase——变基
</a>
</h4>
合并还有一种方法:那就是提取某一分支(8b6bbb7)中引入的补丁和修改,然后在另一分支(ce53a90)的基础上应用一次。在Git中,这种操作就叫做**变基**。可以使用 rebase命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
```bash
$ git reset --hard ce53a90 #首先通过reset回溯到合并前的状态
HEAD is now at ce53a90 add MIT LICENSE
$ git log --oneline --decorate --graph --all -5
* ce53a90 (HEAD -> master) add MIT LICENSE
| * 8b6bbb7 (testing) add print(3) into README
|/
* ebc9b45 add print(2) into README
* 326dd0b add print(1) into README
* b861d60 branch note begin
$ git checkout testing #切换到要进行合并的分支
$ git rebase master #使用rebase命令将testing合并到master
First, rewinding head to replay your work on top of it...
Applying: add print(3) into README
$ git log --oneline --decorate --graph --all -5
* 972909a (HEAD -> testing) add print(3) into README
* ce53a90 (master) add MIT LICENSE
* ebc9b45 add print(2) into README
* 326dd0b add print(1) into README
* b861d60 branch note begin
通过git log
命令,整个历史可以看到没有想merge那样的分岔路,而是一条笔直的提交。rebase的原理是首先找到两个分支(即当前分支 testing、变基操作的目标基底分支master)的最近共同祖先,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底master,最后以此将之前另存为临时文件的修改依序应用。之前分支出去的提交就没有了延续,不会出现在提交历史中了。可以用新的图来表示这个过程。
无论是通过三方merge合并,还是通过rebase变基,最后的结果是一样的,唯一不同的是提交历史的区别,merge还会保存分支的历史,而rebase则不会,它的提交历史是没有分叉的直线,相对整洁。
现在用commit id来简单表示校验和,你在主分支中的C2上创建了一个特性分支server,为服务端添加了一些功能,提交了C3和C4。然后从C3上创建了特性分支client,为客户端添加了一些功能,提交了C8和C9。最后,你回到server分支,又提交了C10。(ps:这里没有进行代码实践,有兴趣的朋友可以自己试试)
![image_1clgoipfepthue1crleu516mdek.png-23.3kB][31]
现在你希望将client中的修改合并到主分支并发布,但暂时并不想合并 server 中的修改,因为它们还需要经过更全面的测试。这时,你就可以使用git rebase命令的--onto选项,选中在client分支里但不在server分支里的修改(即C8和C9),将它们在master分支上重放:
```bash
$ git rebase --onto master server client
```
以上命令的意思是:“取出client分支,找出处于client分支和server分支的共同祖先之后的修改,然后把它们在 master分支上重放一遍”。效果如下:
![image_1clgorr5q5no1it51qt415nb18hsf1.png-19.7kB][32]
然后进行快速合并,
```bash
$ git checkout master
$ git merge client
```
接下来你决定将server分支中的修改也整合进来。使用`git rebase [basebranch] [topicbranch]`命令可以直接将特性分支(即本例中的server)变基到目标分支(即master)上。这样做能省去你先切换到
server 分支,再对其执行变基命令的多个步骤。
```bash
$ git rebase master server
```
结果如下:
![image_1clgp3bke7cfdr1q3r1je0rn2fe.png-24kB][33]
最后我们进行快速合并以及删除server,client分支。
```bash
$ git checkout master
$ git merge server
$ git branch -d client
$ git branch -d client
```
最终的提交历史:
![image_1clgpimj51nbgk45d0hof2butfr.png-15.7kB][34]
奇妙的变基也并非完美无缺,要用它得遵守一条准则:**不要对在你的仓库外有副本的分支执行变基。**否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你
先简单说说这个意思,试想,A团队在本地进行了一次三方合并,然后push到远程服务器,B团队发现仓库有更改,通过git pull
将新的提交拉到本地进行合并。可不久,A团队想对发到远程服务器的版本做做变基,把上次的三方合并修改成了变基,再次push到远程服务器。B团队发送远程服务器版本又更新,而且自己上一次pull下来的一些提交不见了(rebase丢弃掉了),只好再次进行合并,但发现没有,A团队两次推送没有更改最后的提交内容,也就是说B团队合并了两次相同的提交(历史混乱,B团队尴尬),不仅如此,A团队是想清理掉一些提交历史的,但B团队还保留那些历史,等B团队push到远程服务器时,A团队看到自己rebase去掉的历史又出现了(A团队尴尬)。
这样说不太容易理解,下面通过图形进行描述。
- 别人提交了一次合并,你抓取别人的提交,合并到自己的开发分支
- 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交
此时如果你执行git log
命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到C4和C6,因为之前就是他把这两个提交通过变基丢弃的。
解决办法:用变基解决变基,执行git rebase teamone/master,Git将会
- 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)
- 检查其中哪些提交不是合并操作的结果(C2,C3,C4)
- 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4')
- 把查到的这些提交应用在 teamone/master 上面
当然这个办法有一个前提,那就是C4'和C4要几乎一样,否则变基无法识别。还有一个缓解疼痛的方法,同git pull --rebase
替换git pull
,这个方法不会产生新的提交,也是变基。当然,最好的办法还是那条准则:不要对在你的仓库外有副本的分支执行变基!
当然在合并的过程中,可能会出现合并冲突的。合并冲突时,`git merge`命令会显示是在哪个文件产生的冲突,我们来通过例子来试一试。
```
$ git checkout master #接着上面的git rebase
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
$ git merge testing
Updating ce53a90..972909a
Fast-forward
README | 1 +
1 file changed, 1 insertion(+)
$ git branch -d testing
Deleted branch testing (was 972909a).
$ git checkout -b newtesting
Switched to a new branch 'newtesting'
$ echo "print("newtesting")" >> README
$ git commit -am "Newtesting commit"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[newtesting 1aaf545] Newtesting commit
1 file changed, 1 insertion(+)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 5 commits.
(use "git push" to publish your local commits)
$ echo "print("master")" >> README
$ git commit -am "master commit"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[master 3883017] master commit
1 file changed, 1 insertion(+)
$ git merge newtesting
Auto-merging README
CONFLICT (content): Merge conflict in README
Automatic merge failed; fix conflicts and then commit the result.
可以看到最后出现了合并冲突,冲突出现在README文件中,我们需要打开文件看看冲突情况:
```bash
$ vi README
print(1)
print(2)
print(3)
<<<<<<< HEAD
print(master)
=======
print(newtesting)
>>>>>>> newtesting
为了解决冲突,你必须选择使用由=======分割的两部分中的一个,或者你也可以自行合并这些内容。我们选择print(master),从<<<<<<<<HEAD到>>>>>>>>newtesting,除了print(master)那句话,其他的都删除。
$ git add README
$ git status
On branch master
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
$ git commit -am "solve conflicts"
[master dc6b322] solve conflicts
$ git log --oneline --decorate --graph --all -8
* dc6b322 (HEAD -> master) solve conflicts
|\
| * 1aaf545 (newtesting) Newtesting commit
* | 3883017 master commit
|/
* 972909a add print(3) into README
* ce53a90 add MIT LICENSE
* ebc9b45 add print(2) into README
* 326dd0b add print(1) into README
* b861d60 branch note begin
还记得我们之前通过`git push -u origin master`命令推送我们地仓库吗?最后推送的地址是`origin/master`,这个叫做远程分支。其实在本地有一个`origin/master`的指针,这个叫做远程跟踪分支,用来跟踪远程分支(最后一次沟通)的状态,这个指针所指向的位置不会随着本地操作而发生改变,而当使用`git fetch`、`git pull`等命令会随着远程仓库的状态而改变。而本地的master指针是会默认追踪`origin/master`,这个追踪是从`git clone`或者`git remote add`那一刻起。
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。本地的分支并不会自动与远程仓库同步-你必须显式地推送想要分享的分支。这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。下面通过实例进行学习。
首先我们点开在GitHub创建的仓库——playground,然后如图所示,创建一个新分支testing,由于我已经创建,所以已经显示有testing分支。
我们在本地中使用git fetch
命令,将刚刚创建的分支下载到本地。
$ git fetch
From github.com:FangYang970206/playground
* [new branch] testing -> origin/testing
可以看到多出新的分支testing(本地分支)跟踪origin/master(远程跟踪分支)。我们通过git checkout <branch_name>
看到分支是否处于跟踪的状态。
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git checkout testing
Switched to a new branch 'testing'
Branch 'testing' set up to track remote branch 'testing' from 'origin'.
现在分别对testing和master分支做一次提交并push
$ echo "print("testing")" >> README
$ git commit -am README
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[testing f2466c5] README
1 file changed, 1 insertion(+)
$ git push origin testing
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 367 bytes | 183.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:FangYang970206/playground.git
dc6b322..f2466c5 testing -> testing
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ echo "print("master1")" >> README
$ git commit -am README
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[master b849b25] README
1 file changed, 1 insertion(+)
$ git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 360 bytes | 360.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:FangYang970206/playground.git
dc6b322..b849b25 master -> master
还可以更改其他本地指针来跟踪远程跟踪分支,下面通过实例来学习
$ git checkout -b foo origin/master
Switched to a new branch 'foo'
Branch 'foo' set up to track remote branch 'master' from 'origin'.
$ echo "# if" >> test.rb
$ git commit -am "commit test.rb"
warning: LF will be replaced by CRLF in test.rb.
The file will have its original line endings in your working directory.
[foo d3f4109] commit test.rb
1 file changed, 1 insertion(+)
$ git push origin HEAD:master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 260 bytes | 86.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:FangYang970206/playground.git
b849b25..d3f4109 HEAD -> master
$ git checkout testing
Switched to branch 'testing'
Your branch is up to date with 'origin/testing'.
$ git checkout -b testing1 origin/testing
Switched to a new branch 'testing1'
Branch 'testing1' set up to track remote branch 'testing' from 'origin'.
$ echo "print("testing1")" >> README
$ git commit -am "print testing1"
warning: LF will be replaced by CRLF in README.
The file will have its original line endings in your working directory.
[testing1 2600339] print testing1
1 file changed, 1 insertion(+)
$ git push origin HEAD:testing
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 373 bytes | 124.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:FangYang970206/playground.git
f2466c5..2600339 HEAD -> testing
$ git log --oneline --decorate --graph --all -9
* d3f4109 (origin/master, origin/HEAD, foo) commit test.rb
* b849b25 (master) README
| * 2600339 (HEAD -> testing1, origin/testing) print testing1
| * f2466c5 (testing) README
|/
* dc6b322 solve conflicts
|\
| * 1aaf545 Newtesting commit
* | 3883017 master commit
|/
* 972909a (newtesting) add print(3) into README
* ce53a90 add MIT LICENSE
可以看到之前跟踪远程跟踪分支的master和testing指针是没有移动,新建的foo和testing1取代了它们。这样要注意一点,使用不同于远程分支名的分支进行push,一定要指定当前分支,不然默认还是通过branch_name推送到origin/branch_name,出现Everything up-to-date,指定可以通过以上的HEAD(source):testing(target)
这里的取代方式可以有两种:一种是直接新建分支取代,也就是上面的git checkout -b testing1(newbranch) origin/testing(Remote tracking branch)
,还有一种是通过现在分支进行取代,可以通过git branch -u <Remote tracking branch> <existed branch>
命令,注意一点,不要出现远程跟踪分支和跟踪的指针出现分叉,这样会导致Your branch and 'origin/xxx' have diverged
这个问题,详细可参考这个链接。
强烈建议去https://learngitbranching.js.org/,这个网站有很多分支的练习,还有一部分是远程控制的练习,配合动画,非常适合正在学习git的朋友,相信可以让你的git本领上一个台阶。
如果你发现你感兴趣的仓库的bug或者你想添加某个新功能到你感兴趣的仓库中,这时候你就可以pull request这个沟通利器了(一般是先在Issue中提出想法或问题,沟通好后可以在对应编号上创建pull request)。
这里介绍一下一个仓库,可供你进行pull request练习,仓库地址是https://github.com/Data4Democracy/github-playground,当然,也欢迎你对本文的仓库https://github.com/FangYang970206/playground进行pull request,由于对自己的仓库进行pull request,本质上就是本地合并再提交,所以没有必要,完全可以在本地进行,pull request是以协作开发为目的,为了方便,对https://github.com/Data4Democracy/github-playground进行pull request,看本篇文章的朋友就可以随意选择两者之一。
pull request流程:
- 先fork你感兴趣的仓库到自己的仓库中(副本)
- 将副本仓库克隆到本地
- 从master分支中创建一个新分支
- 在分支中进行修改,以此改进项目
- 将分支推送到github仓库
- 创建一个pull request
- 讨论,根据实际情况继续修改
- 项目的拥有者合并或关闭你的合并请求
打开https://github.com/Data4Democracy/github-playground,点击Fork,等待副本仓库生成,复制下载url,使用git clone
到本地,然后在master分支下创建PR_practice分支,然后使用vi hello_test.py对文件进行修改,在后面添加一句print("PR practice, thanks"),然后Ese:wq保存后,再进行提交修改,最后push到origin/PR_practice分支就可以了,详细过程如下:
$ git clone git@github.com:FangYang970206/github-playground.git
Cloning into 'github-playground'...
remote: Counting objects: 230, done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 230 (delta 2), reused 4 (delta 1), pack-reused 221
Receiving objects: 100% (230/230), 307.80 KiB | 324.00 KiB/s, done.
Resolving deltas: 100% (85/85), done.
$ cd github-playground/
$ git checkout -b PR_practice
Switched to a new branch 'PR_practice'
$ vi hello_test.py
$ git add hello_test.py
$ git commit -m "add PR practice"
[PR_practice a874bdf] add PR practice
1 file changed, 1 insertion(+)
$ git push origin PR_practice
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 309 bytes | 309.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:FangYang970206/github-playground.git
* [new branch] PR_practice -> PR_practice
我们再进入我们感兴趣的仓库地址,就会发现如下页面
我们点击旁边的compare&pull request或者直接点击New pull request,写一下标题和commit,然后creat pull request。
最后等待仓库拥有者审核,对这个pull request进行讨论,看是否要进行再修改等等。另外,每一个pull request都可以看files changed,可以看到有哪些行添加进去了,有哪些删除了,很是方便。
以上,就是一个pull request的流程,记得动手操作一遍。
最后,希望这篇文章能对看的朋友有所帮助,欢迎给这篇文章来个star。本文大量参考了[Pro Git][43],建议读者可以去读一读这本git官网推荐的书籍。[git-github-intro][44]对git有一个不错大致简介。[learngitbranching][45]是一个非常不错的动手学习网站,推荐去动手学习,更多资源可以去参考[trygit][46]里面的内容。
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=24bgt0s40480c