这个周末用两天好好学习了git的相关命令,主要参考的是廖学峰老师的网站,十分感谢廖学峰老师。以下是我学习的时候记的一些笔记,一些内容都是直接将内容摘过来,其他的是我自己在电脑上操作后综合了解的知识总结出来的。
在这把廖老师的博客地址贴出来。
http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
Git简介
集中式vs分布式
Linus一直痛恨的CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢?
先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,这还不得把人给憋死啊。
那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
创建版本库
在一个想要让git进行版本控制的目录下执行:
➜ git mkdir gitStydy
➜ git cd gitStydy
➜ gitStydy pwd
/Users/tangcheng/Study/git/gitStydy
➜ gitStydy git init
Initialized empty Git repository in /Users/tangcheng/Study/git/gitStydy/.git/
把文件添加到版本库
言归正传,现在我们编写一个readme.txt文件,内容如下:
Git is a version control system.
Git is free software.
第一步,用命令git add
告诉Git,把文件添加到仓库:
$ git add readme.txt
第二步,使用git commit
把文件提交到版本库
➜ gitStydy git:(master) ✗ git commit -m "first commit by tangcheng"
[master (root-commit) 2e991cd] first commit by tangcheng
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
git版本控制
现在我们已经提交了一个readme.txt文件了
现在,运行git status
命令看看结果:
➜ gitStydy git:(master) ✗ 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.txt
no changes added to commit (use "git add" and/or "git commit -a")
➜ gitStydy git:(master) ✗
git status
命令可以让我们时刻掌握仓库当前的状态
同svn类似的,使用git diff
查看文件内容的修改:
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
(END)
查看完文件的修改以后,提交修改同样也是使用git add
和git commit
命令
➜ gitStydy git:(master) ✗ git add readme.txt
➜ gitStydy git:(master) ✗ git commit readme.txt -m "second modify"
[master 3f9f817] second modify
1 file changed, 1 insertion(+), 1 deletion(-)
➜ gitStydy git:(master)
版本回退
相关命令
当我们多次修改文件以后,git会生成一种版本库的一个快照,这个快照在Git中被称做commit
。一旦文件被我们改乱了或误删了都可以从最近的一次commit中找回.
当然了,在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统干什么。版本控制系统肯定有某个命令可以告诉我们历史记录,在Git中,我们用git log
命令查看:
commit aec699a85bb204b19101482c1c651755d2f01b7b
Author: tangcheng <tangcheng15911@sina.com>
Date: Sat Apr 9 13:41:21 2016 +0800
third modify
commit 3f9f81701e5369517c756092db2179b238ebcf03
Author: tangcheng <tangcheng15911@sina.com>
Date: Sat Apr 9 13:37:26 2016 +0800
second modify
commit 2e991cd3a148dbdb86179e952813caceb5d74371
Author: tangcheng <tangcheng15911@sina.com>
Date: Fri Apr 8 19:03:20 2016 +0800
first commit by tangcheng
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数
➜ gitStydy git:(master) git log --pretty=oneline
aec699a85bb204b19101482c1c651755d2f01b7b third modify
3f9f81701e5369517c756092db2179b238ebcf03 second modify
2e991cd3a148dbdb86179e952813caceb5d74371 first commit by tangcheng
你看到的一大串类似3628164...882e1e0
的是commit id
(版本号),和SVN不一样,Git的commit id
不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示。为什么commit id
需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:
好了,现在我们启动时光穿梭机,准备把readme.txt回退到上一个版本,也就是“first commit by tangcheng”的那个版本
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,也就是最新的提交3628164...882e1e0
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
现在,我们要把当前版本“append GPL”回退到上一个版本“second modify”,就可以使用git reset
命令:
➜ gitStydy git:(master) git reset --hard HEAD^
HEAD is now at 3f9f817 second modify
--hard
参数有啥意义?这个后面再讲
这样readme.txt就回退到了上一个版本。
还可以继续回退到上一个版本first commit by tangcheng
,不过且慢,然我们用git log
再看看现在版本库的状态
commit 3f9f81701e5369517c756092db2179b238ebcf03
Author: tangcheng <tangcheng15911@sina.com>
Date: Sat Apr 9 13:37:26 2016 +0800
second modify
commit 2e991cd3a148dbdb86179e952813caceb5d74371
Author: tangcheng <tangcheng15911@sina.com>
Date: Fri Apr 8 19:03:20 2016 +0800
first commit by tangcheng
最新的那个版本third modify
已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?
办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个third modify
的commit id
是3628164…,于是就可以指定回到未来的某个版本:
➜ gitStydy git:(master) git reset --hard aec699a
HEAD is now at aec699a third modify
再小心翼翼地看看readme.txt的内容:
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
果然,我胡汉三又回来了。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL
:
改为指向add distributed
:
然后顺便把工作区的文件更新了。所以你让HEAD
指向哪个版本号,你就把当前版本定位在哪。
现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id
怎么办?
在Git中,总是有后悔药可以吃的。当你用$ git reset --hard HEAD^
回退到second modify
版本时,再想恢复到second modify
,就必须找到second modify
的commit id
。Git提供了一个命令git reflog
用来记录你的每一次命令:
➜ gitStydy git:(master) git reflog
aec699a HEAD@{0}: reset: moving to aec699a
3f9f817 HEAD@{1}: reset: moving to HEAD^
aec699a HEAD@{2}: commit: third modify
3f9f817 HEAD@{3}: commit: second modify
2e991cd HEAD@{4}: commit (initial): first commit by tangcheng
小结
HEAD
指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
- 穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本。 - 要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本
工作区和暂存区
Git和其他版本控制系统如SVN的不同之处就是有缓存去的概念。
工作区
就是你在电脑里能看到的目录,比如我的gitStudy文件夹就是一个工作区:
版本库
工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
分支和HEAD
的概念我们以后再讲。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
- 第一步是用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区; - 第二步是用
git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
现在先对readme.txt做个修改,比如加上一行内容:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
然后,在工作区新增一个LICENSE
文本文件(内容随便写)。
Git非常清楚地告诉我们,readme.txt
被修改了,而LICENSE
还从来没有被添加过,所以它的状态是Untracked
现在,使用两次命令git add
,把readme.txt
和LICENSE
都添加后,用git status
再查看一下:
➜ gitStydy git:(master) ✗ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENSE
modified: readme.txt
现在,暂存区的状态就变成这样了:
所以,git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。
➜ gitStydy git:(master) ✗ git commit -m "understand how stage works"
[master e6bbe31] understand how stage works
2 files changed, 2 insertions(+)
create mode 100644 LICENSE
现在版本库变成了这样,暂存区就没有任何内容了:
管理修改
下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
为什么说Git管理的是修改,而不是文件呢?我们还是做实验。第一步,对readme.txt做一个修改,比如加一行内容:
➜ gitStydy git:(master) ✗ git add readme.txt
➜ gitStydy git:(master) ✗ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
然后,再修改readme.txt,然后提交:
➜ gitStydy git:(master) ✗ git commit -m "git tracks changes"
[master f3a2c4d] git tracks changes
1 file changed, 1 insertion(+)
提交完后我们在查看git status:
➜ gitStydy git:(master) ✗ git status
On branch master
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git checkout – …” to discard changes in working directory)
modified: readme.txt
咦,怎么第二次的修改没有被提交?
别激动,我们回顾一下操作过程:
第一次修改 -> git add
-> 第二次修改 -> git commit
你看,我们前面讲了,Git管理的是修改,当你用git add
命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit
只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
提交后,用git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别:
diff --git a/readme.txt b/readme.txt
index 76d770f..a9c5755 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
+Git tracks changes of files.
可见,第二次修改确实没有被提交。
那怎么提交第二次修改呢?你可以继续git add
再git commit
,也可以别着急提交第一次修改,先git add第二次修改,再git commit
,就相当于把两次修改合并后一块提交了
撤销修改
相关命令
要在文件中撤销修改,比如,现在readme.txt是这个样子:
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.
现在想要删除最后一句。用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.txt
你可以发现,Git会告诉你,git checkout -- file
可以丢弃工作区的修改:
➜ gitStydy git:(master) ✗ git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
git checkout -- file
命令中的–很重要,没有--
,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout
命令。
如果修改已经被git add
到了缓存区。
➜ gitStydy git:(master) ✗ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
Git同样告诉我们,用命令git reset HEAD file
可以把暂存区的修改撤销掉(unstage),重新放回工作区:
➜ gitStydy git:(master) ✗ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD
时,表示最新的版本。
小结
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节(前提是没有推送到远程库)
删除文件
相关命令
在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交
➜ gitStydy git:(master) ✗ git add test.txt
➜ gitStydy git:(master) ✗ git commit -m "add test.txt"
[master 5bf87da] add test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
然后
rm test.txt
此时使用git status
:
➜ gitStydy git:(master) ✗ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: test.txt
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm
删掉,并且git commit
:
➜ gitStydy git:(master) ✗ git rm test.txt
rm 'test.txt'
➜ gitStydy git:(master) ✗ git commit -m "rm test.txt"
[master db53805] rm test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
现在,文件就从版本库中被删除了。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件从库中恢复到最新版本(没有git rm
和commit
之前):
$ git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
小结
命令git rm用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
远程仓库
Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
使用github
由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "youremail@example.com"
你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh
目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:
添加远程仓库
添加
我们根据GitHub的提示,在本地的gitStudy仓库下运行命令:
➜ gitStydy git:(master) git remote add origin https://github.com/VergilTang/gitStudy.git
添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。
下一步,就可以把本地库的所有内容推送到远程库上:
➜ gitStydy git:(master) git push -u origin master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.60 KiB | 0 bytes/s, done.
Total 20 (delta 5), reused 0 (delta 0)
To https://github.com/VergilTang/gitStudy.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。
git push -u origin master
-u
参数存在的话git会把本地分支推送到远程的master
分支,还会把本地master
分支和远程的master
分支关联起来
从现在起,只要本地作了提交,就可以通过命令:
git push origin master
小结
- 要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
- 关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
- 此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
- 分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
从远程库克隆
克隆
假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
➜ git git clone git@github.com:VergilTang/tcFramwork.git
Cloning into 'tcFramwork'...
Warning: Permanently added the RSA host key for IP address '192.30.252.129' to the list of known hosts.
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
Checking connectivity... done.
小结
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone
命令克隆。
Git
支持多种协议,包括https
,但通过ssh支持的原生git协议速度最快。
分支管理
简介
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
创建和合并分支
git分支原理
每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支,刚开始的话就是master分支。HEAD
严格来说并不是指向某一个commit,而是指向一个指向commit的master指针。每一次提交master指针都会前移一步。
当我们创建了一个新的分支dev,HEAD
指针就指向了此分支dev分支,此时的所有commit
都是在dev分支上提交的。我们在dev上的工作完成以后,再把dev分支合并到master分支上(最简单的方法就是直接让master指针指向dev指针所指向的commit),此时便可以把dev指针安全删除了。
示意图:
相关实战
使用git checkout -b
创建分支
git checkout -b dev
此命令相当于一下两条命令:
git branch dev
git checkout dev
使用
git branch
查看分支情况,有*
在前面的分支代表HEAD
指针所指向的当前分支
➜ gitStydy git:(dev) git branch
* dev
master
然后在分支上做一些修改后再提交后,在分支上的工作结束,讲dev合并到master分支
git checkout master
git merge dev
合并后删除dev分支:
git branch -d dev
小结
Git 鼓励大量使用分支:
* 查看所有分支: git branch
* 创建+分支: git checkout -b <branch-name>
* 创建分支: git branch <branch-name>
* 切换分支: git checkout <branch-name>
* 合并某分支到当前分支: git merge <branch-name>
* 删除分支: git branch -d <branch-name>
解决冲突
冲突测试
新建一个分支feature1
➜ gitStydy git:(master) git branch feature1
➜ gitStydy git:(master) git checkout feature1
修改最后一句话添加Creating a new branch is quick AND simple.
git add && git commit -m "AND simple"
切换到master
,修改最后一句话添加Creating a new branch is quick & simple.
git add && git commit -m "& simple"
现在master
和feature1
分支各自都有新的提交
此时进行合并,讲feature1合并到master,报conflict
➜ gitStydy git:(master) git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
此时gut status
可以看到具体的冲突文件
打开相应的冲突文件,解决冲突后
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存
git add
git commit -m "conflict fixed"
现在,master
分支和feature1
分支变成了下图所示:
使用带参数的git log
也可以查看分支的合并情况:
* 0bfc89d conflict fixed
|\
| * 74857ab AND simple
* | ebf1c2a & simple
|/
...
记得删除feature1分支
小结
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
用git log --graph
命令可以看到分支合并图。
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果强制禁用Fast forward
模式,Git在merge的时候会生成一个新的commit,这样,从分支历史上就能看出分支信息。
强制禁用Fast forward
使用命令git merge
的时候加上参数--no-ff
创建一个新的分支,修改readme.txt
文件后,再使用非Fast forword
的方式合并到master分支上
git merge --no-ff -m "merge with --no-ff" dev
因为在使用--no-ff
的时候会产生一个commit,所以要有-m
参数
不使用Fast forward模式,merge后就像这样:
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
小结
Git分支十分强大,在团队开发中应该充分应用。
合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。
Bug分支
保存当前未提交的修改
当我们开发某些需求的时候,有部分代码写到一半,需要修改master上的一个bug,此时该如何保存当前的工作区呢? 使用如下命令:
➜ gitStydy git:(dev) ✗ git status
On branch dev
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.txt
no changes added to commit (use "git add" and/or "git commit -a")
➜ gitStydy git:(dev) ✗ git stash
Saved working directory and index state WIP on dev: a4c90de test
HEAD is now at a4c90de test
➜ gitStydy git:(dev) git status
On branch dev
nothing to commit, working directory clean
此时再切换回master分支临时生成一个新的bug分支:
git checkout -b issue-101
将bug修改完并提交以后,将issue-101
和master
合并
➜ gitStydy git:(master) git merge --no-ff -m "merged bug fix 101" issue-101
然后删除issue-101分支,接着切回dev分支,释放原来在dev下的修改:
git stash apply@{0} && git stash drop
那个数字是stash中的第几个stash
or
git stash pop
使用git stash list
可以查看stash的内容
小结
修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场。
Feature分支
Feature综述
软件开发中,总有无穷无尽的新的功能要不断添加进来。
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
实践
➜ gitStydy git:(dev) git checkout -b feature-vulcan
➜ gitStydy git:(feature-vulcan) vi index.php
➜ gitStydy git:(feature-vulcan) ✗ git add index.php
➜ gitStydy git:(feature-vulcan) ✗ git commit -m "add a php file"
此时要切换回dev准备合并,但此时此新的feature功能又不要了,现在讲其删除
➜ gitStydy git:(dev) git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
➜ gitStydy git:(dev) git branch -D feature-vulcan
Deleted branch feature-vulcan (was 1628df7).
小结
开发一个新feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
多人协作
概述
如果我们的本地库是从远程仓库克隆的或已经和远程仓库关联了,此时本地的master和远程的master对应起来了,远程仓库的默认名称是origin
查看远程仓库的信息:
`git remote` <-v>
origin https://github.com/VergilTang/gitStudy.git (fetch)
origin https://github.com/VergilTang/gitStudy.git (push)
如果没有推送权限,就看不到push的地址。
推送分支
git push origin master
代表把master
推送到远程仓库中去
并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master
分支是主分支,因此要时刻与远程同步;dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;- bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
- feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!
抓取分支
多人协作时,大家都会往master
和dev
分支上推送各自的修改。
切换到远程仓库的分支:
git checkout -b dev origin/dev
此处测试是我自己用我的centOS虚拟机和我的mac分开操作实验的。
使用git checkout -r
可以查看远程仓库的所有分支
如果两个人协同开发同一个分支,第一个人讲修改内容推送到远程仓库,第二个人再推送他修改的内容到远程仓库的时候,此时第二个人就会提示冲突。
➜ gitStydy git:(dev) git push origin dev
To https://github.com/VergilTang/gitStudy.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'https://github.com/VergilTang/gitStudy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
解决办法:
Git已经提示我们,先用git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送
➜ gitStydy git:(dev) git pull
...
From https://github.com/VergilTang/gitStudy
e7b7581..ac11215 dev -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> dev
第一次拉取失败了,应该使用以下命令设置git pull
应该pull origin上的哪个分支:
➜ gitStydy git:(dev) git branch --set-upstream-to=origin/dev dev
Branch dev set up to track remote branch dev from origin.
➜ `git pull`
如果pull下来有冲突,先在本地解决冲突,git add && git commit
后再git push
到远程仓库
多人协作工作模式
1.先试着用git push origin <branch-name>
推送自己的修改
2.如果推送失败,则因为远程分支比你的本地更新,需要先用git pull
试图合并
3.如果合并有冲突,则解决冲突,并在本地提交;
4.没有冲突或者解决掉冲突后,再用git push origin branch-name
推送就能成功!
如果git pull
提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name
。
小结
- 查看远程库信息,使用
git remote -v
- 从本地推送分支,使用
git push origin branch-name
,如果推送失败,先用git pull
抓取远程的新提交; - 在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致; - 建立本地分支和远程分支的关联,使用
git branch --set-upstream branch-name origin/branch-name
; - 从远程抓取分支,使用
git pull
,如果有冲突,要先处理冲突。
标签管理
发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
创建标签
切换到需要打标签的分支,键入命令git tag <tagname>
:
git tag v1.0
查看所有便签:
git tag
默认的标签是打在最新提交的commit上的。如果需要在过去的commit上打标签,则需要提供历史提交的commit id
8f23a5b merged bug fix 101
7f54eb1 fix bug 101
934282d merge with --no-ff
a4c90de test
0bfc89d conflict fixed
...
➜ gitStydy git:(master) git tag v0.9 0bfc89d
➜ gitStydy git:(master) git tag
v0.9
v1.0
注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>
查看标签信息
git show v1.0
还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字:
git tag -a v0.1 -m "version 0.1 released" 2e991cd
小结
- 命令
git tag <name>
用于新建一个标签,默认为HEAD,也可以指定一个commit id
; git tag -a <tagname> -m "blablabla..."
可以指定标签信息;git tag -s <tagname> -m "blablabla..."
可以用PGP签名标签;- 命令
git tag
可以查看所有标签。
操作标签
删除标签
➜ gitStydy git:(master) git tag -d v0.1
Deleted tag 'v0.1' (was 31178a0)
推送标签到远程仓库git push origin <tagname>
:
➜ gitStydy git:(master) git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/VergilTang/gitStudy.git
* [new tag] v1.0 -> v1.0
或者,一次性推送全部尚未推送到远程仓库的标签:
➜ gitStydy git:(master) git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/VergilTang/gitStudy.git
* [new tag] v0.9 -> v0.9
删除远程仓库中的标签
1.先删除本地的便签
2.然后使用命令push推送到远程仓库
➜ gitStydy git:(master) git tag -d v0.9
Deleted tag 'v0.9' (was 0bfc89d)
➜ gitStydy git:(master) git push origin :refs/tags/v0.9
To https://github.com/VergilTang/gitStudy.git
- [deleted] v0.9
➜ gitStydy git:(master)
小结
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签。
使用GitHub
在gitHub上找一个自己感兴趣的项目,fork到自己这里,然后修改,修改完以后可以发送pull Request到原作者的gitHub,由他决定是否接收你的修改.
自定义Git
例如:
让git显示颜色
git config --global color.ui true
忽略特殊文件
可以在Git工作区的根目录下创建特殊的.gitignore
文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则:
1.忽略操作系统自动生成的文件,比如缩略图等;
2.忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
3.忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
举个例子:
假设你在Windows下进行Python开发,Windows会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下就会有Desktop.ini文件,因此你需要忽略Windows自动生成的垃圾文件:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
然后,继续忽略Python编译产生的.pyc、.pyo、dist
等文件或目录:
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
加上你自己定义的文件,最终得到一个完整的.gitignore
文件,内容如下:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
最后一步就是把.gitignore也提交到Git,就完成了!当然检验.gitignore
的标准是git status
命令是不是说working directory clean
。
配置别名
我们只需要敲一行命令,告诉Git,以后st就表示status:
`$ git config --global alias.st status`
`$ git config --global alias.co checkout`
`$ git config --global alias.br branch`
--global
参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。
在撤销修改一节中,我们知道,命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:
git config --global alias.unstage 'reset HEAD'
配置一个git last
,让其显示最后一次提交信息:
git config --global alias.last 'log -1'
甚至还有人丧心病狂地把lg
配置成了:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
配置文件
配置Git的时候,加上--global
是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
配置文件放哪了?每个仓库的Git配置文件都放在.git/config
文件中:
➜ learngit git:(master) cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote “origin”]
url = https://github.com/VergilTang/learngit.git
fetch = +refs/heads/:refs/remotes/origin/
[branch “master”]
remote = origin
merge = refs/heads/master
而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig
中:
[user]
name = yourName
email = yourName@email.com
[color]
ui = true
[alias]
st = status
co = checkout
ci = commit
revert = reset HEAD
last = log -l
lg = log –color –graph –pretty=format:’%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset’ –abbrev-commit
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。