zoukankan      html  css  js  c++  java
  • 精《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #4 如何使用Git

    HACK #4 如何使用Git

    本节介绍Git的使用方法。
    Git是Linux内核等众多OSS(Open Source Software,开源软件)开发中所使用的SCM(Source Code Management,源码管理)系统。在2005年以前,在Linux内核开发中一直使用一个叫做BitKeeper的SCM。但是由于后来BitKeeper的许可证被更改,可能会对开发造成障碍,因此Linux不得不改用新的SCM进行开发。在这种情况下,Linux内核的创始人Linus Torvalds就开发了Git,将Linus树的仓库转移到了Git中。直到2011年的今天,Linus树仍然使用 Git进行管理,其他大部分的开发树使用的也是Git。目前Git由维护人员滨野纯(Junio C Hamano)等人持续进行开发。
    Git的设计使其能够支持Linux或者OSS的开发模式。Git具有这些特征:分布式仓库;与互联网具有亲和性;版本更新记录管理不以单个文件为对象,而是将整个源码树作为一个对象;处理速度快等。
    Git具有非常多的功能,如果一一进行说明的话能写成一本书。这里主要针对第一次使用Git的读者,通过使用指南的形式介绍日常使用较多的基本功能,同时对相关基本概念进行解说。
    笔者使用的是1.7.1版本的Git。
    分布式仓库型SCM
    仓库是指保存SCM中源代码等信息及历史记录的原始数据的地方。CVS是以往使用较多的SCM,而在CVS中一直是将源代码从仓库签出(checkout)到本地工作区,进行修改后将代码提交到仓库中。像这样,仓库和工作区明确分开,多个开发者针对单一仓库进行提交的SCM称为“单一式仓库型”。
    而Git采用的是与之相反的“分布式仓库型”结构。在Git中,工作区本身就是仓库。也就是说,开发者拥有各自的仓库,它们之间不存在结构层面的上下关系,所有仓库都是并行存在的。在Linux内核中一般认为linus树的仓库是“中央”仓库,其实这只是大家一致认可的叫法,从Git的结构上看,完全没有任何设置是以linus树作为中央的。
    如上所述,Git直到完结时都是将本地磁盘的工作区作为仓库的。要能够熟练使用Git,首先必须掌握如何在本地仓库进行操作。下面将首先讲述在本地仓库进行操作的流程,然后介绍与其他仓库进行协作的方法。
    在本地仓库进行操作
    创建新的仓库
    使用Git管理源代码的第一步是创建新的仓库。创建仓库需要创建普通的目录,并将该目录作为Git的仓库使用。操作过程如下。

    $ mkdir 杙 ~/hello
    $ cd ~/hello
    $ git init
    

    这时,~/hello就可以作为Git的仓库使用了。下面就使用这个仓库来了解一下Git的基本功能。
    小贴士:Git的命令以git 的形式启动。每条命令都有man page(帮助页面),当想要阅读帮助页面时,请用连字符将man git-和子命令连接起来。
    例如,当想要阅读init子命令的帮助页面时,应写为:

    $ man git-init
    

    如果写成:

    $ man git init
    

    显示出来的就是git(1)和init(8)的页面。
    Git设置
    在进行实际的文件操作前,首先要进行最低限度的必要设置。笔者一般进行的最低设置是提交者(committer)的姓名与邮件地址。在仓库目录下可以执行下列操作来进行这些设置。在这里设置的是笔者的信息。

    $ git config --add user.email"m_ikeda@hogeraccho.com"
    $ git config --add user.name"Munehiro "Muuhh " Ikeda"
    

    把这个设置写入仓库目录的.git/config文件中。执行上面的git config命令后,这个文件中就应当写入了下列信息。
    [user]

    email = m_ikeda@hogeraccho.com
         name = Munehiro "Muuhh" Ikeda
    

    除此以外,还可以进行很多种设置,这里就先点到为止。
    小贴士:写入.git/config的设置项目仅能适用于该仓库。
    如果为git config指定--global选项,还可以参照要在用户已启动的所有仓库上共同使用的设置。与之对应的设置文件为~/.gitconfig。
    当想要参照或添加整个系统的共同设置时,可以使用--system选项。与之对应的设置文件是/etc/gitconfig。
    将文件添加到仓库中
    现在,尝试创建文件并将其添加到Git的仓库中。首先,创建一个hello.c文件,其内容如下。

    /* hello.c */
    #include <stdio.h>
    
    int main(void)
    {
        printf("Herro world!!
    ");
        return 0;
    }
    

    在Git中向仓库增加或修改文件的过程称为“提交”。提交分为两阶段进行,首先指定要提交的对象文件,然后进行实际的提交。

    $ git add hello.c
    $ git commit
    

    执行git commit后,会启动一个用来输入提交信息的编辑器。由于这是第一次提交,因此在第一行中输入Initial commit,并保存文件。
    小贴士:这里为git add明确指定了文件名hello.c,如果有多个文件,可以使用:

    $ git add
    

    将当前目录下的所有文件作为提交对象。
    小贴士:在编辑提交信息时,如果没有保存文件就关闭了编辑器,就不会提交文件。
    这样,hello.c就提交到了仓库中,以后就可以使用Git来管理和修改记录等。
    修改并提交文件
    仔细看一看刚才提交的文件,突然发现Hello居然写成了Herro了!下面就学习如何进行修改。
    将hello.c的printf("Herro world!!n");行修改成printf("Hello world!!n");保存后提交。对文件进行修改时,也像新增加时一样需要对文件执行git add命令,将其作为提交对象。但是,当对多个文件进行修改时,一般会希望先把修改后的文件全部提交。在这种情况下,如果使用git commit的-a选项,就不需要执行git add命令。

    $ git commit -a
    

    这时会像新增加时一样启动编辑器,要求输入提交信息,在输入Correct misspelling后保存文件并关闭编辑器。
    这样修改内容就提交到了仓库中。
    小贴士:git commit -a原本是用来提交Git所管理的所有文件的。一次也没有执行git add命令的文件不会提交,例如,新创建的文件等。
    确认工作区的状态
    如果进行了很多修改,就需要确认已经提交工作区的仓库处于什么状态。我们试着稍微改变源代码来进行确认。
    首先将hello.c的return 0;改为return 1;。然后创建新文件goodbye.c。

    /* goodbye.c */
    #include <stdio.h>
    
    int main(void)
    {
        printf("Goodbye world!!
    ");
        return 0;
    }
    

    用来确认状态的第一个命令是git status,尝试执行以下命令。

    $ git status
    # On branch master
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #    modified:   hello.c
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #    goodbye.c
    no changes added to commit (use "git add" and/or "git commit -a")
    

    输出的内容显示hello.c的修改还没有提交,goodbye.c不在Git的管理范围内。
    要确认哪个文件在Git的管理范围内,可以使用下列命令。

    $ git ls-files
    hello.c
    

    如果想要看到修改hello.c的历史记录,可以执行下列命令。差别就会分段显示出来。

    $ git diff
    diff --git a/hello.c b/hello.c
    index aa28db5..7ef0a54 100644
    --- a/hello.c
    +++ b/hello.c
    @@ -4,6 +4,6 @@
     int main(void)
     {
         printf("Hello world!!
    ");
    -    return 0;
    +    return 1;
     }
    

    显示出差别的只有Git管理范围内的文件。由于goodbye.c还不在Git的管理范围内,因此没有任何显示。
    下面,对goodbye.c执行git add命令,确认前者是否在Git的管理范围内。

    $ git add goodbye.c
    $ git ls-files
    goobye.c
    hello.c
    

    可以看到多出了goodbye.c。
    这时可以再次使用git diff来显示差别。但奇怪的是,刚才明明对goodbye.c使用了git add命令,为什么没有显示呢?这时,再执行下列命令。

    $ git add hello.c
    $ git diff
    

    而这次竟然什么也不显示了。这是怎么回事?
    事实上,git diff显示的并不是最新提交与工作区之间的差别,而是“缓存区”(staging area,也称为分段存储区)与工作区之间的差别。缓存区是用来暂时存放下一次要提交到仓库的信息的区域。也就是说,git add的作用是将当前工作区的内容存放到缓存区。执行git commit后,最新提交和缓存区的内容是一致的。在工作区进行修改后再次执行git add,最新提交与缓存区的内容就变得不同,而缓存区与工作区的内容一致。在上例中,在执行git add hello.c后工作区与缓存区的内容是完全一致的。因此git diff就不会再显示任何内容。
    当想看到的不是缓存区与当前工作区的差别,而是最新提交与当前工作区的差别时,可以为git diff指定表示最新提交的HEAD。

    $ git diff HEAD
    diff --git a/goodbye.c b/goodbye.c
    new file mode 100644
    index 0000000..13f79ea
    --- /dev/null
    +++ b/goodbye.c
    @@ -0,0 +1,9 @@
    +/* goodbye.c */
    +#include <stdio.h>
    +
    +int main(void)
    +{
    +    printf("Goodbye world!!
    ");
    +    return 0;
    +}
    +
    diff --git a/hello.c b/hello.c
    index aa28db5..7ef0a54 100644
    --- a/hello.c
    +++ b/hello.c
    @@ -4,6 +4,6 @@
     int main(void)
     {
         printf("Hello world!!
    ");
    -    return 0;
    +    return 1;
     }
    

    如果想要知道最新提交与缓存区的差别,可以使用git diff --cached。由于当前缓存区与工作区是完全一致的,因此输入的内容与上述内容相同。
    然后,使用git commit -a提交所作的修改,提交信息为Add goodbye.c。
    参照提交记录
    当想要参照提交记录时,可以使用git log命令。如果在当前仓库执行这条命令,就会显示关于之前进行的3次提交的下列相关信息(日期等信息因环境不同而各异)。

    $ git log
    commit 9b670c34bd7bd772648a99738017802f2b24f859
    Author: Munehiro "Muuhh" Ikeda <m_ikeda@hogeraccho.com>
    Date:   Sat Apr 2 14:09:39 2011 -0700
    
        Add goodbye.c
    
    commit c47feeef44a652bda15dbb580d48213dc1294664
    Author: Munehiro "Muuhh" Ikeda <m_ikeda@hogeraccho.com>
    Date:   Sat Apr 2 13:20:10 2011 -0700
    
        Correct misspelling
    
    commit 83d9b3f95cdb43e76953c77f03d2700e978dde8d
    Author: Munehiro "Muuhh" Ikeda <m_ikeda@hogeraccho.com>
    Date:   Sat Apr 2 13:18:07 2011 -0700
    
    Initial commit

    紧接着commit后面显示的,是仅指示该提交的散列(hash)值。Author描述的是提交者的信息。事先使用git config设置提交者的信息,就是为了将这些作为提交信息记录下来。可以发现,每次提交的最后一行显示的都是提交时输入的提交信息。
    git log默认输出所有的提交记录,但也可以用如表1-11所示的命令行参数来指定提交的散列值,限制输出范围。
    表1-11 指定git log的提交范围(绝对散列值)
    image

    散列值不需要完整输入,只需输入一定长度使其仅指示这次提交(但最少要输入4个字)。
    最新提交可以用HEAD这一别名进行参照。另外还可以将从HEAD开始的相对位置指定为HEAD~2等。使用这一方法还可以进行如表1-12所示的指定。
    表1-12 指定git log的提交范围(相对于HEAD)
    image

    在Git的其他子命令下指定提交范围的方法也是相同的。使用git diff等也可以输出指定范围的差别。
    如果为git log指定文件,则仅输出与该文件有关的提交。还可以使用-p选项将提交后的变更内容以段落形式显示出来。当前仓库显示的内容如下。

    $ git log -p goodbye.c
    commit 9b670c34bd7bd772648a99738017802f2b24f859
    Author: Munehiro "Muuhh" Ikeda <m_ikeda@hogeraccho.com>
    Date:   Sat Apr 2 14:09:39 2011 -0700
    
        Add goodbye.c
    
    diff --git a/goodbye.c b/goodbye.c
    new file mode 100644
    index 0000000..13f79ea
    --- /dev/null
    +++ b/goodbye.c
    @@ -0,0 +1,9 @@
    +/* goodbye.c */
    +#include <stdio.h>
    +
    +int main(void)
    +{
    +    printf("Goodbye world!!
    ");
    +    return 0;
    +}
    +
    

    修改提交
    在进行提交后,有时也会想要对已经提交的内容进行修改。修改方法大致可以分为两种。
    第一种方法是进行新的提交来取消某个提交。在这种情况下,原先的提交和后来为了取消它而进行的提交都会保留记录。要取消当前仓库的最新提交时,进行如下操作。

    $ git revert HEAD
    

    提交信息可以根据个人喜好进行修改,这里就不作修改,直接以默认内容保存,并关闭编辑器。goodbye.c从工作区被删除,hello.c的内容也回到前一次提交的状态(返回值为0)。使用git log -p来确认提交的内容,可以发现使用git revert进行的最新提交(提交信息Revert "Add goodbye.c")与前一次提交(提交信息Add goodbye.c)的变更是完全相反的。
    第二种方法是直接对提交进行修改。直接修改提交也有三种作法,得出的结果在细节上有一些不同。
    直接修改提交的第一种作法,适用于对最新提交进行较小修改的情况。假设在刚才的git revert中,想保留hello.c的返回值1,不作修改,并在提交信息中记录下来。在这种情况下需要进行如下操作。

    $ vi hello.c
    

    (将return 0;重新修改为return 1;)

    $ git add hello.c
    $ git commit - -amend
    

    将提交信息修改为Revert "Add goodbye.c" except for return value,保存并关闭编辑器。
    再用git log -p来确认提交信息与变更内容,可以发现返回值的更改从最新提交中被删除,提交信息也发生了改变。
    直接修改提交的第二种作法,就是取消提交。假设想要取消最新提交,也就是将记录恢复到最新提交前一次的提交(HEAD~1),但是希望工作区的源代码维持原状。这时应执行下列命令。

    $ git reset - -soft HEAD~1
    

    然后用git log查看记录,可以发现Revert...的提交已经消失了。但是由于并没有对工作区作出修改,因此原本通过当前最新提交的Add goodbye.c应当添加的文件goodbye.c不存在。
    最后一种作法,是在恢复记录的同时,将工作区也恢复到相应的状态。可以执行下列命令:

    $ git reset - -hard HEAD
    

    由于工作区也已经回到了当前的HEAD(即Add goodbye.c),因此goodbye.c恢复。目前记录、工作区都已经完全恢复到提交Add goodbye.c后的状态。
    小贴士:对提交记录进行修改时请慎重。特别需要注意的是,这个方法如果用在被其他仓库参照的仓库中,会出现相互之间记录不兼容的问题,因此不能在此情况下使用。
    为提交加标签
    可以为每次提交加上“标签”。为发布版本等关键的提交加上标签,以后就可以使用标签名称来参照本次提交,十分方便。
    假设将HEAD~1的提交Correct misspelling发布为版本1,然后尝试为ver1加上标签。

    $ git tag ver1 HEAD~1
    

    可以使用下列命令来显示仓库内的标签列表。

    $ git tag -1
    

    创建分支
    想要在保留当前开发系统的同时进行其他系统的开发,就需要创建“分支”。下面以刚才加了标签ver1的提交为起点,创建名为ver1x的分支。ver1x是针对下一版本的开发分支。

    $ git branch ver1x ver1
    

    仓库的分支列表可以用下列命令来显示。

    $ git branch
    * master
     ver1x
    

    如图1-5所示,从输出的内容可以看到,新的分支ver1x已经创建。前面有*的是当前工作区需要的分支(当前分支)。然后,将当前分支更改为ver1x。

    $ git checkout ver1x
    Switched to branch 'ver1x'
    $ git branch
     master
    * ver1x
    

    image
    图1-5 分支的创建
    在ver1x分支中创建新的文件thanks.c,并提交。

    /* thanks.c */
    #include <stdio.h>
    
    int main(void)
    {
        printf("Thank you guys!!
    ");
        return 0;
    }
    
    $ git add thanks.c
    $ git commit
    

    将ver1x:Add thanks.c作为提交信息。
    使用git log查看记录,可以发现,master分支里的Add goodbye.c在分支ver1x内是无效的,提交Add thanks.c的祖先是分支ver1x的起点,即提交Correct misspelling。像这样创建分支,就可以在同一仓库内独立地进行其他系统的开发。
    rebase命令
    开发版必须一直在最新发行版的基础上进行开发。例如,当发行版安装新功能时,必须将其也安装到开发版中。在当前的仓库中,goodbye.c就相当于新安装的功能。这时就需要将开发分支ver1x的起点移动到发行版的最新提交中。这种分支起点的移动称为复位基底(rebase),如图1-6所示。想要将当前分支复位基底到分支master的最新提交,需要执行下列命令。由于现在的当前分支为ver1x,因此这条命令复位基底的是ver1x。
    $ git rebase master
    小贴士:上例中是rebase到master的最新提交,但可以使用--onto选项来指定要rebase到的任意提交。默认为所指定分支(上例中为master)的最新提交。

    image

    图1-6 分支的rebase
    合并分支
    为了合并发行版master分支与开发版分支ver1x中各自进行修改与开发的情况,就需要对文件进行修改。
    首先在目前需要的ver1x分支中进行修改。通过下列命令来修改goodbye.c的注释。

    $ git branch
    master
    * ver1x
    $ vi goodbye.c
    (将/* goodbye.c */修改为/* goodbye.c : needed? */)
    $ git commit -a
    

    将提交信息写为ver1x: Modify comment in goodbye.c。然后移动到master分支,在这边也对goodbye.c进行修改。

    $ git checkout master
    $ vi goodbye.c
    (将/* goodbye.c */修改为/* goodbye.c : yes, needed! */
    将return 0; 修改为return 1;)
    $ git commit -a
    

    将提交信息写为Modify comment and return value of goodbye.c。
    到此为止,ver1x分支下的开发就基本完成了,假设即将将其作为版本2进行发布。在这种情况下,需要将分支ver1x合并到分支master中,将ver1x下的开发成果整合到发行版中(见图1-7)。将当前分支作为master执行下列命令。
    image

    图1-7 分支的合并

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

    这时提示由于goodbye.c中发生了冲突而无法合并。这是由于在两个分支下都对goodbye.c进行了修改。发生冲突的文件可以使用git status命令,显示为unmerged。

    $ git status
    goodbye.c: needs merge
    # On branch master
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #    new file:   thanks.c
    #
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #    unmerged:   goodbye.c
    #
    

    再查看一下goodbye.c的内容。

    <<<<<<< HEAD:goodbye.c
    /* goodbye.c : yes, needed! */
    =======
    /* goodbye.c : needed? */
    >>>>>>> ver1x:goodbye.c
    #include <stdio.h>
    
    int main(void)
    {
        printf("Goodbye world!!
    ");
        return 1;
    }
    

    发生冲突的部分是用冲突标记<<<<<<<和>>>>>>>显示的。必须人工决定选择其中的哪一个。这里选择采用master分支下的修改,即yes,needed!。
    将goodbye.c修改为下列内容。

    /* goodbye.c : yes, needed! */
    #include <stdio.h>
    
    int main(void)
    {
        printf("Goodbye world!!
    ");
        return 1;
    }
    

    使用git add通知Git修改已结束,并进行提交。

    $ git add goodbye.c
    $ git commit
    

    到这一步,两个分支的合并就结束了。使用git log确认记录,可以发现进行合并后,在ver1x分支中进行的ver1x: Add thanks.c等修改在master分支中也体现出来。冲突的解决也作为一个提交记录下来。
    然后加上标签,以便将这个状态作为版本2进行参照。

    $ git tag ver2
    

    小贴士:即使在两个分支下对相同文件进行了修改,如果是针对不同行进行的修改,Git也会自动将这些修改合并。上例就在master分支下修改了goodbye.c的返回值,这个部分也由Git自动进行了合并。
    参照图形记录
    对分支进行合并后,提交之间的从属关系变得复杂,比较难把握。这时可以使用git log --graph命令,在文字界面上将从属关系以图形显示出来。
    除此以外,也可以使用gitk数据包里所含的基于图形界面的工具gitk。图1-8所示为gitk的界面。
    image

    图1-8 gitk
    提取补丁
    想要根据从版本1到版本2的各次提交的差别提取补丁文件,可以执行下列命令。

    $ git format-patch ver1..ver2
    

    当前目录下就会生成0001-Add-goodbye.c.patch等补丁文件。
    在这里使用标签名称指定了补丁的起点和终点,除此以外,还可以指定提交的散列值与分支名。
    提取源码树
    执行下列命令,可以将版本2的源代码作为tar文件提取出来。

    $ git archive -- format=tar - -prefix="hello-v2/" ver2 > ../hello-v2.tar
    

    根目录下就会生成名为hello-v2.tar的tar文件。
    小贴士:当各文件包含到tar文件中时,文件名前面会加上使用--prefix选项指定的文字。这只是单纯地添加了文字,因此,当指定tar文件内的根目录名时,要记得如上例中的“hello-v2/”这样在文字的最后加上“/”。
    小贴士:.git目录不会包含到tar文件中,因此即使将这里生成的tar文件解压缩,也不能发挥Git仓库的功能。
    相反的,如果将Git仓库的目录连同.git目录在内全部复制,就能够发挥与原来的仓库完全相同的功能。从这一点也可以看出Git完全是分布式仓库。
    与远程仓库进行共同作业
    本地仓库的操作已经基本掌握,下面就将介绍与远程仓库进行共同作业的方法。
    这里将按照一般开发者进行Linux内核上游开发时的流程来说明。大致流程如下。
    将上游的仓库复制到本地。
    不断追踪上游仓库的最新状态,同时在本地仓库进行开发。
    以补丁的形式将开发成果提交维护人员及开发邮件列表。
    复制仓库
    当进行上游开发时,首先要复制各维护人员所管理的开发仓库(远程仓库),建立本地仓库。在Git中将这一步称为“复制”。 复制是通过git clone命令来进行的。例如,复制Linus树的仓库的命令为:

    $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
    

    复制Linus树时要下载1GB以上的数据,因此需要花费很长时间,仅在必要时再进行这个操作。这里将之前生成的hello仓库当做远程仓库,将其复制到其他位置。

    $ cd
    $ git clone hello local
    

    生成local目录后,里面包含的文件就与hello目录完全相同。进入local目录,使用git log确认记录,就可以发现至今为止的记录已完全复制过来。
    建立本地分支
    请在本地仓库执行git branch命令。在这一阶段只有master分支。这个master分支是在远程仓库(即hello仓库)的master分支关联的基础上,在本地仓库生成的分支。关联,就是指此后与远程仓库进行同步时,hello仓库在master分支下所作的改动会合并到这个分支。因此,如果在master分支中进行开发,进行同步时两个仓库所作的更改就有可能发生冲突。为了避免发生这种情况,就要事先从master分支中分出一个用于在本地进行开发的分支work。

    $ git checkout -b work
    

    git checkout -b命令将在创建分支的同时进行检查(针对当前分支的最新修改)。
    追踪分支
    为了便于说明,上文的描述比较简单,可能会让人认为本地仓库的master分支是hello仓库master分支的副本,而其实并不是这样。hello仓库的master分支,在本地仓库是以origin/master的标题出现的。这个分支才完全是hello仓库master分支的副本,这种分支称为“追踪分支”。在使用git pull对仓库进行同步时,首先同步的就是这个追踪分支。然后,把追踪分支的提交合并到追踪分支所关联的本地分支中。
    通过执行git branch -r命令,可以显示追踪分支的列表。下方显示的就是在本地仓库中执行这一命令的输出结果(见图1-9)。

    $ git branch -r
      origin/HEAD -> origin/master
      origin/master
      origin/ver1x
    

    远程仓库的相关信息可以使用git remote show命令来确认。虽然可以设置多个远程仓库,但仅设置一个时其默认名称为origin,因此执行该命令时可以指定origin。
    image

    图1-9 本地分支的建立

    $ git remote show origin
    * remote origin
      URL: /home/munehiro/hello
      Remote branch merged with 'git pull' while on branch master
        master
      Tracked remote branches
        master
        ver1x
    

    这里的输出具有下列含义。
    远程仓库origin的URL:/home/munehiro/hello
    在本地分支master上执行git pull时合并的远程分支:master
    追踪分支:master,ver1x
    这些信息是通过.git/config文件设置的。相关各部分的内容(section)如下所示。[remote "origin"]部分规定了远程仓库的URL、远程仓库上的分支、追踪分支之间的关系。[branch "master"]部分规定了合并到本地分支master的远程分支为origin仓库(即hello仓库)的master分支。

    $ cat .git/config
    ...
    [remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = /home/munehiro/hello
    [branch "master"]
        remote = origin
        merge = refs/heads/master
    ...
    

    追踪分支是为了追踪远程仓库而存在的,因此不能在这个分支上进行本地修改(从技术上是可以的,但并不推荐)。
    以追踪分支为起点建立本地分支后,本地分支就被追踪分支关联。例如,可以通过下列命令,建立与master分支的追踪分支相关联的本地分支master2。

    $ git branch master2 origin/master
    

    本地分支master及master2虽然被关联,但二者完全是本地分支。因此也可以直接在上面进行本地开发。但是,由于需要通过git pull进行合并,因此如果发生了冲突,就必须在这时候解决。
    与远程仓库同步
    想要看到在远程仓库上不断进行的开发,可以在hello仓库的master分支下对thanks.c进行如下修改并提交(将负(–)的行改为正(+)的行)。

    -    return 0;
    +    return 2;
    
    $ git commit -a
    

    将Modify return value of thanks.c into 2作为提交信息。
    使用git pull命令可以让本地仓库与远程仓库的最新状态保持同步。在本地仓库执行下列命令后,在hello仓库的master分支下进行的修改就会全部整合到本地仓库的master分支。

    $ git checkout master
    $ git pull
    

    这时使用git log查看记录,可以发现hello仓库的提交Modify return value of thanks.c into 2已经整合并完成同步。
    将开发分支rebase到最新状态
    在本地仓库的work分支下不断进行本地开发。将当前分支设置为work后,对thanks.c进行如下的修改并提交。

    $ git checkout work
    
    -       printf("Thank you guys!!
    ");
    -       return 0;
    +       printf("Thank you so much guys!!
    ");
    +       return 1;
    
    $ git commit -a
    

    在此以前,提交信息都只有1行,而这次需要输入多行,如下所示。第2行只需另起一空行。

    Modify message thanks.c
    
    I really appreciate your efforts.
    

    另外,本地开发成果必须基于最新版的远程仓库(上游仓库)。当前的work分支是以版本2为起点的,而这已经不是最新版。处于最新状态的是刚才进行了同步的本地仓库的master分支。因此,如图1-10所示,只需要将work分支复位基底到master分支的HEAD。
    thanks.c内发生了冲突,可以按照与上文所述“合并分支”同样的方法来消除冲突。为了保留上游的修改,并加入自己的开发成果,需要对thanks.c进行如下修改。

    $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: Modify thanks.c
    Using index info to reconstruct a base tree...
    Falling back to patching base and 3-way merge...
    Auto-merging thanks.c
    CONFLICT (content): Merge conflict in thanks.c
    Failed to merge in the changes.
    Patch failed at 0001 Modify thanks.c
    
    When you have resolved this problem run "git rebase --continue".
    If you would prefer to skip this patch, instead run "git rebase --skip".
    To restore the original branch and stop rebasing run "git rebase --abort".
    

    image

    图1-10 本地开发分支的复位基底
    例1-1 thanks.c的冲突标记

    <<<<<<< HEAD
            printf("Thank you guys!!n");
            return 2;
    =======
            printf("Thank you so much guys!!n");
            return 1;
    >>>>>>> Modify thanks.c
    

    例1-2 thanks.c的修改结果

    printf("Thank you so much guys!!
    ");
            return 2;
    

    因为发生冲突而中断的复位基底,在消除冲突并对文件执行git add命令后,再执行git rebase--continue就会继续。

    $ git add thanks.c
    $ git rebase --continue
    

    这样就成功地复位基底到最新版了。这时也可以使用git log确认记录。
    用邮件将补丁发送给维护人员
    现在,本地仓库的开发终于完成了。Linux内核开发的流程是先以补丁的形式将开发成果发送到邮件列表,经过评估与讨论后再整合到上游的仓库内。可以使用git format-patch将补丁输出为文件,粘贴到平时使用的电子邮件的正文并发送,而Git也备有直接发送邮件的功能—git send-email命令。这里使用这条命令来发送邮件。
    另外,在笔者所使用的环境(Ubuntu 10.04、Fedora14)中,git send-mail命令原来是放在与Git不同的git-email数据包里的,因此需要事先下载。
    小贴士:通过Git直接发送邮件,就可以避免电子邮件软件出现的换行符等问题。
    要将本地仓库的开发成果,即以master分支为起点的work分支的各次提交(目前只有一个),作为补丁以邮件发送,可以执行下列命令。另外,如果事先在配置文件中设置各种选项,就不需要每次都在命令行进行输入。表1-13所示为与选项对应的配置文件的段落名。

    $ git send-email --to=hello_maintainer@hogeraccho.com --cc=hello-ml@hogeraccho.com --smtp-server=smtp.googlemail.com --smtp-encryption=ssl --smtp-server-port=465 --smtp-user=youruser@gmail.com --smtp-pass=yourpw master..work
    

    表1-13 git send-email的选项
    image

    将--to、--cc等改成自己的地址,并尝试发送邮件,应当会收到如下列内容的邮件。

    From: Munehiro Muuhh Ikeda <m_ikeda@hogeraccho.com>
    To: you@your.domain.co.jp
    Cc: Munehiro Muuhh Ikeda <m_ikeda@hogeraccho.com>
    Subject: [PATCH] Modify message thanks.c
    
    I really appreciate your efforts.
    ---
     thanks.c |    2 +-
     1 files changed, 1 insertions(+), 1 deletions(-)
    
    diff --git a/thanks.c b/thanks.c
    index c28de46..806371d 100644
    --- a/thanks.c
    +++ b/thanks.c
    @@ -3,7 +3,7 @@
     
     int main(void)
     {
    -    printf("Thank you guys!!
    ");
    +    printf("Thank you so much guys!!
    ");
         return 2;
     }
     
    --
    

    1.7.1
    提交信息的第1行是邮件标题,从空行之后(即第3行开始)是邮件的正文。
    git send-email有非常多的选项,可以进行各种设置。建议浏览帮助页面(man page),进行各种尝试,最后生成最佳的配置文件。
    其他有用的命令
    除上面介绍的以外,还有很多其他有用的命令。表1-14简单整理了其中的一部分,详细内容请参考git的帮助页面。
    表1-14 其他命令
    image

    小结
    当进行Linux内核的上游开发时,Git可以说是必需的工具。Git具有优秀的功能与速度,在多个开发人员参与的项目中可以成为非常有效的工具,并且不仅限于Linux内核的开发。一直以来使用单一仓库型SCM的人可以体验分布式仓库型SCM的高度可扩展性。当然,对于初次使用SCM的人来说也一定会是很好用的工具。

  • 相关阅读:
    LeetCode 242. Valid Anagram (验证变位词)
    LeetCode 205. Isomorphic Strings (同构字符串)
    LeetCode 204. Count Primes (质数的个数)
    LeetCode 202. Happy Number (快乐数字)
    LeetCode 170. Two Sum III
    LeetCode 136. Single Number (落单的数)
    LeetCode 697. Degree of an Array (数组的度)
    LeetCode 695. Max Area of Island (岛的最大区域)
    Spark中的键值对操作
    各种排序算法总结
  • 原文地址:https://www.cnblogs.com/tcicy/p/8552655.html
Copyright © 2011-2022 走看看