git am / format-patch / cherry-pick

来源:互联网 发布:崩坏学园2官方淘宝店 编辑:程序博客网 时间:2024/06/04 23:24

介绍:

    UNIX世界的软件开发大多都是协作式的,因此,Patch(补丁)是一个相当重要的东西,因为几乎所有的大型UNIX项目的普通贡献者,都是通过 Patch来提交代码的。作为最重要的开源项目之一,Linux,也是这样的。普通开发者从软件仓库clone下代码,然后写入代码,做一个Patch, 最后用E-mail发给Linux Kernel的维护者就好了。Git最初作为Linux的版本控制工具,提供了透明、完整、稳定的Patch功能。

    我们先介绍一下Patch是什么。如果一个软件有了新版本,我们可以完整地下载新版本的代码进行编译安装。然而,像Linux Kernel这样的大型项目,代码即使压缩,也超过70MB,每次全新下载是有相当大的代价的。然而,每次更新变动的代码可能不超过1MB,因此,我们只要能够有两个版本代码的diff的数据,应该就可以以极低的代价更新程序了。因此,Larry Wall开发了一个工具:patch。它可以根据一个diff文件进行版本更新。

    不过在git中,我们没有必要直接使用diff和patch来做补丁,这样做既危险又麻烦。git提供了两种简单的patch方案。一是用git diff生成的标准patch,二是git format-patch生成的Git专用Patch。

diff 和format-patch的比较:

  • 兼容性:很明显,git diff生成的Patch兼容性强。如果你在修改的代码的官方版本库不是Git管理的版本库,那么你必须使用git diff生成的patch才能让你的代码被项目的维护人接受。
  • 除错功能:对于git diff生成的patch,你可以用git apply --check 查看补丁是否能够干净顺利地应用到当前分支中;如果git format-patch 生成的补丁不能打到当前分支,git am会给出提示,并协助你完成打补丁工作,你也可以使用git am -3进行三方合并,详细的做法可以参考git手册或者《Progit》。从这一点上看,两者除错功能都很强。
  • 版本库信息:由于git format-patch生成的补丁中含有这个补丁开发者的名字,因此在应用补丁时,这个名字会被记录进版本库,显然,这样做是恰当的。因此,目前使用Git的开源社区往往建议大家使用format-patch生成补丁。

在使用git-am之前, 你要首先git am –abort 一次,来放弃掉以前的am信息,这样才可以进行一次全新的am,不然会遇到这样的错误。
.git/rebase-apply still exists but mbox given.

git-am 可以一次合并一个文件,或者一个目录下所有的patch,或者你的邮箱目录下的patch.

下面举两个例子:

  1. 你现在有一个code base: small-src, 你的patch文件放在~/patch/0001-trival-patch.patch
cd small-srcgit-am ~/patch/0001-trival-patch.patch

如果成功patch上去, 你就可以去喝杯茶了。

如果失败了, git 会提示错误, 比如:

error: patch failed: android/mediascanner.cpp:452error: android/mediascanner.cpp: patch does not apply

这样你就需要先看看patch, 然后改改错误的这个文件,让这个patch能够patch上去。

  1. 你有一堆patch, 名字是上面提到的那一堆patch, 你把他们放在~/patch-set/目录下(路径随意)
cd opencoregit am ~/patch-set/*.patch

(这里git就会按照文件名的顺序一次am这些patch)
如果一切顺利, 你所有的patch都OK了, 你又Lucky了。

不过不顺利的时候十有八九,如果git am中间遇到了patch,am就会停到打这个
patch的地方, 告诉你是哪个patch打不上去。

比如我现在有一个文件file,有两个patch.
file 的内容是

the textmore text

两个patch分别是:

0001-add-line.patch:

From 48869ccbced494e05738090afa5a54f2a261df0f Mon Sep 17 00:00:00 2001From: zhangjiejing <zhangjiejing@zhangjiejing-desktop.(none)>Date: Thu, 22 Apr 2010 13:04:34 +0800Subject: [PATCH 1/2] add line--- file |    2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)diff --git a/file b/fileindex 067780e..685f0fa 100644--- a/file+++ b/file@@ -3,3 +3,5 @@ file: some text more text++add line--1.6.3.3

0002-change-line.patch:

From f756e1b3a87c216b7e0afea9d15badd033171578 Mon Sep 17 00:00:00 2001From: zhangjiejing <zhangjiejing@zhangjiejing-desktop.(none)>Date: Thu, 22 Apr 2010 13:05:19 +0800Subject: [PATCH 2/2] change line--- file |    2 +- 1 files changed, 1 insertions(+), 1 deletions(-)diff --git a/file b/fileindex 685f0fa..7af7852 100644--- a/file+++ b/file@@ -1,6 +1,6 @@ file:-some text+Change line text more text--1.6.3.3

运行
git am *.patch

来merge这些patch, 报错, Patch failed at 0001 add line这样我们看0001这
个patch,原来patch需要的是some text, 而file里面是the text, 所以我们用编
辑器把这行改成some text,

vi filegit apply 0001-add-line.patchgit add filegit am --resolved

在解决完冲突以后, 比如用git add来让git知道你已经解决完冲突了。

  • 如果你发现这个冲突是无法解决的, 要撤销整个am的东西。 可以运行git am –abort,
  • 如果你想只是忽略这一个patch,可以运行git am –skip来跳过这个patch.

使用git format-patch生成所需要的patch:

# git format-patch -s 1bbe3c8c197a35f79bfddaba099270a2e54ea9c7

please replace the hash code with your repo previous commit.

then you can find the patch under repo directory.

then mail your patch to configuration admin. 


# git format-patch -M master         // 当前分支所有超前master的提交

# git format-patch -s 4e16                // 某次提交以后的所有patch, --4e16指的是SHA1 ID, -s : signed off

# git format-patch -1                     //  单次提交
# git format-patch -3                    // 从master往前3个提交的内容,可修改为你想要的数值
# git format-patch –n 07fe            // -n指patch数,07fe对应提交的名称, 某次提交(含)之前的几次提交
# git format-patch -1 commit-id// + commit-id 选定的那个commit打patch

git format-patch -s --root origin     // 从origin到指定提交的所有patch

应用patch:
先检查patch文件:# git apply --stat newpatch.patch
检查能否应用成功:# git apply --check  newpatch.patch
打补丁:# git am --signoff < newpatch.patch

(使用-s或--signoff选项,可以commit信息中加入Signed-off-by信息)


今天发现有个需求,就是我提交了错误的提交,如何再切换到先前的版本,但是并没有建立branch的时候,

原来用git checkout 就可以了,

# git checkout 7a1eba1f6d   // SHA1 ID 这种情况只是切换,但是不做任何处理,如果需要反转就用

# git revert

如果需要复位就用

# git reset SHA1 ID

git revert还原最近一次的修改,但版本commit会向前推进,只是创建一个相反的内容,再次提交
git revert commit-id还原指定版本的修改
git reset --hard commit-id   # 整個 Repository 恢復到某個版本的狀態
git reset HEAD^
git reset HEAD~1        撤销最后一次提交,默认模式-mixed,回退commit与index,
git reset --soft 回退commit,但不回退index
git reset --hard HEAD^  撤销最后一次提交并清除本地修改。
git reset commit-id          回到commit-id对应的提交状态。

git rebase -i HEAD~*     // 用来修改commit的内容,可以删除或者修改名字或者移动位置或者合并,然后ctrl+o,ctrl+x保存退出。

  # 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

git log -p test.c          // 查看这个文件提交的记录,及内容的修改

git log test.c              // 仅查看提交的记录

git log --oneline -2    // 显示一行,显示最近的两次提交

gitlog --pretty=short

# git config --global alias.lg "log --graph"

# git config -l
alias.lg=log --graph --pretty=format:'%Cred%h%Creset: %s %Cgreen(%cr)%Creset %C(yellow)%d%Creset' --abbrev-commit --date=short
Explain:

%Cred : 显示红色

%h : 简短的commit id

%Creset : 颜色复位成常规的颜色

%s : 显示提交的内容标题

%cr : 显示提交的日期,这个按照比今天前多少天

%Cyellow : 显示黄色

%d : ref name


git add . 

git commit --amend  // 追加到前一次提交,这个类似于 git rebase -i HEAD~2

git show        // 显示最近的一次提交的内容

git show --name-only   // 只显示修改的文件名,而不具体显示修改的行的内容

git grep -n -w  “searched name”   // git 提供的比较方便的搜索方式, -w 只搜索全匹配的字符串,适合于搜索函数名

git am -3 *.patch

git am --ignore-space-change -3 xxx.patch "忽略空格的改变,这个可以忽略windows和linux换行符的差异"

git reset commit-id // 复位到指定的commit,但是会把修改的内容保留在目录下,后面可以修改并继续提交

git blame -L 40,60 test.c      // 责备40行 - 60行
git blame -L 40,+21 test.c    // 责备第40行,及后面21行

git blame --since=3.weeks -- test.c

git am 用了git apply,用它打补丁会生成commit信息。如果出现以下错误:
previous rebase directory ../.git/rebase-apply still exists but mbox given
可以用# git am --abort

丢弃某个commit:

# git revert commit-id -n, --no-commit

git cherry-pick. 如何把已经提交的commit, 从一个分支放到另一个分支

实际问题: 
  在本地 master 分支上做了一个commit ( 38361a68138140827b31b72f8bbfd88b3705d77a ) , 如何把它放到 本地 old_cc 分支上? 
办法之一: 使用 cherry-pick.  根据git 文档:

Apply the changes introduced by some existing commits 

就是对已经存在的commit 进行apply (可以理解为再次提交)
简单用法:

git cherry-pick <commit id>

例如:
$ git checkout old_cc
$ git cherry-pick 38361a68     # 这个 38361a68 号码,位于:

$ git log 
commit 38361a68138140827b31b72f8bbfd88b3705d77a 
Author: Siwei Shen <siwei.shen@focusbeijing.com>
Date:   Sat Dec 10 00:09:44 2011 +0800


1. 如果顺利,就会正常提交。结果:

Finished one cherry-pick.
# On branch old_cc
# Your branch is ahead of 'origin/old_cc' by 3 commits.


2. 如果在cherry-pick 的过程中出现了冲突

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result with: 

        git commit -c 15a2b6c61927e5aed6718de89ad9dafba939a90b


就跟普通的冲突一样,手工解决:
2.1 $ git status    # 看哪些文件出现冲突

both modified:      app/models/user.rb 


2.2 $ vim app/models/user.rb  # 手动解决它。 
2.3 $ git add app/models/user.rb
2.4 git commit -c <新的commit号码>