Git 学习笔记

----------------- 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上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

最简单的,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后就像这样:

merge --no-ff

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,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。

推荐阅读更多精彩内容

  • 学习资料pro git[https://git-scm.com/book/zh/v2]git - 简明指南[htt...
    合肥黑阅读 13,858评论 1 20
  • Git教程 一、Git简介 1.1. Git的诞生1.2.集中式的vs分布式 二、安装Git 三、创建版本库 四、...
    曹渊说创业阅读 534评论 0 2
  • 1. 安装 Github 查看是否安装git: $ git config --global user.name "...
    Albert_Sun阅读 11,427评论 9 162
  • git学习笔记 声明 本文是本人学习Git过程中所做的笔记,以便日后查阅,文中多有错漏之处,不建议用作学习材料,文...
    sayonara_yoyo阅读 314评论 0 1
  • 那时候和现在不一样,未来也和现在不一样
    Wind季阅读 90评论 0 0
  • 从小就被人说没有审美。 好嘛那我培养不就是了!! 1、横、纵位构图 2、三角形构图 3、对称形构图 4.S形和斜线...
    老谈_TX阅读 166评论 2 2
  • “你一定要做到,可以取代任何人,然后再考虑到,任何人都不能取代你。” 这是马伊琍主演的《我的前半生》里,贺涵对罗子...
    白驹过隙2016阅读 196评论 0 0
  • 月饼节对于单身汪来说,绝对是个宅窝里刷剧的好时机,好吧,这也是本人至今没把自己推销出去的一大原因。鉴于本人这个异类...
    积木十年阅读 225评论 1 4