Git (一)

2017-05-12  本文已影响137人  carolwhite

以下笔记主要参考gitgot,大致了解git使用和原理。

第一部分我们从个人的视角去研究如何用好Git,并且揭示Git的原理和奥秘,主要有以下内容。

一.创建git本地库

$ mkdir learngit  //创建空目录
$ cd learngit
$ pwd
/Users/michael/learngit
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
$ git init demo   //创建目录并进入当前目录

此时该目录下会多一个.git的隐藏目录,保存了git库信息,具体内容稍后介绍.

Screen Shot 2017-05-12 at 5.11.53 PM.png

二.工作区和版本库

Screen Shot 2017-05-13 at 10.45.27 AM.png
工作区(Working Directory)就是你在电脑里能看到的目录。
版本库(Repository)工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
版本库里面最主要的内容为stage(又叫index)暂存区和mater分枝,指向master的指针HEAD.(在创建Git版本库时,Git自动为我们创建了唯一一个master分支)
第一步是用git add --> 把文件添加进去,实际上就是把文件修改添加到暂存区
第二步是用git commit -->提交更改,实际上就是把暂存区的所有内容提交到当前分支。
Screen Shot 2017-05-12 at 5.51.18 PM.png
1 .查看状态
$ git status  

我们先参照上面的实例,在read.txt文件中新增一行,执行该命令.

baihangdeMacBook-Pro:demo baihang$ vi read.txt
baihangdeMacBook-Pro:demo baihang$ 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:   welcome.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

也就是说要对修改的:read.txt文件执行:command:git add命令,将修改的文件添加到“提交任务”中,然后才能提交.
或者执行git checkout --read.txt删除工作区改动.

好了,现在就将修改的文件“添加”到提交任务中,再执行stauts命令。

baihangdeMacBook-Pro:demo baihang$ git add read.txt
baihangdeMacBook-Pro:demo baihang$ git status 
On branch master
 Changes to be committed:
    (use "git reset HEAD <file>..." to unstage)
 
    modified:   read.txt
 
baihangdeMacBook-Pro:demo baihang$ 

此时说明可以被提交。你也可以试试用reset HEAD read.txt撤销本次暂存区改动。接下来,我们提交暂存区内容.

baihangdeMacBook-Pro:demo baihang$ git commit -m "add first"
[master 5c80ce0] add first
 1 file changed, 1 insertion(+)
baihangdeMacBook-Pro:demo baihang$ git status
On branch master
nothing to commit, working tree clean

git commit -m "add first"是提交命令,-m "add first"是提交说明。此时查看状态说明没东西可以提交,工作区也是干净的。

当我们使用git status命令时,显示内容可能过长,我们可以使用git status -s来简化输出内容。

baihangdeMacBook-Pro:demo baihang$ vi read.txt
baihangdeMacBook-Pro:demo baihang$ git st -s
 M read.txt
baihangdeMacBook-Pro:demo baihang$ git add read.txt
baihangdeMacBook-Pro:demo baihang$ git st -s
M  read.txt
2.diff 比较差异
Screen Shot 2017-05-13 at 10.54.04 AM.png

这时read.txt有三个版本,一个在工作区,一个在等待提交的暂存区,还有一个是版本库中最新版本的,我们可以用diff命令查看其区别.

$ git diff   //工作区和提交任务(提交暂存区,stage)中相比的差异。
$ git diff readme.txt  //readme.txt中工作区和暂存区差异
$ git diff HEAD //工作区和HEAD(当前工作分支)相比
$ git diff --cached //提交暂存区(提交任务,stage)和版本库中文件

我们可以用以下命令查看暂存区和HEAD(版本库中当前提交)指向的目录树.

baihangdeMacBook-Pro:demo baihang$ git ls-files -s //暂存区
100644 9c59e24b8393179a5d712de4f990178df5734d99 0   read.txt
baihangdeMacBook-Pro:demo baihang$ git ls-tree  HEAD //HEAD指向的目录树
100644 blob 9c59e24b8393179a5d712de4f990178df5734d99    read.txt```

#三.HEAD和master分支
#####1.git对象
首先我们对git对象进行一下研究。

baihangdeMacBook-Pro:demo baihang$ git log -1 --pretty=raw
commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
parent a0c641e92b10d8bcca1ed1bf84ca80340fdefee6
author jkbaihang 909920027@qq.com 1494642732 +0800
committer jkbaihang 909920027@qq.com 1494642732 +0800

add first
git log表示显示历史提交版本,-1表示显示一个,--pretty=raw表示显示详细日志输出。
一个提交中居然包含了三个SHA1哈希值(一种表示数据的方法,当我们使用时可以只用前几位)表示的对象ID。
 - commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
这是本次提交的唯一标识。
 - tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
这是本次提交所对应的目录树。
 - parent a0c641e92b10d8bcca1ed1bf84ca80340fdefee6这是本地提交的父提交(上一次提交)。

这时候我们需要使用一个工具如下.

git cat-file -t,查看Git对象的类型,主要的git对象包括tree,commit,parent,和blob等。
git cat-file -p,查看Git对象的内容

查看类型

$ git cat-file -t e695606
commit
$ git cat-file -t f58d
tree
$ git cat-file -t a0c6
commit

查看内容

$ git cat-file -p e695606
tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
parent a0c641e92b10d8bcca1ed1bf84ca80340fdefee6
author jkbaihang 909920027@qq.com 1494642732 +0800
committer jkbaihang 909920027@qq.com 1494642732 +0800

add first

$ git cat-file -p f58da9a
100644 blob fd3c069c1de4f4bc9b15940f490aeb48852f3c42 read.txt


$ git cat-file -p a0c641e //此时没parent,因为是初始提交
tree 190d840dd3d8fa319bdec6b8112b0957be7ee769
author jkbaihang 909920027@qq.com 1494642732 +0800
committer jkbaihang 909920027@qq.com 1494642732 +0800

add read


上述tree中存在一个blog对象,该对象保存着read.txt的内容

$ git cat-file -t fd3c069c1de4f4bc9b15940f490aeb48852f3c42
blob
$ git cat-file -p fd3c069c1de4f4bc9b15940f490aeb48852f3c42
first
second

这些对象保存在哪里的呢?在objects目录下。

baihangdeMacBook-Pro:demo baihang$ cd .git
baihangdeMacBook-Pro:.git baihang$ ls
COMMIT_EDITMSG branches hooks logs
HEAD config index objects
ORIG_HEAD description info refs
baihangdeMacBook-Pro:.git baihang$ cd objects

![Screen Shot 2017-05-13 at 11.22.43 AM.png](https://img.haomeiwen.com/i1485048/f9531778587f3069.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#####2.HEAD和mater分支

$ git branch

`git branch`查看所有分支,*代表当前分支。

$ git log -1 HEAD //查看HEAD指针指向的提交
commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
Author: jkbaihang 909920027@qq.com
Date: Sat May 13 10:32:12 2017 +0800

add first

$ git log -1 master //查看master分支对应的提交
commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
Date: Sat May 13 10:32:12 2017 +0800

add first

$ git log -1 refs/heads/master /查看master分支对应提交,可以省略前面部分
commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
Date: Sat May 13 10:32:12 2017 +0800

add first
说明head和master对应的提交都是一样的。接下来,我们查看一下他们所对应的文件的内容.

$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
e695606fc5e31b2ff9038a48a3d363f4c21a3d86
$ git cat-file -t e695606
commit
$ git cat-file -p e695606
tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
commit e695606fc5e31b2ff9038a48a3d363f4c21a3d86
Author: jkbaihang 909920027@qq.com
Date: Sat May 13 10:32:12 2017 +0800

add first
根据结果可以得出结论-----master代表分支master中最新的提交,而HEAD指向的是master引用。

![Screen Shot 2017-05-13 at 6.00.16 PM.png](https://img.haomeiwen.com/i1485048/8763a33650dcede0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


目录:file:`.git/refs`是保存引用的命名空间,其中:file:`.git/refs/heads`目录下的引用又称为分支。对于分支既可以使用正规的长格式的表示法,如:file:`refs/heads/master`,也可以去掉前面的两级目录用master来表示。Git 有一个底层命令:command:`git rev-parse`可以用于显示引用对应的提交ID。

$ git rev-parse master
e695606fc5e31b2ff9038a48a3d363f4c21a3d86
$ git rev-parse refs/heads/master
e695606fc5e31b2ff9038a48a3d363f4c21a3d86
$ git rev-parse HEAD
e695606fc5e31b2ff9038a48a3d363f4c21a3d86

#四.Git重置 (reset命令)

$ git log --oneline //--oneline代表简化一行内容
e695606 add first
a0c641e add read

上面是我们的提交记录,这个时候我们再进行一次修改和提交。

$ vi read.txt
$ git add read.txt
$ git commit -m "add second"
[master 562b0df] add second
1 file changed, 1 insertion(+)
$ git log --oneline
4902dc3 add second
e695606 add first
a0c641e add read
$ cat .git/refs/heads/master
4902dc375672fbf52a226e0354100b75d4fe31e3

可以看到,master引用变成了最新的提交,如果这个时候我们想取消这一次提交,我们就可以使用`reset`命令。

这里我们学习一些方法,可以方便的访问Git库中的对象。
 - 使用master代表分支master中最新的提交,使用全称refs/heads/master亦可。

 - 使用HEAD代表版本库中最近的一次提交。

 - 符号` ^可以用于指代父提交。例如:

  - HEAD^代表版本库中上一次提交,即最近一次提交的父提交。
  - HEAD^^则代表HEAD^的父提交。

 - 对于一个提交有多个父提交,可以在符号^后面用数字表示是第几个父提交。例如:

  - a573106^2含义是提交a573106的多个父提交中的第二个父提交。
  - HEAD^1相当于HEAD^含义是HEAD多个父提交中的第一个。
  - HEAD^^2含义是HEAD^(HEAD父提交)的多个父提交中的第二个。

####reset具体使用

$ git reset HEAD^ //或者git reset e695606,可以重置到前一次提交,也可以直接使用提交ID重置到任何一次提交。
Unstaged changes after reset:
M read.txt
$ git log --oneline
e695606 add first
a0c641e add read

可以看到这个时候提交记录second取消了,最新提交变成first,然后工作区和暂存区内容有差异,为啥会有差异呢?
reset有两种用法:
 - 一种是含有路径(不会重置引用,更不会改变工作区,而是用指定提交状态(<commit>)下的文件(<paths>)替换掉暂存区中的文件)

$ git vi read.txt //文本中增加一行
$ git add read.txt //此时工作区和暂存区都增加了一行
$ git reset read.txt //将此时提交状态下文件替换暂存区

 - 一种是不含路径(重置引用,根据不同的选项,可以对暂存区或者工作区进行重置)。下面我们主要说第二种。

![Screen Shot 2017-05-13 at 8.34.31 PM.png](https://img.haomeiwen.com/i1485048/feced4ee1c0dfb4f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

$ git reset //git reset HEAD也是同作用

仅用HEAD指向的目录树重置暂存区,工作区不会受到影响,相当于将之前用`git add`命令更新到暂存区的内容撤出暂存区。引用也未改变,因为引用重置到HEAD相当于没有重置。

$ git reset HEAD^

工作区不改变,但是暂存区会回退到上一次提交之前,引用也会回退一次。

 reset命令有三个参数类型,--soft,--mixed(一般默认类型),--hard类型。
   - 
  - 使用参数--hard,如::command:`git reset --hard <commit>`。
会执行上图中的1、2、3全部的三个动作。即:
1.替换引用的指向。引用指向新的提交ID。
2.替换暂存区。替换后,暂存区的内容和引用指向的目录树一致。
3.替换工作区。替换后,工作区的内容变得和暂存区一致,也和HEAD所指向的目录树内容相同。
- 使用参数--soft,如::command:`git reset --soft <commit>`。
会执行上图中的操作1。即只更改引用的指向,不改变暂存区和工作区。

 - 使用参数--mixed或者不使用参数(缺省即为--mixed),如:command:`git reset <commit>`。
会执行上图中的操作1和操作2。即更改引用的指向以及重置暂存区,但是不改变工作区。

命令 | 工作区 | 暂存区 | Master
----|------
当前版本 |B|B |B(A为上一提交)
--soft | B| B|A
--mixed  | B| A|A
--hard|  A | A|A

####用reflog挽救错误的重置
1.可以查看日志来查询master历史

$ tail -5 .git/logs/refs/heads/master

2.可以使用reflog查询到历史操作记录  

$ git reflog //查看HEAD
$ git reflog master //查看master

#五.Git检出 (checkout命令)
#### 1.Git检出
上一节reset作用主要是修改master,而这节的checkout则是修改HEAD,HEAD一般是默认指向refs/heads/master。接下来我们checkout命令。

$ git log --oneline
dd35068 three
b4f6dd1 second
519bbb0 first
b4f87be init
$ git checkout 519bbb0
Note: checking out '519bbb0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 519bbb0... first

$ git checkout 519bbb0
注意: 正检出 '519bbb0'.

您现在处于 '分离头指针' 状态。您可以检查、测试和提交,而不影响任何分支。
通过执行另外的一个 checkout 检出指令会丢弃在此状态下的修改和提交。

如果想保留在此状态下的修改和提交,使用 -b 参数调用 checkout 检出指令以
创建新的跟踪分支。如:

git checkout -b new_branch_name

头指针现在指向 519bbb0... 提交说明为:first

什么叫做“分离头指针”状态?查看一下此时HEAD的内容就明白了。

$ cat .git/HEAD
519bbb07a0aaef319088ca21ebbe91301b5c583b
$ git bra

我们再查看一下最新日志

$ git reflog -1 //注意该日志是HEAD,而不是master
519bbb0 HEAD@{0}: checkout: moving from master to 519bbb0

很明显,HEAD由指向master变为指向first那个提交Id。我们再看下master现在的id是什么。

$ git rev-parse HEAD master
519bbb07a0aaef319088ca21ebbe91301b5c583b
dd35068fac7a74780d85194edfbc2adf1d815c54

综上 
 -  HEAD指向了一个新的ID,而master并无变动。

我们在该状态下,新建一个文件,并提交。

$ vi head.txt
$ git add head.txt
$ git commit -m "init head.txt"
[detached HEAD f573e31] init head.txt
1 file changed, 2 insertions(+)

接下来查看一下,日志,可以看到HEAD指向了新的提交.

git log --oneline
f573e31 init head.txt
519bbb0 first
b4f87be init
baihangdeMacBook-Pro:baihang baihang$ cat .git/HEAD
f573e314bd5d1640f7853b26010887e4b6892e48
baihangdeMacBook-Pro:baihang baihang$

我们切换回master分支,并查看日志。

$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

f573e31 init head.txt

If you want to keep it by creating a new branch, this may be a good time
to do so with:

git branch <new-branch-name> f573e31

Switched to branch 'master'
baihangdeMacBook-Pro:baihang baihang$ git log --oneline
dd35068 three
b4f6dd1 second
519bbb0 first
b4f87be init
baihangdeMacBook-Pro:baihang baihang$

很明显,之前HEAD提交记录不在了。我们在看下上面这段话,他的大致意思是,该提交没连接任何分支,如果你想保存它,那你必须通过命令新建一个分支,因为该记录随时会被删除 。(在后面新建分支中会讲到)

#####2.挽救分离头指针。
 在“分离头指针”模式下进行的测试提交除了使用提交ID(f573e31)访问之外,不能通过master分支或其他引用访问到。如果这个提交是master分支所需要的,那么该如何处理呢?
 - 我们可以通过**合并**分支来执行。

 我们先确保返回master分支。

git bra -v

 然后执行合并操作

$ git merge f573e31
Merge made by the 'recursive' strategy.
head.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 head.txt

 工作区此时多了一个head.txt文件

$ ls
head.txt read.txt

此时我们再查看一下日志,使用--graph 可以看到链接状态。

$ git log --graph --oneline

再查看一下最新提交的内容

$ git cat-file -p HEAD
tree 749f7a050f7e5dbfe4afc559ed3308172ea96921
parent dd35068fac7a74780d85194edfbc2adf1d815c54
parent f573e314bd5d1640f7853b26010887e4b6892e48
author jkbaihang 909920027@qq.com 1494742070 +0800
committer jkbaihang 909920027@qq.com 1494742070 +0800

Merge commit 'f573e31'

-  该提交中有两个父提交,这就是合并的奥秘。

#####3.checkout命令
![](https://img.haomeiwen.com/i1485048/67d16272a20f32b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 命令::command:`git checkout branch`
检出branch分支。要完成如图的三个步骤,更新HEAD以指向branch分支,以branch指向的树更新暂存区和工作区。

 - 命令::command:`git checkout -- filename`
用暂存区中:file:`filename`文件来覆盖工作区中的:file:`filename`文件。相当于取消自上次执行:command:`git add filename`以来(如果执行过)本地的修改。(这个命令很危险,因为对于本地的修改会悄无声息的覆盖,毫不留情。)

 - 命令::command:`git checkout -- . 或写做 git checkout .`
注意::command:`git checkout`命令后的参数为一个点(“.”)。这条命令最危险!会取消所有本地的修改(相对于暂存区)。相当于将暂存区的所有文件直接覆盖本地文件,不给用户任何确认的机会!

 - 命令::command:`git checkout branch -- filename`
维持HEAD的指向不变。将branch所指向的提交中的:file:`filename`替换暂存区和工作区中相应的文件。注意会将暂存区和工作区中的:file:`filename`文件直接覆盖。

#六.Git stash
有些时候你手上的工作正做到一半,但是领导需要你马上做另一项工作,我们就可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作。

我们新建一个git,然后创建一个read.txt。

$ echo init > read.txt
$ git add read.txt
git ci -m "init"
[master (root-commit) 9d885c5] init
1 file changed, 1 insertion(+)
create mode 100644 read.txt


接下来,我们新增一行,如果这个时候你需要暂停当前工作,你就可以使用stash命令保存。

$ echo first >> read.txt
$ git add read.txt
$ git stash save "firstSave"
Saved working directory and index state On master: firstSave
HEAD is now at 9d885c5 init


我们查看一下状态,发现工作区和暂存区都恢复正常状态了。

$ git status
On branch master
nothing to commit, working tree clean

我们可以用stash list查看一下当前stash进度列表

$ git stash list
stash@{0}: On master: firstSave

###stash是怎么保存工作区和暂存区数据的呢?
我们先看看git文件里面refs/stash的数据。

$ cat .git/refs/stash
0f27315d1a24e01bc701cab00b71d8b6ec5bb887
$ git cat-file -p 0f27315d
tree 154a4f6d4a332c54dcf9ef2867ecd45714222b84
parent f2e10ed446f10ebcc6f7173aa0b4695057f445b7
parent ea4f40fd65186f5f53aa9f35a9af79be1801df90
author jkbaihang 909920027@qq.com 1494827393 +0800
committer jkbaihang 909920027@qq.com 1494827393 +0800

我们再看看提交历史。

$ git log --graph --pretty=raw 0f27315d

| |
| * commit ea4f40fd65186f5f53aa9f35a9af79be1801df90
|/ tree 3c191023105e79c4844cb22d320346772ecc2b65
| parent 9d885c5c380150fc1cad8c944dbcbdd582f92c9a
| author jkbaihang 909920027@qq.com 1494827393 +0800
| committer jkbaihang 909920027@qq.com 1494827393 +0800
|
| index on master: 9d885c5c init
|

可以看到在提交关系图可以看到进度保存的最新提交是一个合并提交。最新的提交代表了工作区进度。而最新提交的第二个父提交(上图中显示为第二个提交)有index on master字样,说明这个提交代表着暂存区的进度。
#####恢复操作
如果我们想要恢复当前保存的内容,可以执行下面pop操作

$ git stash pop stash@{0} //不写stash@{0}编号的话,默认为最新一次
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:   read.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (acd7dc99fed73b95b802da0f7e555867ce59a798)
$ git stash list //查询一下stash列表可以看到无stash

根据提示,我们可以看到恢复了工作区,同时stash@{0}被删除。(如果我们想同时恢复暂存区,应该加--index参数,git stash pop  --index stash@{0})

如果我们不想删除stash存储的进度的话,我们可以使用apply命令,之后也可以使用drop命令删除存储的进度。

$ git stash apply
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:   read.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git stash drop stash@{0}
Dropped stash@{0} (9395ebe4c5d60bfc122202416a078a602052da82)



###stash命令列表
 - 命令::command:`git stash`

 保存当前工作进度。会分别对暂存区和工作区的状态进行保存。
 - 命令::command:`git stash list`

显示进度列表。此命令显然暗示了:command:`git stash`可以多次保存工作进度,并且在恢复的时候进行选择。

 - 命令::command:`git stash pop [--index] [<stash>]`

如果不使用任何参数,会恢复最新保存的工作进度,并将恢复的工作进度从存储的工作进度列表中清除。

如果提供<stash>参数(来自于:command:`git stash list`显示的列表),则从该<stash>中恢复。恢复完毕也将从进度列表中删除<stash>。

选项--index除了恢复工作区的文件外,还尝试恢复暂存区。这也就是为什么在本章一开始恢复进度的时候显示的状态和保存进度前略有不同。

 - 命令::command:`git stash apply [--index] [<stash>]`

除了不删除恢复的进度之外,其余和:command:`git stash pop`命令一样。

 - 命令::command:`git stash drop [<stash>]`

删除一个存储的进度。缺省删除最新的进度。

 - 命令::command:`git stash clear`

删除所有存储的进度。

- 命令::command:`git stash branch <branchname> <stash>`

基于进度创建分支。


#七.Git 删除文件

$ ls
read.txt
$ echo "hello" > hack.txt
$ git add hack.txt
$ git ci -m "add hack.txt"
[master 13dc137] add hack.txt
1 file changed, 1 insertion(+), 1 deletion(-)
$ ls
hack.txt read.txt

我们新建一个hack.txt并提交,如果后面我们不需要hack.txt怎么办?

首先我们需要在本地,就是工作区中删除,然后再在暂存区中删除,最后再提交一次。

$ rm hack.txt //本地删除
$ 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:    hack.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git rm hack.txt //缓存区删除
rm 'hack.txt'
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted:    hack.txt

$ git commit -m "delete hack.txt" //提交,文件在版本库最新提交中删除了
[master ff6c9a4] delete hack.txt
1 file changed, 1 deletion(-)


如果我们需要已经删除的数据怎么办?注意,该文件知识在版本库最新提交中删除了,我们可以在历史版本中查看。

$ git cat-file -p HEAD^:hack.txt
hello


####一些其他命令
- :command:`git add -u`
执行:command:`git add -u`命令可以将(被版本库追踪的)本地文件的变更(修改、删除)全部记录到暂存区中。
比如上面例子中,我们可以用`git add-u` 代替`git rm hack.txt 
- :command:`git add -A`
执行:command:`git add -A`命令会对工作区中所有改动以及新增文件添加到暂存区,也是一个常用的技巧。如下

git cat-file -p HEAD^:hack.txt >hack.txt
$ git add -A
$ git commit -m "restore hack.txt"
[master 86d4116] restore hack.txt
1 file changed, 1 insertion(+)
create mode 100644 hack.txt


#八.文件忽略
像我们做c语言程序时,经常会出现一些对象文件o,如果我们执行add-A命令后,会把这些对象文件也添加进暂存区。(但是并不需要)。我们就需要进行文件忽略设置。

$ echo "first" > hello.o //新建一个o文件
$ git st -s //问号表示未跟踪
?? hello.o
$ echo "*.o" > .gitignore //新建一个.gitignore文件,并设置忽略内容
$ cat .gitignore
*.o
$ git status -s
?? .gitignore //可以发现hello.o文件被忽略了
$ git add .gitignore
$ git ci -m "ignore object files" //如果不希望添加到库里,也不希望:file:.gitignore文件带来干扰,可以在忽略文件中忽略自己。)
[master d067bdb] ignore object files
1 file changed, 1 insertion(+)
create mode 100644 .gitignore

- 忽略文件步骤为新建一个.gitnore文件并设置忽略内容,再add,commit。

如果我们想查看忽略的文件,可以使用--ignored参数。

$ git st --ignored -s
?? .gitignore
!! hello.o


要是我们想增加一个忽略类型的文件,可以使用-f参数,并明确文件名。

git add -f hello.o
baihangdeMacBook-Pro:baihangdemo baihang$ git st -s
A hello.o
?? .gitignore


#九.Git amend
在日常的Git操作中,会经常出现这样的状况,输入:command:`git commit`命令刚刚敲下回车键就后悔了:可能是提交说明中出现了错别字,或者有文件忘记提交,或者有的修改不应该提交,诸如此类。于是Git提供了一个简洁的操作——修补式提交,命令是::command:`git commit --amend`。
- 修改最新提交说明

$ git log --pretty=oneline
36b133bdd6d3c169bc6ed22479712d650179bc79 mistake commit
92cb42d419326fc4bf22afb2463dac9606596bbe init
$ git commit --amend
[master 9ea37af] correct commit
Date: Mon May 15 22:44:39 2017 +0800
1 file changed, 1 insertion(+), 1 deletion(-)
baihangdeMacBook-Pro:demo baihang$ git log --pretty=oneline
9ea37afeb54ea83835fe2707a68bb900cbf7a1fe correct commit
92cb42d419326fc4bf22afb2463dac9606596bbe init


当然上述也可以使用reset重置执行。

#十.Git cherry-pick(拣选),rebase(变基),revert(反转提交)
####1.cherry-pick
假设有这样一个提交记录,我们想删除中间D提交,应该怎么做呢。
![Screen Shot 2017-05-16 at 11.19.43 AM.png](https://img.haomeiwen.com/i1485048/dff92697a2aa76e7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们可以在C的基础上新建一个分支,然后将E和F嫁接在上面。
![Screen Shot 2017-05-16 at 11.21.00 AM.png](https://img.haomeiwen.com/i1485048/16bf433c2403ebf7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们先自己做一个提交记录如下。

git log --oneline
5934eeb six
99f7448 five
476e3d5 four
76adf28 three
cc624c3 second
20ad652 first

然后每次的提交版本,我们设置一个tag。(大致就是就是每个提交版本设置标识,后面会详细说明)

$ git tag F
$ git tag E HEAD^
$ git tag D HEAD^^
$ git tag C HEAD^^^
$ git tag B HEAD~4
$ git tag A HEAD~5
$ git log --oneline --decorate //--decorate 显示tag
5934eeb (HEAD -> master, tag: F) six
99f7448 (tag: E) five
476e3d5 (tag: D) four
76adf28 (tag: C) three
cc624c3 (tag: B) second
20ad652 (tag: A) first

我们将头指针切换到  C,切换过程显示处于非跟踪状态的警告,没有关系,因为只是大致演示。

$ git checkout C
Note: checking out 'C'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 76adf28... three

 - 执行拣选操作将E提交在当前HEAD上重放,我的理解就是嫁接在上面。

$ git cherry-pick master^
error: could not apply 99f7448... five
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

这个时候说明有些冲突,没关系,你打开文件,编辑内容,重新提交。

$ git add read.txt
baihangdeMacBook-Pro:cherry baihang$ git ci -m "five "
[detached HEAD a93028e] five
Date: Tue May 16 10:33:30 2017 +0800
1 file changed, 2 insertions(+)

 - 执行拣选操作将F提交在当前HEAD上重放。

$ git cherry-pick master
[detached HEAD 03bc166] six
Date: Tue May 16 10:33:48 2017 +0800
1 file changed, 1 insertion(+)
baihangdeMacBook-Pro:cherry baihang$ git log --pretty=oneline
03bc166 (HEAD) six
a93028e five change
76adf28 (tag: C) three
cc624c3 (tag: B) second
20ad652 (tag: A) first

这个时候D已经不在了,如果我们不想删除D,希望把CD合并成一次提交又该怎么做呢?

![Screen Shot 2017-05-16 at 11.38.00 AM.png](https://img.haomeiwen.com/i1485048/685462f07ea4415a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们先切换回初始状态。

$ git checkout master
Warning: you are leaving 2 commits behind, not connected to
any of your branches:

03bc166 six
a93028e five change

If you want to keep them by creating a new branch, this may be a good time
to do so with:

git branch <new-branch-name> 03bc166

Switched to branch 'master'
$ git reset --hard F
HEAD is now at 5934eeb six

头指针指向提交D

$ git checkout D
Note: checking out 'D'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 476e3d5... four

融合C,D。下面soft表示工作区和暂存区都没变,相当于内容是D的内容,但是当前版本是B。

$ git reset --soft HEAD^^

执行提交,提交说明重用C提交的提交说明。

baihangdeMacBook-Pro:cherry baihang$ git ci -C C
[detached HEAD 4ce6589] three
Date: Tue May 16 10:33:05 2017 +0800
1 file changed, 2 insertions(+)

接下来执行拣选操作。

git cherry-pick E
[detached HEAD d9b0199] five
Date: Tue May 16 10:33:30 2017 +0800
1 file changed, 1 insertion(+)
$ git cherry-pick F
[detached HEAD 01f1bd5] six
Date: Tue May 16 10:33:48 2017 +0800
1 file changed, 1 insertion(+)

我们查看一下日志,发现C和D已经融合。

$ git log --oneline --decorate
01f1bd5 (HEAD) six
d9b0199 five
4ce6589 three
cc624c3 (tag: B) second
20ad652 (tag: A) first

####2.reabase
命令:command:`git rebase`是对提交执行变基操作,即可以实现将指定范围的提交“嫁接”到另外一个提交之上。其常用的命令行格式有:

用法1: git rebase --onto <newbase> <since> <till> //<since>..<till>是指包括<till>的所有历史提交排除<since>以及<since>的历史提交后形成的版本范围。
用法2: git rebase --onto <newbase> <since>
用法3: git rebase <newbase> <till>
用法4: git rebase <newbase>

下面是上述命令的完整版本。

用法1: git rebase --onto <newbase> <since> <till>
用法2: git rebase --onto <newbase> <since> [HEAD]
用法3: git rebase [--onto] <newbase> [<newbase>] <till>
用法4: git rebase [--onto] <newbase> [<newbase>] [HEAD]

当遇到冲突时的一些指令

用法6: git rebase --continue //继续变基操作
用法7: git rebase --skip //跳过此提交
用法8: git rebase --abort //就此终止变基操作切换到变基前的分支上。


#######变基操作的过程:
 - 首先会执行:command:`git checkout`切换到<till>。
因为会切换到<till>,因此如果<till>指向的不是一个分支(如master),则变基操作是在detached HEAD(分离头指针)状态进行的,当变基结束后,还要像在“时间旅行一”中那样,对master分支执行重置以实现把变基结果记录在分支中。

 - 将<since>..<till>所标识的提交范围写到一个临时文件中。<since>..<till>是指包括<till>的所有历史提交排除<since>以及<since>的历史提交后形成的版本范围。

 - 当前分支强制重置(git reset --hard)到<newbase>。
相当于执行::command:`git reset --hard <newbase>`。

 - 从保存在临时文件中的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上。

- 如果遇到提交已经在分支中包含,跳过该提交。

- 如果在提交过程遇到冲突,变基过程暂停。用户解决冲突后,执行:command:`git rebase --continue`继续变基操作。或者执行:command:`git rebase --skip`跳过此提交。或者执行:command:`git rebase --abort`就此终止变基操作切换到变基前的分支上。

现在我们了解了大致过程,先执行上一节中的删除D。

$ git rebase --onto C E^ F
First, rewinding head to replay your work on top of it...
Applying: five
Using index info to reconstruct a base tree...
M read.txt
Falling back to patching base and 3-way merge...
Auto-merging read.txt
CONFLICT (content): Merge conflict in read.txt
error: Failed to merge in the changes.
Patch failed at 0001 five
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

发现出现冲突,下面有3个方案,一是修改冲突后,继续,而是跳过该提交,三是取消变基操作。我们分别来试验一下。

1.修改冲突

$ vi read.txt
$ git add read.txt //注意修改后,需要加入暂存区
$ git rebase --continue
Applying: five
Applying: six
Using index info to reconstruct a base tree...
M read.txt
Falling back to patching base and 3-way merge...
Auto-merging read.txt
$ git log --oneline --decorate
1cf52e2 (HEAD) six
f29de0d five
76adf28 (tag: C) three
cc624c3 (tag: B) second
20ad652 (tag: A) first
$ git branch


2.跳过

$ git rebase --skip
Applying: six
Using index info to reconstruct a base tree...
M read.txt
Falling back to patching base and 3-way merge...
Auto-merging read.txt
CONFLICT (content): Merge conflict in read.txt
error: Failed to merge in the changes.
Patch failed at 0002 six
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
$ vi read.txt
$ git add read.txt
$ git rebase --continue
Applying: six
$ git log --oneline --decorate
ec67d91 (HEAD) six
76adf28 (tag: C) three
cc624c3 (tag: B) second
20ad652 (tag: A) first

此时发现跳过之后,E提交不在了。

3.取消 rebase

$ git rebase --abort
$ git log --oneline --decorate
5934eeb (HEAD, tag: F, master) six
99f7448 (tag: E) five
476e3d5 (tag: D) four
76adf28 (tag: C) three
cc624c3 (tag: B) second
20ad652 (tag: A) first


试试合并C和D

$ git checkout D
Previous HEAD position was 5934eeb... six
HEAD is now at 476e3d5... four
$ git reset --soft HEAD^^
$ git commit -C C
$ git tag newbase
$ git rebase --onto newbase E^ master //变基操命令行没有像之前的操作使用使用了参数F,而是使用分支master。所以接下来的变基操作会直接修改master分支,而无须再进行对master的重置操作。
$ git rebase --onto newbase E^ master
First, rewinding head to replay your work on top of it...
Applying: five
Applying: six
$ git log --oneline --decorate
f4b4a50 (HEAD -> master) six
7388125 five
94e64aa (tag: newbase) three
cc624c3 (tag: B) second
20ad652 (tag: A) first

 - 看看提交日志,看到提交C和提交D都不见了,代之以融合后的提交newbase。还可以看到最新的提交除了和HEAD的指向一致,也和master分支的指向一致。

#####3.rebase加强版
在rebase操作上我们可以增加-i参数,进入一个交互界面。

首先我们按照上两节要求删除D

$git rebase -i C

pick 476e3d5 four
pick 99f7448 five
pick 5934eeb six

Rebase 76adf28..5934eeb onto 76adf28 (3 commands)

Commands:

p, pick = use commit

r, reword = use commit, but edit the commit message

e, edit = use commit, but stop for amending

s, squash = use commit, but meld into previous commit

f, fixup = like "squash", but discard this commit's log message

x, exec = run command (the rest of the line) using shell

d, drop = remove commit

These lines can be re-ordered; they are executed from top to bottom.

If you remove a line here THAT COMMIT WILL BE LOST.

However, if you remove everything, the rebase will be aborted.

Note that empty commits are commented out

~
"~/cherry/.git/rebase-merge/git-rebase-todo" 22L, 692C

于是我们进入一个编辑界面,我们可以看到下面有很多命令。

 - 开头的四行由上到下依次对应于提交C、D、E、F。

 - 前四行缺省的动作都是pick,即应用此提交。

 - 参考配置文件中的注释,可以通过修改动作名称,在变基的时候执行特定操作。

 - 动作reword或者简写为r,含义是变基时应用此提交,但是在提交的时候允许用户修改提交说明。

- 动作edit或者简写为e,也会应用此提交,但是会在应用时停止,提示用户使用:command:`git commit --amend`执行提交,以便对提交进行修补。

- 当用户执行:command:`git commit --amend`完成提交后,还需要执行:command:`git rebase --continue`继续变基操作。Git会对用户进行相应地提示。

- 实际上用户在变基暂停状态执行修补提交可以执行多次,相当于把一个提交分解为多个提交。而且edit动作也可以实现reword的动作,因此对于老版本的Git没有reword可用,则可以使用此动作。

- 动作squash或者简写为s,该提交会与前面的提交压缩为一个。

- 动作fixup或者简写为f,类似squash动作,但是此提交的提交说明被丢弃。

- 可以通过修改配置文件中这四个提交的先后顺序,进而改变最终变基后提交的先后顺序。

- 可以对相应提交对应的行执行删除操作,这样该提交就不会被应用,进而在变基后的提交中被删除。

接下来我们删除其中的D的那一行,保存,于是就成功删除了。

pick 99f7448 five
pick 5934eeb six

Successfully rebased and updated refs/heads/master.

合并又是怎么做的呢?

$ rebase -i C^
pick 76adf28 three
squash 476e3d5 four
pick 99f7448 five
pick 5934eeb six

下面显示了合并信息。

This is a combination of 2 commits.

This is the 1st commit message:

three

This is the commit message #2:

four



#####4.revert
前面介绍的操作都涉及到对历史的修改,这对于一个人使用Git没有问题,但是如果多人协同就会有问题了。在这种情况下要想修正一个错误历史提交的正确做法是反转提交,即重新做一次新的提交,相当于错误的历史提交的反向提交,修正错误的历史提交。

当前版本库最新的提交包含如下改动:

$ git show HEAD
commit 5934eeb5b5c53308fb4d09fae00e5395231e2353
Author: jkbaihang 909920027@qq.com
Date: Tue May 16 10:33:48 2017 +0800

six

diff --git a/read.txt b/read.txt
index 8fda00d..cead32e 100644
--- a/read.txt
+++ b/read.txt
@@ -3,3 +3,4 @@ B
C
D
E
+F

在不改变这个提交的前提下对其修改进行撤销,就需要用到git revert反转提交。

$ git Revert "six revert"

This reverts commit 5934eeb5b5c53308fb4d09fae00e5395231e2353.


可以在编辑器中修改提交说明,提交说明编辑完毕保存退出则完成反转提交。查看提交日志可以看到新的提交相当于所撤销提交的反向提交。

[master de89819] Revert "six revert"
1 file changed, 1 deletion(-)

$ git log --stat -2
commit de89819510c78833caa438c841e23c2c60e467a3
Author: jkbaihang 909920027@qq.com
Date: Tue May 16 23:30:26 2017 +0800

Revert "six revert"

This reverts commit 5934eeb5b5c53308fb4d09fae00e5395231e2353.

read.txt | 1 -
1 file changed, 1 deletion(-)

commit 5934eeb5b5c53308fb4d09fae00e5395231e2353
Author: jkbaihang 909920027@qq.com
Date: Tue May 16 10:33:48 2017 +0800

six

read.txt | 1 +
1 file changed, 1 insertion(+)


#10.Git克隆
Git的版本库目录和工作区在一起,因此存在一损俱损的问题,即如果删除一个项目的工作区,同时也会把这个项目的版本库删除掉。一个项目仅在一个工作区中维护太危险了,如果有两个工作区就会好很多。


![Screen Shot 2017-05-19 at 3.33.12 PM.png](https://img.haomeiwen.com/i1485048/ce416eb5e20ef5c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 - 版本库A通过克隆操作创建克隆版本库B。
 - 版本库A可以通过推送(PUSH)操作,将新提交传递给版本库B;
 - 版本库A可以通过拉回(PULL)操作,将版本库B中的新提交拉回到自身(A)。
 - 版本库B可以通过拉回(PULL)操作,将版本库A中的新提交拉回到自身(B)。
 - 版本库B可以通过推送(PUSH)操作,将新提交传递给版本库A;

###Clone
Git使用:command:`git clone`命令实现版本库克隆,主要有如下三种用法:

>用法1: git clone <repository> <directory>
>用法2: git clone --bare   <repository> <directory.git>
>用法3: git clone --mirror <repository> <directory.git>

 - 用法1将<repository>指向的版本库创建一个克隆到:file:`<directory>`目录。目录:file:`<directory>`相当于克隆版本库的工作区,文件都会检出,版本库位于工作区下的:file:`.git`目录中。
 - 用法2和用法3创建的克隆版本库都不含工作区,直接就是版本库的内容,这样的版本库称为裸版本库。一般约定俗成裸版本库的目录名以:file:`.git`为后缀,所以上面示例中将克隆出来的裸版本库目录名写做:file:`<directory.git>`。
 - 用法3区别于用法2之处在于用法3克隆出来的裸版本对上游版本库进行了注册,这样可以在裸版本库中使用:command:`git fetch`命令和上游版本库进行持续同步。
用法3只在 1.6.0 或更新版本的Git才提供。

###Remote
为了便于管理,Git要求每个远程主机都必须指定一个主机名。`git remote`命令就用于管理主机名。
不带选项的时候,`git remote`命令列出所有远程主机。

$ git remote
origin


使用`-v`选项,可以参看远程主机的网址。

$ git remote -v
origin /users/baihang/user1 (fetch)
origin /users/baihang/user1 (push)

上面命令表示,当前只有一台远程主机,叫做`origin`,以及它的网址。

`git remote add`命令用于添加远程主机。

$ git remote add <主机名> <网址>

`git remote rm`命令用于删除远程主机。

$ git remote rm <主机名>

`git remote rename`命令用于远程主机的改名。

$ git remote rename <原主机名> <新主机名>


###Fetch
一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到`git fetch`命令。

$ git fetch <远程主机名>

上面命令将某个远程主机的更新,全部取回本地。
`git fetch`命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。
默认情况下,`git fetch`取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。

$ git fetch <远程主机名> <分支名>

比如,取回origin主机的master分支。

$ git fetch origin master


所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取。比如`origin`主机的`master`,就要用`origin/master`读取。
git branch命令的-r选项,可以用来查看远程分支,-a选项查看所有分支。

$ git branch -r
origin/master

$ git branch -a


上面命令表示,本地主机的当前分支是`master`,远程分支是`origin/master`。
取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支。

$ git checkout -b newBrach origin/master

上面命令表示,在`origin/master`的基础上,创建一个新分支。
此外,也可以使用`git merge`命令或者`git rebase`命令,在本地分支上合并远程分支。

$ git merge origin/master

或者

$ git rebase origin/master

上面命令表示在当前分支上,合并origin/master。




###Push
`git push`命令用于将本地分支的更新,推送到远程主机。它的格式与`git pull`命令相仿。

$ git push <远程主机名> <本地分支名>:<远程分支名>

注意,分支推送顺序的写法是<来源地>:<目的地>,所以`git pull`是<远程分支>:<本地分支>,而`git push`是<本地分支>:<远程分支>。
如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。

$ git push origin master

上面命令表示,将本地的`master`分支推送到`origin`主机的`master`分支。如果后者不存在,则会被新建。
如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。

$ git push origin :master

等同于

$ git push origin --delete master

上面命令表示删除`origin`主机的`master`分支。

如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。

$ git push origin

如果当前分支只有一个追踪分支,那么主机名都可以省略。

$ git push

如果当前分支与多个主机存在追踪关系,则可以使用-u选项指定一个默认主机,这样后面就可以不加任何参数使用`git push`。

$ git push -u origin master

上面命令将本地的master分支推送到origin主机,同时指定`origin`为默认主机,后面就可以不加任何参数使用`git push`了。



###Pull
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。

$ git pull <远程主机名> <远程分支名>:<本地分支名>

比如,取回`origin`主机的`next`分支,与本地的`master`分支合并,需要写成下面这样。

$ git pull origin next:master

如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin next

上面命令表示,取回`origin/next`分支,再与当前分支合并。实质上,这等同于先做`git fetch`,再做`git merge`。

$ git fetch origin
$ git merge origin/next

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在`git clone`的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的`master`分支自动"追踪"`origin/master`分支。

Git也允许手动建立追踪关系。

git branch --set-upstream master origin/next


上面命令指定master分支追踪`origin/next`分支。
如果当前分支与远程分支存在追踪关系,`git pull`就可以省略远程分支名。

$ git pull origin

上面命令表示,本地的当前分支自动与对应的origin主机"追踪分支"(remote-tracking branch)进行合并。
如果当前分支只有一个追踪分支,连远程主机名都可以省略。

$ git pull

上面命令表示,当前分支自动与唯一一个追踪分支进行合并。




####对等工作区

![](https://img.haomeiwen.com/i1485048/4fb7cd30ca127ccf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们先创建一个版本库,和user2空目录。

$ git init user1
Initialized empty Git repository in /Users/baihang/user1/.git/
$ mkdir user2

接下来我们进入user1,初始一个空的提交,然后克隆到user2。

$ cd user1
$git ci --allow-empty -m "init commit"
[master (root-commit) 50fa2fc] init commit
$ git clone /users/baihang/user1 /users/baihang/user2
Cloning into '/users/baihang/user2'...
done.

在user1生成一些测试提交

git commit --allow-empty -m "test1"
[master 0ed19a0] test1
$ git commit --allow-empty -m "test2"
[master 0efd5b0] test2

这个时候我们能执行push试试

git push /users/baihang/user2
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream /users/baihang/user2 master
这我们说明一下upstream是什么
>git中存在upstream和downstream,简言之,当我们把仓库A中某分支x的代码push到仓库B分支y,此时仓库B的这个分支y就叫做A中x分支的upstream,而x则被称作y的downstream,这是一个相对关系,每一个本地分支都相对地可以有一个远程的upstream分支(注意这个upstream分支可以不同名,但通常我们都会使用同名分支作为upstream)。
************
初次提交本地分支,例如git push origin develop操作,并不会定义当前本地分支的upstream分支,我们可以通过git push --set-upstream origin develop,关联本地develop分支的upstream分支。

接下来我们关联后,得出一大推内容。

$ git push --set-upstream /users/baihang/user2 master
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 257 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require 'git reset --hard' to match
remote: the work tree to HEAD.
remote:
remote: You can set 'receive.denyCurrentBranch' configuration variable to
remote: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: its current branch; however, this is not recommended unless you
remote: arranged to update its work tree to match what you pushed in some
remote: other way.
remote:
remote: To squelch this message and still keep the default behaviour, set
remote: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To /users/baihang/user2
! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to '/users/baihang/user2'

翻译成中文意思如下

$ git push --set-upstream /users/baihang/user2 master
...
对方说: 错了:
拒绝更新已检出的分支 refs/heads/master 。
缺省更新非裸版本库的当前分支是不被允许的,因为这将会导致
暂存区和工作区与您推送至版本库的新提交不一致。这太古怪了。

          如果您一意孤行,也不是不允许,但是您需要为我设置如下参数:

              receive.denyCurrentBranch = ignore|warn

          到/users/baihang/user2

! [对方拒绝] master -> master (分支当前已检出)
错误: 部分引用的推送失败了, 至 '/users/baihang/user2'

从错误输出可以看出,虽然可以改变Git的缺省行为,允许向工作区推送已经检出的分支,但是这么做实在不高明。


我们试试到备用库中执行pull命令,可以看到同步。

$ cd user2
$ git pull
From /users/baihang/user1
50fa2fc..0efd5b0 master -> origin/master
Updating 50fa2fc..0efd5b0
Fast-forward
$ git log --oneline -2
0efd5b0 test2
0ed19a0 test1

#####为什么执行 git pull 拉回命令没有像执行 git push 命令那样提供那么多的参数呢?
这是因为在执行:command:`git clone`操作后,克隆出来的user2版本库中对源版本库进行了注册,所以当在 user2版本库执行拉回操作,无须设置源版本库的地址。

可以使用下面的命令查看对上游版本库的注册信息:

git remote -v
origin /users/baihang/user1 (fetch)
origin /users/baihang/user1 (push)


实际注册上游远程版本库的奥秘都在Git的配置文件中(略去无关的行):

$ cat .git/config
[core]
...
[remote "origin"]
url = /users/baihang/user1
fetch = +refs/heads/:refs/remotes/origin/
[branch "master"]
remote = origin
merge = refs/heads/master


####克隆生成裸版本库
上一节在对等工作区模式下,工作区之间执行推送,可能会引发大段的错误输出,如果采用裸版本库则没有相应的问题。这是因为裸版本库没有工作区。没有工作区还有一个好处就是空间占用会更小。


![Screen Shot 2017-05-19 at 4.25.46 PM.png](https://img.haomeiwen.com/i1485048/93bec42a97f8f131.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们克隆一个裸版本库,以.git后缀命名(裸版本库以.git为后缀)。

$ git clone --bare /users/baihang/user1 /users/baihang/user1.git
Cloning into bare repository '/users/baihang/user1.git'...

上一篇下一篇

猜你喜欢

热点阅读