----------------- Git 学习 ------------------
Git 简介
Git,分布式版本控制系统!
我的理解:对每次操作都有一次记录,方便修改和回退!来达到版本的控制.
** 集中式和分布式的区别 **
集中式版本控制系统
- 版本库集中存放在中央服务器上,干活的时候,要先从服务器取得最新版本,然后开始干活.干完活,再把自己的活推送给中央服务器.
- 比喻: 图书馆借书 -> 看完 -> 再还回去
- 缺点: 必须联网才能工作(或者内部局域网里搭建了一个服务器来承载);外网,网速慢,下载和提交时间长
分布式版本控制系统
- 根本没有"中央服务器",每个人的电脑都是一个完整的版本库,工作的时候,就不需要联网了,因为版本库就在自己电脑上.
- 每个人都是一个版本库,那如何体现多人协同合作呢? 这里只需要把各自的修改推送给对方,就可以互相看到对方的修改了,强大的分支管理.
Git 安装
Ubuntu 下 安装
首先,你可以试着输入git,看看系统有没有安装Git:
$ git
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git
像上面的命令,有很多Linux会友好地告诉你Git没有安装,还会告诉你如何安装Git。
执行 apt-get install git 安装
twitch@laravel:~$ sudo apt-get install git
[sudo] twitch 的密码:
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
git 已经是最新版 (1:2.7.4-0ubuntu1)。
这边我是之前就安装好了!
注意: 系统版本老一点的话,把命令改为sudo apt-get install git-core,因为以前有个软件也叫GIT(GNU Interactive Tools),结果Git就只能叫git-core了。由于Git名气实在太大,后来就把GNU Interactive Tools改成gnuit,git-core正式改为git。
创建版本库
版本库:
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
创建一个版本库
一、版本库目录
创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录(learngit,不要有中文的命名哦!):
twitch@laravel:~$ mkdir learngit
twitch@laravel:~$ cd learngit
twitch@laravel:~/learngit$ pwd
/home/twitch/learngit
二、让你的目录成为Git可以管理的仓库
twitch@laravel:~/learngit$ git init
初始化空的 Git 仓库于 /home/twitch/learngit/.git/
提示这个,就说明我们的git仓库创建好了! 初始化仓库后 在你的版本目录下 会出现一个 .git 的隐藏文件了!
twitch@laravel:~/learngit$ ll
总用量 12
drwxrwxr-x 3 twitch twitch 4096 9月 20 16:57 ./
drwxr-xr-x 29 twitch twitch 4096 9月 20 16:50 ../
drwxrwxr-x 7 twitch twitch 4096 9月 20 16:57 .git/
三、添加文件到版本库
在learngit仓库下,创建一个 readme.md 文件,内容如下:
Git is a version control system.
Git is free software.
然后,使用 git status 命令,来看看现在仓库的状态
twitch@laravel:~/learngit$ git status
位于分支 master
初始提交
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
readme.md
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
提示很清晰的告诉了我们,提交内容为空,但是仓库里存在一个为被跟踪的文件 "readme.md",也提示到 使用 git add 命令 来跟踪!
twitch@laravel:~/learngit$ git add . //. 当前文件目录下
twitch@laravel:~/learngit$ git status
位于分支 master
初始提交
要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: readme.md
查看下当前的仓库状态,可以看到初始提交到一个文件,但是这提交的文件是暂时存放在暂存区里,并没有真正的提交上去.
接下来,真正的把文件提交到仓库
twitch@laravel:~/learngit$ git commit -m "wrote a readme file"
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'twitch@laravel.(none)')
提交的时候,如果出现这些提示,说明你还没自报家门,没有声明你是谁!也人性化的告诉了你使用申明方法去声明你的身份!
twitch@laravel:~/learngit$ git config --global user.email "342766475@qq.com"
twitch@laravel:~/learngit$ git config --global user.name "twitch"
然后再次提交下, 显示下面这样就说明提交成功了!-m 后面输入本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
twitch@laravel:~/learngit$ git commit -m "wrote a readme file"
[master (根提交) 89293b9] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.md
为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,只做一次提交!
twitch@laravel:~/learngit$ touch file1.txt // 使用 touch 命令 创建三个文件!
twitch@laravel:~/learngit$ git add file1.txt
twitch@laravel:~/learngit$ git add file2.txt file3.txt
twitch@laravel:~/learngit$ git commit -m "add three files"
[master fa7ab77] add three files
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
时光机穿梭
对 readme.md 文件进行修改,修改内容如下:
Git is a distributed version control system.
Git is free software.
再输入 git status 查看下仓库的当前的状态,就能知道一个 文件被修改过了!
twitch@laravel:~/learngit$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: readme.md
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
再次 输入 git diff(顾名思义就是查看difference)查看下具体修改的内容:
twitch@laravel:~/learngit$ git diff
diff --git a/readme.md b/readme.md
index 46d49bf..9247db6 100644
--- a/readme.md
+++ b/readme.md
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
对修改过的文件提交和新文件的提交方式都一样,两步:
twitch@laravel:~/learngit$ git add readme.md
twitch@laravel:~/learngit$ git status
位于分支 master
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
修改: readme.md
twitch@laravel:~/learngit$ git commit -m "add distributed!"
[master fa65c67] add distributed!
1 file changed, 1 insertion(+), 1 deletion(-)
版本回退
现在,我对的仓库里有三个版本
版本1:wrote a readme file
Git is a version control system.
Git is free software.
版本2:add three files
版本3: add distributed
Git is a distributed version control system.
Git is free software.
现在,我知道 有三个版本和修改了什么,因为简单量少,要是在一个千行的代码,你还记得修改了什么,所以Git 给我们提供了一个历史记录的功能,使用 git log(显示从最近到最远的提交日志) 查看:
twitch@laravel:~/learngit$ git log
commit fa65c67b5b04c1f8939c4fa570591f531c174222
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 21:08:11 2016 +0800
add distributed!
commit fa7ab774b59f8ab74d690e38f963049eff0296ee
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:31:41 2016 +0800
add three files
commit 89293b9bca9197b3800c3568a800e08175900b96
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:17:54 2016 +0800
wrote a readme file
加上 --pretty=oneline 参数 是对日志显示进行格式化,只显示 提交版本号 和 提交的记录信息
twitch@laravel:~/learngit$ git log --pretty=oneline
fa65c67b5b04c1f8939c4fa570591f531c174222 add distributed!
fa7ab774b59f8ab74d690e38f963049eff0296ee add three files
89293b9bca9197b3800c3568a800e08175900b96 wrote a readme file
正式开启 时光穿梭机
将 readme.md 回退到上一个版本.
首先,Git必须知道当前版本是哪个版本,在Git中,用 HEAD 表示当前版本,也就是最新的提交3628164...882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是 HEAD^,上上一个版本就是 HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成 HEAD~100。
twitch@laravel:~/learngit$ git reset --hard HEAD^
HEAD 现在位于 fa7ab77 add three files
再使用 git log 查看下日志,你会发现最早的版本不见了
commit fa7ab774b59f8ab74d690e38f963049eff0296ee
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:31:41 2016 +0800
add three files
commit 89293b9bca9197b3800c3568a800e08175900b96
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:17:54 2016 +0800
wrote a readme file
想再回去已经回不去了,肿么办?
办法就是:1、你的当前执行命令窗口没关,就可以顺这往上找,只要找到 版本3 的 commit id 号 就回到未来指定的版本了!
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
twitch@laravel:~/learngit$ git reset --hard fa65c67b5b04c1f8939c4fa570591f531c174222
HEAD 现在位于 fa65c67 add distributed!
commit fa65c67b5b04c1f8939c4fa570591f531c174222
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 21:08:11 2016 +0800
add distributed!
commit fa7ab774b59f8ab74d690e38f963049eff0296ee
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:31:41 2016 +0800
add three files
commit 89293b9bca9197b3800c3568a800e08175900b96
Author: twitch <342766475@qq.com>
Date: Tue Sep 20 20:17:54 2016 +0800
wrote a readme file
2、要是回退后关闭了命令窗口,找不到版本 commit id 了,怎么班了!这里你就要使用 git reflog (查看你操作的每一次记录!),然后就可以找到你的commit id 了!
twitch@laravel:~/learngit$ git reflog
fa65c67 HEAD@{0}: reset: moving to fa65c67b5b04c1f8939c4fa570591f531c174222
fa7ab77 HEAD@{1}: reset: moving to HEAD^
fa65c67 HEAD@{2}: commit: add distributed!
fa7ab77 HEAD@{3}: commit: add three files
89293b9 HEAD@{4}: commit (initial): wrote a readme file
工作区和暂存区
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
- 工作区: 电脑里能看到的目录,就是我们创建的 learngit 文件夹.
- 版本库: 工作区里有一个隐藏的 .git 目录,这个就是 git 的版本库.
- Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
twitch@laravel:~/learngit/.git$ ll
总用量 64
drwxrwxr-x 8 twitch twitch 4096 9月 20 22:29 ./
drwxrwxr-x 3 twitch twitch 4096 9月 20 21:56 ../
drwxrwxr-x 2 twitch twitch 4096 9月 20 16:57 branches/
-rw-rw-r-- 1 twitch twitch 17 9月 20 21:08 COMMIT_EDITMSG
-rw-rw-r-- 1 twitch twitch 225 9月 20 20:14 COMMIT_EDITMSG.save
-rw-rw-r-- 1 twitch twitch 1024 9月 20 20:13 .COMMIT_EDITMSG.swp
-rw-rw-r-- 1 twitch twitch 92 9月 20 16:57 config
-rw-rw-r-- 1 twitch twitch 73 9月 20 16:57 description
-rw-rw-r-- 1 twitch twitch 23 9月 20 16:57 HEAD
drwxrwxr-x 2 twitch twitch 4096 9月 20 16:57 hooks/
-rw-rw-r-- 1 twitch twitch 353 9月 20 21:56 index //暂存区
drwxrwxr-x 2 twitch twitch 4096 9月 20 16:57 info/
drwxrwxr-x 3 twitch twitch 4096 9月 20 20:17 logs/
drwxrwxr-x 11 twitch twitch 4096 9月 20 20:57 objects/
-rw-rw-r-- 1 twitch twitch 41 9月 20 21:56 ORIG_HEAD
drwxrwxr-x 4 twitch twitch 4096 9月 20 16:57 refs/
分析下我们把文件添加到仓库中的两步操作:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
举个栗子:
对 readme.md 进行一次修改,再创建一个listen文件!
twitch@laravel:~/learngit$ vim readme.md
twitch@laravel:~/learngit$ vim listen
twitch@laravel:~/learngit$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: readme.md
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
listen
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
可以很清晰的看到,一个修改,一个为被追踪!然后我们分别两次执行下 git add 命令:
twitch@laravel:~/learngit$ git add readme.md
twitch@laravel:~/learngit$ git add listen
twitch@laravel:~/learngit$ git status
位于分支 master
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
新文件: listen
修改: readme.md
git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。这样就清晰的明白了暂存区了吧!
twitch@laravel:~/learngit$ git commit -m "understand how stage works"
[master f56fcb6] understand how stage works
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 listen
twitch@laravel:~/learngit$ git status
位于分支 master
无文件要提交,干净的工作区
管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
git commit 只会对 暂存区里的文件进行提交!
撤销修改
在修改过某个文件后,你使用 git status 命令可以清晰看到git 的给你的提示信息,其中就有一个;
使用 "git checkout -- <文件>..." 丢弃工作区的改动
twitch@laravel:~/learngit$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: listen
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
一般有两种情况出现:
- 1、文件修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
- 2、文件修改后已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令!
另一个撤销的方法: git reset HEAD file(文件名)可以把暂存区的修改撤销掉(unstage),重新放回工作区
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
删除文件
在Git里,删除一个文件也是一个修改操作!
工作区删除文件
twitch@laravel:~/learngit$ rm test.txt
twitch@laravel:~/learngit$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add/rm <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
删除: test.txt
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
很清晰的提示给你有两个选择,一是从版本库删除,使用 "git rm "并且提交,二是撤销删除,使用 " git checkput -- filename"!
远程仓库
以 Github 作为 远程仓库
第一步、创建SSH Key
本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以需要带着钥匙来访问!首先看下你用户主目录下有没有一个隐藏的".ssh"目录,该目录下有没有id_rsa和id_rsa.pub两个文件;有的话就说明你已经拥有了钥匙 和 锁!
创建 SSH Key:
twitch@laravel:~/learngit$ ssh-keygen -t rsa -C "342766475@qq.com"
Generating public/private rsa key pair. // 生成公共/私有密钥对
Enter file in which to save the key (/home/twitch/.ssh/id_rsa): // 输入保存键的文件 文件名 默认就行!
Enter passphrase (empty for no passphrase): //输入密码(无密码为空): (默认就行)
Enter same passphrase again:
Your identification has been saved in /home/twitch/.ssh/id_rsa.
Your public key has been saved in /home/twitch/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:tRogpxgwX0eYczLbjAT7jGsqwwdxvaeY8ZiP3n+Wb3I 342766475@qq.com
The key's randomart image is:
+---[RSA 2048]----+
|o ...+o |
| + oB.. |
| +.oXo . |
| . Bo=o. . . |
| = + . S . |
| . o . . o |
|. + B o . . |
|o+ *oo = E |
|o.oo.o..o =. |
+----[SHA256]-----+
可以看到".ssh目录下生成了两个文件:" id_rsa和id_rsa.pub,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
第二步、登陆GitHub,打开“settings”,“SSH and GPG Keys”页面
然后,点“New SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容就ok了,确认添加就好了!
第三步、在GitHub添加远程库
本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,GitHub 点击右上角"+"的符号,找到 new repository!创建好后这个仓库,learngit仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
远程仓库与本地仓库关联 使用命令"git remote -h"查看相关命令帮助!
twitch@laravel:~/learngit$ git remote add origin git@github.com:Twitchboy/leanergit.git // 通过SSH通道关联
twitch@laravel:~/learngit$ clear
twitch@laravel:~/learngit$ git push -u origin master // 推送把本地库的所有内容推送到远程库上
The authenticity of host 'github.com (192.30.253.112)' 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,192.30.253.112' (RSA) to the list of known hosts.
对象计数中: 15, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (12/12), 完成.
写入对象中: 100% (15/15), 1.22 KiB | 0 bytes/s, 完成.
Total 15 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), done.
To git@github.com:Twitchboy/leanergit.git
* [new branch] master -> master
分支 master 设置为跟踪来自 origin 的远程分支 master。
由于远程库是空的,我们第一次推送master分支时,加上了 -u 参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,远程仓库就和本地仓库内容一致了!从现在起,只要本地作了提交,就可以通过命令:
twitch@laravel:~/learngit$ git push origin master
SSH警告!
当你第一次使用Git的clone或者push命令连接GitHub时,会得到一个警告:
twitch@laravel:~/learngit$ git push -u origin master
The authenticity of host 'github.com (192.30.253.112)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8. //如果你实在担心有人冒充GitHub服务器,输入yes前可以对照GitHub的RSA Key的指纹信息是否与SSH连接给出的一致。
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,192.30.253.112' (RSA) to the list of known hosts. //Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:只会出现一次!
这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes回车即可。
从远程仓库克隆
第一步、先创建远程仓库
勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。创建完毕后,可以看到README.md文件.
第二步、克隆到本地仓库
twitch@laravel:~$ git clone git@github.com:Twitchboy/gitnote.git
正克隆到 'gitnote'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (3/3), 完成.
检查连接... 完成。
twitch@laravel:~$ cd gitnote
twitch@laravel:~/gitnote$ ls
README.md
你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
分支管理
分支简介
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
优点: Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
创建与合并分支
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
HEAD(当前版本) -> master -> 最新的提交
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在 dev 上:
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
合并
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
实操下:
创建分支
twitch@laravel:~/learngit$ git branch // 查看当前分支
* master
twitch@laravel:~/learngit$ git checkout -b dev // 创建了一个分支,并且切换到 dev分支上
M listen
D test.txt
切换到一个新分支 'dev'
twitch@laravel:~/learngit$ git branch // 当前分支就在 dev 上了
* dev
master
在dev分支上修改readme.md 文件,并合并分支
twitch@laravel:~/learngit$ vim readme.md
twitch@laravel:~/learngit$ git add readme.md
twitch@laravel:~/learngit$ git commit -m "Branch test"
[dev 347f60e] Branch test
1 file changed, 2 insertions(+)
// 分支提交完,我们来看看 主分支有变化没!
twitch@laravel:~/learngit$ git checkout master
切换到分支 'master'
您的分支与上游分支 'origin/master' 一致
// 切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
分支合并
twitch@laravel:~/learngit$ git merge dev
更新 1af2398..347f60e
Fast-forward
readme.md | 2 ++
1 file changed, 2 insertions(+)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意 到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
删除dev分支
twitch@laravel:~/learngit$ git branch -d dev
已删除分支 dev(曾为 347f60e)。
twitch@laravel:~/learngit$ git branch
* master
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
解决冲突
我们来创建一个冲突
新分支 featurel 下,修改 file1.txt 文件,然后切回 master 分支上,也进行修改同一个文件 file1.txt 同一行,然后提交!最后在合并看看!
twitch@laravel:~/learngit$ git checkout -b featurel
M listen
D test.txt
切换到一个新分支 'featurel'
twitch@laravel:~/learngit$ git branch
* featurel
master
twitch@laravel:~/learngit$ vim file1.txt
twitch@laravel:~/learngit$ git add file1.txt
twitch@laravel:~/learngit$ git commit -m "AND simple"
[featurel 7887db5] AND simple
1 file changed, 2 insertions(+)
twitch@laravel:~/learngit$ git checkout master
M listen
D test.txt
切换到分支 'master'
您的分支领先 'origin/master' 共 1 个提交。
(使用 "git push" 来发布您的本地提交)
twitch@laravel:~/learngit$ vim file1.txt
twitch@laravel:~/learngit$ git add file1.txt
twitch@laravel:~/learngit$ git commit -m "& simple"
[master 88ea4c7] & simple
1 file changed, 1 insertion(+)
twitch@laravel:~/learngit$ git merge featurel
自动合并 file1.txt
冲突(内容):合并冲突于 file1.txt
自动合并失败,修正冲突然后提交修正的结果。
果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:
twitch@laravel:~/learngit$ git status
位于分支 master
您的分支领先 'origin/master' 共 2 个提交。
(使用 "git push" 来发布您的本地提交)
您有尚未合并的路径。
(解决冲突并运行 "git commit")
未合并的路径:
(使用 "git add <文件>..." 标记解决方案)
双方修改: file1.txt
直接查看file1.txt的内容:
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
1、
Creating a new branch is quick AND simple.
>>>>>>> featurel
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,手动修改成 我们想要的!再保存下!
twitch@laravel:~/learngit$ git log --graph --pretty=oneline --abbrev-commit
* 488db75 conflict fixed
|\
| * 7887db5 AND simple
* | 88ea4c7 & simple
|/
* 347f60e Branch test
* 1af2398 add test.txt
* f56fcb6 understand how stage works
* fa65c67 add distributed!
* fa7ab77 add three files
* 89293b9 wrote a readme file
删除分支
twitch@laravel:~/learngit$ git branch -d featurel
已删除分支 featurel(曾为 7887db5)。
分支管理策略
合并分支有两种模式:
- 1、fast forward 模式,合并后,删除分支,分支信息将会丢掉。
- 2、普通模式 合并时添加 参数: "--no-ff" ,合并(merge)时生成一个新的commit,这样分支历史上就可以看到分支信息
合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
实操:
twitch@laravel:~/learngit$ git checkout -d dev
twitch@laravel:~/learngit$ vim file2.txt
twitch@laravel:~/learngit$ git add file2.txt
twitch@laravel:~/learngit$ git commit -m 'add file2 1'
twitch@laravel:~/learngit$ git checkout master
twitch@laravel:~/learngit$ git vim file2.txt
twitch@laravel:~/learngit$ git add file2.txt
twitch@laravel:~/learngit$ git commit -m "add file2 2"
twitch@laravel:~/learngit$ git merge --no-ff -m "merge with --no-ff" dev
有一个冲突!
twitch@laravel:~/learngit$ git log --graph --pretty=oneline --abbrev-commit
* e3fb75f merge --no-ff
|\
| * 5125905 add file2.txt 1
* | 3942b60 add file2
* | fcd3074 add file2.txt
|/
* 439b640 change2
* 23a53ed change
* 488db75 conflict fixed
|\
| * 7887db5 AND simple
* | 88ea4c7 & simple
|/
* 347f60e Branch test
* 1af2398 add test.txt
* f56fcb6 understand how stage works
* fa65c67 add distributed!
* fa7ab77 add three files
* 89293b9 wrote a readme file
可以看到,不使用Fast forward模式,merge后就像这样:
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
多人协作
要查看远程库的信息,用git remote: (默认:origin)
twitch@laravel:~/learngit$ git remote
origin
用git remote -v显示更详细的信息:
twitch@laravel:~/learngit$ git remote -v
origin git@github.com:Twitchboy/leanergit.git (fetch)
origin git@github.com:Twitchboy/leanergit.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
twitch@laravel:~/learngit$ git push origin master
Everything up-to-date
已是最新更新的了
如果要推送其他分支,比如dev,就改成:
twitch@laravel:~/learngit$ git push origin dev
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。
现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
twitch@laravel:~/test$ git clone git@github.com:Twitchboy/leanergit.git
正克隆到 'leanergit'...
remote: Counting objects: 48, done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 48 (delta 15), reused 47 (delta 14), pack-reused 0
接收对象中: 100% (48/48), 6.69 KiB | 0 bytes/s, 完成.
处理 delta 中: 100% (15/15), 完成.
检查连接... 完成。
twitch@laravel:~/test$ ls
leanergit
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:
twitch@laravel:~/test/leanergit$ git branch
* master
现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:
twitch@laravel:~/test/leanergit$ git checkout -b dev origin/dev
分支 dev 设置为跟踪来自 origin 的远程分支 dev。
切换到一个新分支 'dev'
现在,就可以在dev上继续修改,然后,时不时地把dev分支push到远程
twitch@laravel:~/test/leanergit$ git add file4.txt
twitch@laravel:~/test/leanergit$ git status
位于分支 dev
您的分支与上游分支 'origin/dev' 一致。
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
修改: file4.txt
twitch@laravel:~/test/leanergit$ git push
warning: push.default 尚未设置,它的默认值在 Git 2.0 已从 'matching'
变更为 'simple'。若要不再显示本信息并保持传统习惯,进行如下设置:
git config --global push.default matching
若要不再显示本信息并从现在开始采用新的使用习惯,设置:
git config --global push.default simple
当 push.default 设置为 'matching' 后,git 将推送和远程同名的所有
本地分支。
从 Git 2.0 开始,Git 默认采用更为保守的 'simple' 模式,只推送当前
分支到远程关联的同名分支,即 'git push' 推送当前分支。
参见 'git help config' 并查找 'push.default' 以获取更多信息。
('simple' 模式由 Git 1.7.11 版本引入。如果您有时要使用老版本的 Git,
为保持兼容,请用 'current' 代替 'simple')
对象计数中: 3, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (2/2), 完成.
写入对象中: 100% (3/3), 289 bytes | 0 bytes/s, 完成.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local objects.
To git@github.com:Twitchboy/leanergit.git
74de2bf..7e01dea dev -> dev
这里推送是往github上推送!要是你的也同做了这个事件,你的推送就会失败!
twitch@laravel:~/learngit$ git push origin dev
To git@github.com:Twitchboy/leanergit.git
! [rejected] dev -> dev (fetch first)
error: 无法推送一些引用到 'git@github.com:Twitchboy/leanergit.git'
提示:更新被拒绝,因为远程仓库包含您本地尚不存在的提交。这通常是因为另外
提示:一个仓库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更
提示:(如 'git pull ...')。
提示:详见 'git push --help' 中的 'Note about fast-forwards' 小节。
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
twitch@laravel:~/learngit$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
展开对象中: 100% (3/3), 完成.
来自 github.com:Twitchboy/leanergit
74de2bf..7e01dea 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 dev origin/<branch>
git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接
twitch@laravel:~/learngit$ git branch --set-upstream-to origin/dev
分支 dev 设置为跟踪来自 origin 的远程分支 dev。
再进行 git pull
twitch@laravel:~/learngit$ git pull
自动合并 file4.txt
冲突(内容):合并冲突于 file4.txt
自动合并失败,修正冲突然后提交修正的结果。
这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:
twitch@laravel:~/learngit$ git add file4.txt
twitch@laravel:~/learngit$ git commit -m "merge & fix file4.txt"
[dev 452314c] merge & fix file4.txt
twitch@laravel:~/learngit$ clear
twitch@laravel:~/learngit$ git push
warning: push.default 尚未设置,它的默认值在 Git 2.0 已从 'matching'
变更为 'simple'。若要不再显示本信息并保持传统习惯,进行如下设置:
git config --global push.default matching
若要不再显示本信息并从现在开始采用新的使用习惯,设置:
git config --global push.default simple
当 push.default 设置为 'matching' 后,git 将推送和远程同名的所有
本地分支。
从 Git 2.0 开始,Git 默认采用更为保守的 'simple' 模式,只推送当前
分支到远程关联的同名分支,即 'git push' 推送当前分支。
参见 'git help config' 并查找 'push.default' 以获取更多信息。
('simple' 模式由 Git 1.7.11 版本引入。如果您有时要使用老版本的 Git,
为保持兼容,请用 'current' 代替 'simple')
对象计数中: 5, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (4/4), 完成.
写入对象中: 100% (5/5), 524 bytes | 0 bytes/s, 完成.
Total 5 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local objects.
To git@github.com:Twitchboy/leanergit.git
7e01dea..452314c dev -> dev
因此,多人协作的工作模式通常是这样:
首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to branch-name origin/branch-name。