[关闭]
@xtccc 2016-02-19T16:37:24.000000Z 字数 5684 阅读 2874

Git

给我写信
GitHub

此处输入图片的描述


开发工具


目录:



Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。

1. 远程仓库


1.1 克隆仓库

通过命令clone来克隆某个分支到本地时,可以不使用public key,而是直接使用username/password来实现。

例如,如果我的Github用户名为 TaoXiao,分支的地址为 https://github.com/GridX/gridx.git ,那么我可以用如下的命令来克隆该分支代码到本地:

  1. git clone https://TaoXiao@github.com/GridX/gridx.git

执行该命令,随后会让你输入该用户名的密码,输入正确后就可以下载了。

QQ20160207-0@2x.png-131.2kB

clone仓库到本地后,本地分支只有master,看不到其他的分支,我们必须手动地创建本地分支,并将远程分支拉取到本地分支。

  1. git checkout -b feat1 origin/feat1

1.2 推送仓库

我们在GitHub上创建一个空的Repository,为其取名为 learn

  1. 在本地用命令创建一个同名的版本库:

    1. mkdir learn; cd learn
    2. git init

    这将创建一个名为master的本地分支。

  2. 将这个本地版本库与远程仓库进行关联:

    1. git remote add origin git@github.com:TaoXiao/learn.git

    这里的TaoXiao是我在Github上的账户名,origin是远程仓库的名称。

  3. 在本地版本库中加入一些新文件并commit

    1. touch README.MD
    2. git add README.MD
    3. git commit -m "add README.MD"
  4. 将本地版本库的内容推送到远程仓库

    1. git push -u origin master

    这里的 origin 是远程仓库的名字,master 是远程仓库的默认分支名。通过命令 git remote 可以查看远程仓库的名称,命令 git remote -v 则可以显示更详细的信息。

    由于远程仓库是空的,第一次推送master分支时,我们带上了参数 -u 。这个命令不但会把本地的master分支推送到远程仓库的master分支,还会把它们俩关联起来。

    现在,远程仓库中的状态就与本地版本库的状态达到了一致。今年,如果在本地作了提交,就可以通过命令 git push origin master 将本地master分支的最新修改推送到远程仓库的master分支






2. 版本管理


2.1 查阅

2.1.1 文件变化情况

可以通过 git diff [filename] 来查看文件的变化情况。这个命令可以比较一个文件在不同版本下的差异

  1. 比较doc.txt文件对应于某两次提交的差异

    git diff [commit_1 id] [commit_2 id] [file_path]
    
  2. 比较当前文件与其最新提交状态的差异

    git diff HEAD -- [file_path]
    

2.1.2 commit 日志

通过以下两种命令,可以按照从新到旧的顺序查看commit的历史:

  1. git log
  2. git log --pretty=oneline

例:
QQ20160218-0@2x.png-54kB

其中,左侧的一长串文字是commit id (版本号)


2.2 操作

2.2.1 版本回退

将项目内容回退到某个版本时,必须知道目标版本号,这通过 git log 命令就可以看到。

HEAD是当前版本,HEAD^是上一个版本,..., HEAD~100是往前第100个版本。

回退到某个版本的命令:
git reset --hard [commit id]

除了从新版本回退到旧版本,也可以从旧版本回退到新版本。

假如我们从当前版本A回退到了一个旧版本B,然后又想再次回退到A。但是回退到B后,通过命令 git log 已经找不到版本A的commit id了,怎么办?

通过命令 git reflog 就可以重新看到版本A的commit id。

2.2.2 撤销修改

通过命令 git checkout -- [file],可以把当前工作区(working directory)内的修改撤销,包括两种情况:

  1. 文件在工作区被修改后还未被放到暂存区(stash/index),这样,撤销修改后文件就回到与版本库完全相同的状态;
  2. 文件被添加到暂存区后,又作了修改,这样,撤销修改后文件就回到了与暂存区完全相同的状态,但是不能撤销暂存区内已有的修改。

总之,这个命令就是让文件回到最近一次 git add 或者 git commit 时的状态。

如果一个文件修改了之后,被加入了暂存区,那么,命令 git checkout -- [file] 无法让该文件恢复到上次commit后的状态。 通过命令 git reset HEAD [file],可以撤销(unstage)暂存区内的修改。但是,工作区内的文件还是不会变化的。如果继续使用命令 git checkout -- [file],则可以使该文件的状态与版本库的状态完全相同。

2.2.3 删除文件

用命令 rm [file] 会将该文件从FS中删掉,但是并没有从版本库中删掉。调用命令 git status 就可以看出来:
QQ20160218-1@2x.png-194.4kB

如果是误删了该文件,由于版本库中依然存在该文件,因此我们可以用命令 git checkout -- [file] 轻松恢复。

如果是真的想将该文件从版本库中彻底删掉,则可以用命令 git rm [file] 将其删除,并且 git commit




3. 分支管理


参考



每次提交,Git都把这些commits串成一条时间线,这条时间线就是一个分支。HEAD将指向当前的分支。

一开始时,只有一个分支master。Git将HEAD指向master,并将master指向最新的提交。这样,就能确定当前的分支,以及当前分支的提交点。

每次提交,master都前进一步,随着我们不断地提交,master分支也越来越长。

3.1 创建分支

首先,创建名为dev的分支:

  1. git branch dev

然后,切换到dev分支:

  1. git checkout dev

或者直接用下面的命令创建并切换分支:

  1. git checkout -b dev

查看当前的分支:

  1. $ git branch
  2. * dev
  3. master

3.2 合并分支

将分支dev合并到当前分支的命令为:

  1. git merge dev

如果没有冲突,合并顺利完成,则可以删除dev分支:

  1. git branch -d dev

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

3.3 解决冲突

假设,当前master分支与feat分支中文件README.MD内容是相同的:

this is a readme file
This branch is 'dev'



下面演示冲突的产生。

  1. 切换到分支feat1,编辑该文件,然后提交

    this is a readme file
    This branch is 'dev'
    This line is added within branch 'feat1'

  2. 切换到分支master,编辑该文件,然后提交

    this is a readme file
    This branch is 'dev'
    This line is added by branch 'master'

  3. 试图将分支feat1合并到分支master

    $ git merge feat1
    Auto-merging README.MD
    CONFLICT (content): Merge conflict in README.MD
    Automatic merge failed; fix conflicts and then commit the result.

合并失败!必须手动解决文件的冲突后再提交。git status 命令可以告诉我们发生冲突的文件:
QQ20160219-0@2x.png-110.4kB



可以直接在vim中查看README.MD文件的内容:

    this is a readme file
    This branch is 'dev'
    <<<<<<< HEAD
    This line is added by branch 'master'
    =======
    This line is added within branch 'feat1'
    >>>>>>> feat1

Git会用 <<<<<<<=======>>>>>>> 标记出不同分支的内容,我们可以直接修改该文件的内容:

    this is a readme file
    This branch is 'dev'
    This line is added by branch 'master'
    This line is added within branch 'feat1'

现在,冲突已经被人为解决了,提交该文件:

$ git commit -m "解决冲突"
[master 3ee94f4] 解决冲突

还可以用命令 git log --graph --pretty=oneline --abbrev-commit 来查看分支的合并情况:
QQ20160219-1@2x.png-80.5kB


3.4 未提交的改动会影响其他分支

假设现在master分支与feat1分支的状态完全相同(是3.3小节中冲突解决后的状态)。下面演示,master分支中的改动如果没有被commit,是怎样影响到feat1分支的。

  1. master分支中增加一个新文件LICENSE,并修改文件README.MD的内容:

    今天天气不错
    
  2. 以上改动,不要commitmaster分支;是否调用add都无所谓

  3. 切换到分支feat,并查看工作区的状态:
    QQ20160219-2@2x.png-231.4kB
  4. 可以看到,feat1分支的工作区也受到了影响,原因在于:master分支的改动没有被提交;

尝试一下如果提交master分支的改动,会不会影响feat1分支?
答案是:不会。

3.5 stash

有时候我们需要保留工作区的改动,不要提交这些改动,但同时让这些工作区的改动对其他的分支不可见。

考虑这样的一个需求:

master分支中是稳定的代码,此刻我正在feat1分支中修改一些文件。突然,我需要紧急修复一个bug,所以我需要新建一个名为bug的临时分支。但是,feat1分支中的代码改动还未完成,不能提交。而对bug的修改又是必须基于master分支的(即feat1分支中的改动不能让bug分支看到),此时该怎么办?

这时我们可以利用stash来储藏工作现场。所谓的储藏工作现场,是指将当前的工作区的当前状态储藏起来,等以后恢复现场之后继续工作。当工作现场储藏起来后,工作区就表现为“干净的”工作区。

例子:
假设master分支和feat1分支中的README.MD文件内容是一致的:

this is a readme file
This branch is 'dev'
This line is added by branch 'master'
This line is added within branch 'feat1'
今天天气不错
  1. 我正在feat1中添加新的功能,修改了README.MD文件:

    this is a readme file
    This branch is 'dev'
    This line is added by branch 'master'
    This line is added within branch 'feat1'
    今天天气不错
    新增加的feature应该很cool,
    但是得花我好几天的时间才行!
    
  2. master分支的代码上发现一个bug,必须立即修复,但是新feature的代码还没写完,现在不能commit,怎么办?用git stash来保存feat1分支中的工作现场吧。
    QQ20160219-3@2x.png-92.1kB

    可以看到,用命令git status查看工作区状态,发现现在它是是“干净的”,之前的工作现场被存储了。如果转到master分支查看,发现master分支看不到我在feat1分支中新增加但还未被提交的代码。

  3. 由于我们要基于master分支修复bug,所以就转到该分支,然后新建bug分支。在bug分支中进行修改、提交,然后转回master分支,将bug分支合并到master分支,然后删除bug分支。

  4. 回到feat1分支,恢复工作现场,继续干活。查看stash内容可以使用命令git stash list,恢复工作现场有两种方式:

    a). 调用命令 git stash apply,但是stash内容并不删除,需要调用git stash drop来删除stash内容;
    b). 调用命令 git stash pop,恢复工作现场并删除stash内容




4. 多人协作


当多个人协作时,各人都需要向同一个分支上推送自己的改动。

下面,我们用两个git目录(p1和p2目录)来模拟两个用户,它们俩要协作开发feat1分支。

假设这个分支中存在文件 LICENSE,它当前的内容为:

任何人都可以自由地分发和修改本软件



首先,用户p1在本地修改该文件:

任何人都可以自由地分发和修改本软件
我是用户p1
我认为:自由软件是最好的。

修改好了之后将其commit,并push到远程仓库的feat1分支。


接着,用户p2也在本地修改该文件:

任何人都可以自由地分发和修改本软件
我是p2
其实,非开源软件也有很牛逼的,比如苹果的iOS和OSX

修改好了之后将其commit.
但是在试图push到远程仓库时发生了冲突:
QQ20160219-4@2x.png-128.5kB



所以,我们应该先用 git pull 将远程仓库的feat1分支的最新内容拉取到本地,在本地进行合并,解决冲突了再提交并推送到远程仓库。

pull成功了,但是automatic merge失败了:
QQ20160219-5@2x.png-158.2kB



所以我们需要在本地手动解决冲突,完成合并,然后在提交并推送到远程仓库:
QQ20160219-6@2x.png-117.2kB

合并冲突的方法在之前的小节中讲过了,这里就不赘述了。


总结:

因此,多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin branch-name推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!



添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注