《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #4 如何使用Git

    xiaoxiao2021-04-17  224

    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!!\n"); 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!!\n"); 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!!\n"); - 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!!\n"); + 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!!\n"); - 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的提交范围(绝对散列值)

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

    在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!!\n"); + 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

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

    /* thanks.c */ #include <stdio.h> int main(void) { printf("Thank you guys!!\n"); 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)的最新提交。

    图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执行下列命令。

    图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!!\n"); return 1; }

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

    /* goodbye.c : yes, needed! */ #include <stdio.h> int main(void) { printf("Goodbye world!!\n"); 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的界面。

    图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。

    图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!!\n"); - return 0; + printf("Thank you so much guys!!\n"); + 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".

    图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!!\n"); 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的选项

    将--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!!\n"); + printf("Thank you so much guys!!\n"); return 2; } --

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

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

    相关资源:新年快乐! python实现绚烂的烟花绽放效果

    最新回复(0)