요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
Conflict 원인 commit부터 찾는다
backporting.rst:86-152Conflict는 patch가 기대한 변경 줄과 주변 context가 destination tree와 다를 때 발생한다. Backport source에는 있고 target에는 없는 refactoring이나 prerequisite가 일반적인 원인이지만, target에만 별도 backport가 있어 branch가 반대로 달라졌을 수도 있다.
Marker를 지우고 compile만 되게 만드는 것으로는 충분하지 않다. 차이를 만든 commit을 찾고 그 changelog를 읽어야 semantic dependency를 놓치지 않는다. 익숙하지 않은 subsystem에서는 이 이력이 conflict resolution의 설계 문서 역할을 한다.
git log와 git blame으로 prerequisite 추적
backporting.rst:154-221git log HEAD..<commit>^ -- <path>
git log -L:'\<function\>':<path> HEAD..<commit>^
git log -G'regex' HEAD..<commit>^ -- <path>
git blame <commit>^ -- <path>
git blame -L:'\<function\>' <commit>^ -- <path>
git log range는 destination HEAD와 picked commit의 parent 사이에서 해당 file을 바꾼 commit을 보여 준다. -L은 function history를, -G는 diff에서 특정 symbol·assignment가 추가 또는 제거된 commit을 찾는다.
git blame은 picked commit 직전 version에서 conflict 줄을 마지막으로 만든 commit을 찾는다. 같은 줄이 여러 번 바뀌었다면 더 이전 revision을 대상으로 blame을 반복하고 각 candidate를 git show로 읽는다.
필수 patch와 우연히 겹친 patch 구분
backporting.rst:223-245Whitespace 정리나 symbol rename처럼 같은 줄을 건드렸지만 의미를 바꾸지 않은 patch는 incidental change이므로 target code에 맞춰 수동으로 흡수할 수 있다. 반면 fix가 수정하는 function 자체가 target에 없다면 그 function을 도입한 commit이 실제 prerequisite인지 검토해야 한다.
필수 prerequisite를 찾았으면 현재 작업을 git cherry-pick --abort로 중단하고 prerequisite부터 옮긴다. 이미 해결한 다른 file이 있다면 임시 copy로 보존할 수 있지만 최종 history는 dependency 순서가 명확해야 한다.
Combined diff, diff3와 zdiff3
backporting.rst:247-344Default merge marker는 HEAD의 현재 tree와 picked commit 적용 후 내용을 두 부분으로 보여 준다. Conflict 중 plain git diff는 두 column marker를 쓰는 combined diff이며 ours→working tree와 ours→theirs 차이를 함께 표현한다.
git diff HEAD 또는 git diff --ours는 현재 branch와 resolution working tree만 비교해 일반 diff처럼 읽기 쉽다. 전체 branch 차이가 너무 많을 때 유용하다.
git config merge.conflictStyle diff3
<<<<<<< HEAD
현재 target code
||||||| parent of <commit>
upstream patch가 기대한 변경 전 code
=======
upstream patch 적용 후 code
>>>>>>> <commit>
Diff3는 target, upstream before, upstream after를 모두 보여 줘 original patch가 실제로 바꾼 의미를 구분할 수 있으므로 강하게 권장된다. Git 2.35의 zdiff3는 같은 세 영역에서 공통 줄을 잘라 marker를 더 작게 만든다.
Conflict resolution을 반복 검토하는 방법
backporting.rst:346-411각 hunk마다 '왜 이 줄이 original patch에 들어갔는가'를 답한 뒤 target code에서 같은 목적을 구현한다. Unrelated context가 큰 conflict는 marker를 모두 제거하고 target version에 최소 semantic change를 손으로 다시 적용하는 편이 명확할 수 있다.
복잡한 conflict는 git add 또는 git add -i로 확인된 부분을 단계적으로 stage한다. git diff HEAD는 남은 working resolution을, git diff --cached는 지금까지 완성된 backport를 보여 준다.
File rename을 Git이 감지하지 못하면 작은 변경은 새 경로에 직접 옮긴다. 큰 변경은 rename threshold를 낮춰 시도하거나 target에서 임시 rename commit을 만들고 cherry-pick한 뒤 원래 이름으로 돌려 마지막에 squash하는 방법이 있다. Rename patch 자체를 backport하는 것은 보통 첫 선택이 아니다.
Function argument, error path와 refactoring
backporting.rst:413-471- 비슷해 보이는 i와 j 같은 argument가 바뀌지 않았는지 call site를 문자 단위로 확인한다.
- 추가된 goto의 target label이 old branch에서도 같은 cleanup 순서를 의미하는지 확인한다.
- Return, break와 continue도 target branch의 control-flow에서 같은 의미인지 확인한다.
- git diff -W와 git show -W로 변경 function 전체를 표시해 hunk 밖 error path까지 검토한다.
- Upstream에서 여러 위치의 공통 code가 helper로 합쳐졌다면 helper 한 곳의 fix를 old tree 여러 위치에 각각 적용해야 할 수 있다.
- git grep으로 같은 bug pattern이 old branch의 다른 복제 code에도 남아 있는지 찾는다.
Backport 과정에서 upstream에도 같은 pattern이 여러 곳 남아 있음을 발견하면 original author에게 확인한다. 단순 conflict 해결이 새 bug 발견으로 이어지는 경우도 있다.
원본과 비교하고 build·runtime test한다
backporting.rst:473-550colordiff -yw -W 200 \
<(git diff -W <upstream-commit>^-) \
<(git diff -W HEAD^-) | less -SR
make path/to/file.o
Side-by-side colordiff는 whitespace를 제외하고 original fix와 backport의 다른 줄을 강조한다. 특히 goto label이나 변경되지 않은 주변 context 차이를 눈에 띄게 만든다. Single object build는 빠른 compiler check지만 linker error를 찾지 못하므로 full build가 뒤따라야 한다.
Conflict 없이 compile되고 boot된다고 dependency가 완전한 것은 아니다. 서로 다른 hunk에서 register save와 use가 분리된 assembly 변경처럼 text conflict가 없지만 runtime state를 손상하는 경우가 있다. Final patch를 새 patch와 같은 수준으로 review하고 unit·regression test를 실행해야 한다.
Stable backport 제출 형식
backporting.rst:552-594<original patch title>
[ Upstream commit <mainline rev> ]
<original changelog>
[ <conflict와 해결 방법 요약> ]
Signed-off-by: <name and email>
Subject prefix에는 PATCH 6.1.y처럼 대상 series를 표시한다. Active stable version마다 별도 patch를 보내고 각각 독립적으로 test한다. 결과에 대한 확신 수준을 솔직히 밝히고 관련 maintainer의 명시적 ack를 구한다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
===================================
Backporting and conflict resolution
===================================
:Author: Vegard Nossum <[email protected]>
.. contents::
:local:
:depth: 3
:backlinks: none
Introduction
============
Some developers may never really have to deal with backporting patches,
merging branches, or resolving conflicts in their day-to-day work, so
when a merge conflict does pop up, it can be daunting. Luckily,
resolving conflicts is a skill like any other, and there are many useful
techniques you can use to make the process smoother and increase your
confidence in the result.
This document aims to be a comprehensive, step-by-step guide to
backporting and conflict resolution.
Applying the patch to a tree
============================
Sometimes the patch you are backporting already exists as a git commit,
in which case you just cherry-pick it directly using
``git cherry-pick``. However, if the patch comes from an email, as it
often does for the Linux kernel, you will need to apply it to a tree
using ``git am``.
If you've ever used ``git am``, you probably already know that it is
quite picky about the patch applying perfectly to your source tree. In
fact, you've probably had nightmares about ``.rej`` files and trying to
edit the patch to make it apply.
It is strongly recommended to instead find an appropriate base version
where the patch applies cleanly and *then* cherry-pick it over to your
destination tree, as this will make git output conflict markers and let
you resolve conflicts with the help of git and any other conflict
resolution tools you might prefer to use. For example, if you want to
apply a patch that just arrived on LKML to an older stable kernel, you
can apply it to the most recent mainline kernel and then cherry-pick it
to your older stable branch.
It's generally better to use the exact same base as the one the patch
was generated from, but it doesn't really matter that much as long as it
applies cleanly and isn't too far from the original base. The only
problem with applying the patch to the "wrong" base is that it may pull
in more unrelated changes in the context of the diff when cherry-picking
it to the older branch.
A good reason to prefer ``git cherry-pick`` over ``git am`` is that git
knows the precise history of an existing commit, so it will know when
code has moved around and changed the line numbers; this in turn makes
it less likely to apply the patch to the wrong place (which can result
in silent mistakes or messy conflicts).
If you are using `b4`_. and you are applying the patch directly from an
email, you can use ``b4 am`` with the options ``-g``/``--guess-base``
and ``-3``/``--prep-3way`` to do some of this automatically (see the
`b4 presentation`_ for more information). However, the rest of this
article will assume that you are doing a plain ``git cherry-pick``.
.. _b4: https://people.kernel.org/monsieuricon/introducing-b4-and-patch-attestation
.. _b4 presentation: https://youtu.be/mF10hgVIx9o?t=2996
Once you have the patch in git, you can go ahead and cherry-pick it into
your source tree. Don't forget to cherry-pick with ``-x`` if you want a
written record of where the patch came from!
Note that if you are submitting a patch for stable, the format is
slightly different; the first line after the subject line needs to be
either::
commit <upstream commit> upstream
or::
[ Upstream commit <upstream commit> ]
Resolving conflicts
===================
Uh-oh; the cherry-pick failed with a vaguely threatening message::
CONFLICT (content): Merge conflict
What to do now?
In general, conflicts appear when the context of the patch (i.e., the
lines being changed and/or the lines surrounding the changes) doesn't
match what's in the tree you are trying to apply the patch *to*.
For backports, what likely happened was that the branch you are
backporting from contains patches not in the branch you are backporting
to. However, the reverse is also possible. In any case, the result is a
conflict that needs to be resolved.
If your attempted cherry-pick fails with a conflict, git automatically
edits the files to include so-called conflict markers showing you where
the conflict is and how the two branches have diverged. Resolving the
conflict typically means editing the end result in such a way that it
takes into account these other commits.
Resolving the conflict can be done either by hand in a regular text
editor or using a dedicated conflict resolution tool.
Many people prefer to use their regular text editor and edit the
conflict directly, as it may be easier to understand what you're doing
and to control the final result. There are definitely pros and cons to
each method, and sometimes there's value in using both.
We will not cover using dedicated merge tools here beyond providing some
pointers to various tools that you could use:
- `Emacs Ediff mode <https://www.emacswiki.org/emacs/EdiffMode>`__
- `vimdiff/gvimdiff <https://linux.die.net/man/1/vimdiff>`__
- `KDiff3 <http://kdiff3.sourceforge.net/>`__
- `TortoiseMerge <https://tortoisesvn.net/TortoiseMerge.html>`__
- `Meld <https://meldmerge.org/help/>`__
- `P4Merge <https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge>`__
- `Beyond Compare <https://www.scootersoftware.com/>`__
- `IntelliJ <https://www.jetbrains.com/help/idea/resolve-conflicts.html>`__
- `VSCode <https://code.visualstudio.com/docs/editor/versioncontrol>`__
To configure git to work with these, see ``git mergetool --help`` or
the official `git-mergetool documentation`_.
.. _git-mergetool documentation: https://git-scm.com/docs/git-mergetool
Prerequisite patches
--------------------
Most conflicts happen because the branch you are backporting to is
missing some patches compared to the branch you are backporting *from*.
In the more general case (such as merging two independent branches),
development could have happened on either branch, or the branches have
simply diverged -- perhaps your older branch had some other backports
applied to it that themselves needed conflict resolutions, causing a
divergence.
It's important to always identify the commit or commits that caused the
conflict, as otherwise you cannot be confident in the correctness of
your resolution. As an added bonus, especially if the patch is in an
area you're not that familiar with, the changelogs of these commits will
often give you the context to understand the code and potential problems
or pitfalls with your conflict resolution.
git log
~~~~~~~
A good first step is to look at ``git log`` for the file that has the
conflict -- this is usually sufficient when there aren't a lot of
patches to the file, but may get confusing if the file is big and
frequently patched. You should run ``git log`` on the range of commits
between your currently checked-out branch (``HEAD``) and the parent of
the patch you are picking (``<commit>``), i.e.::
git log HEAD..<commit>^ -- <path>
Even better, if you want to restrict this output to a single function
(because that's where the conflict appears), you can use the following
syntax::
git log -L:'\<function\>':<path> HEAD..<commit>^
.. note::
The ``\<`` and ``\>`` around the function name ensure that the
matches are anchored on a word boundary. This is important, as this
part is actually a regex and git only follows the first match, so
if you use ``-L:thread_stack:kernel/fork.c`` it may only give you
results for the function ``try_release_thread_stack_to_cache`` even
though there are many other functions in that file containing the
string ``thread_stack`` in their names.
Another useful option for ``git log`` is ``-G``, which allows you to
filter on certain strings appearing in the diffs of the commits you are
listing::
git log -G'regex' HEAD..<commit>^ -- <path>
This can also be a handy way to quickly find when something (e.g. a
function call or a variable) was changed, added, or removed. The search
string is a regular expression, which means you can potentially search
for more specific things like assignments to a specific struct member::
git log -G'\->index\>.*='
git blame
~~~~~~~~~
Another way to find prerequisite commits (albeit only the most recent
one for a given conflict) is to run ``git blame``. In this case, you
need to run it against the parent commit of the patch you are
cherry-picking and the file where the conflict appeared, i.e.::
git blame <commit>^ -- <path>
This command also accepts the ``-L`` argument (for restricting the
output to a single function), but in this case you specify the filename
at the end of the command as usual::
git blame -L:'\<function\>' <commit>^ -- <path>
Navigate to the place where the conflict occurred. The first column of
the blame output is the commit ID of the patch that added a given line
of code.
It might be a good idea to ``git show`` these commits and see if they
look like they might be the source of the conflict. Sometimes there will
be more than one of these commits, either because multiple commits
changed different lines of the same conflict area *or* because multiple
subsequent patches changed the same line (or lines) multiple times. In
the latter case, you may have to run ``git blame`` again and specify the
older version of the file to look at in order to dig further back in
the history of the file.
Prerequisite vs. incidental patches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Having found the patch that caused the conflict, you need to determine
whether it is a prerequisite for the patch you are backporting or
whether it is just incidental and can be skipped. An incidental patch
would be one that touches the same code as the patch you are
backporting, but does not change the semantics of the code in any
material way. For example, a whitespace cleanup patch is completely
incidental -- likewise, a patch that simply renames a function or a
variable would be incidental as well. On the other hand, if the function
being changed does not even exist in your current branch then this would
not be incidental at all and you need to carefully consider whether the
patch adding the function should be cherry-picked first.
If you find that there is a necessary prerequisite patch, then you need
to stop and cherry-pick that instead. If you've already resolved some
conflicts in a different file and don't want to do it again, you can
create a temporary copy of that file.
To abort the current cherry-pick, go ahead and run
``git cherry-pick --abort``, then restart the cherry-picking process
with the commit ID of the prerequisite patch instead.
Understanding conflict markers
------------------------------
Combined diffs
~~~~~~~~~~~~~~
Let's say you've decided against picking (or reverting) additional
patches and you just want to resolve the conflict. Git will have
inserted conflict markers into your file. Out of the box, this will look
something like::
<<<<<<< HEAD
this is what's in your current tree before cherry-picking
=======
this is what the patch wants it to be after cherry-picking
>>>>>>> <commit>... title
This is what you would see if you opened the file in your editor.
However, if you were to run ``git diff`` without any arguments, the
output would look something like this::
$ git diff
[...]
++<<<<<<<< HEAD
+this is what's in your current tree before cherry-picking
++========
+ this is what the patch wants it to be after cherry-picking
++>>>>>>>> <commit>... title
When you are resolving a conflict, the behavior of ``git diff`` differs
from its normal behavior. Notice the two columns of diff markers
instead of the usual one; this is a so-called "`combined diff`_", here
showing the 3-way diff (or diff-of-diffs) between
#. the current branch (before cherry-picking) and the current working
directory, and
#. the current branch (before cherry-picking) and the file as it looks
after the original patch has been applied.
.. _combined diff: https://git-scm.com/docs/diff-format#_combined_diff_format
Better diffs
~~~~~~~~~~~~
3-way combined diffs include all the other changes that happened to the
file between your current branch and the branch you are cherry-picking
from. While this is useful for spotting other changes that you need to
take into account, this also makes the output of ``git diff`` somewhat
intimidating and difficult to read. You may instead prefer to run
``git diff HEAD`` (or ``git diff --ours``) which shows only the diff
between the current branch before cherry-picking and the current working
directory. It looks like this::
$ git diff HEAD
[...]
+<<<<<<<< HEAD
this is what's in your current tree before cherry-picking
+========
+this is what the patch wants it to be after cherry-picking
+>>>>>>>> <commit>... title
As you can see, this reads just like any other diff and makes it clear
which lines are in the current branch and which lines are being added
because they are part of the merge conflict or the patch being
cherry-picked.
Merge styles and diff3
~~~~~~~~~~~~~~~~~~~~~~
The default conflict marker style shown above is known as the ``merge``
style. There is also another style available, known as the ``diff3``
style, which looks like this::
<<<<<<< HEAD
this is what is in your current tree before cherry-picking
||||||| parent of <commit> (title)
this is what the patch expected to find there
=======
this is what the patch wants it to be after being applied
>>>>>>> <commit> (title)
As you can see, this has 3 parts instead of 2, and includes what git
expected to find there but didn't. It is *highly recommended* to use
this conflict style as it makes it much clearer what the patch actually
changed; i.e., it allows you to compare the before-and-after versions
of the file for the commit you are cherry-picking. This allows you to
make better decisions about how to resolve the conflict.
To change conflict marker styles, you can use the following command::
git config merge.conflictStyle diff3
There is a third option, ``zdiff3``, introduced in `Git 2.35`_,
which has the same 3 sections as ``diff3``, but where common lines have
been trimmed off, making the conflict area smaller in some cases.
.. _Git 2.35: https://github.blog/2022-01-24-highlights-from-git-2-35/
Iterating on conflict resolutions
---------------------------------
The first step in any conflict resolution process is to understand the
patch you are backporting. For the Linux kernel this is especially
important, since an incorrect change can lead to the whole system
crashing -- or worse, an undetected security vulnerability.
Understanding the patch can be easy or difficult depending on the patch
itself, the changelog, and your familiarity with the code being changed.
However, a good question for every change (or every hunk of the patch)
might be: "Why is this hunk in the patch?" The answers to these
questions will inform your conflict resolution.
Resolution process
~~~~~~~~~~~~~~~~~~
Sometimes the easiest thing to do is to just remove all but the first
part of the conflict, leaving the file essentially unchanged, and apply
the changes by hand. Perhaps the patch is changing a function call
argument from ``0`` to ``1`` while a conflicting change added an
entirely new (and insignificant) parameter to the end of the parameter
list; in that case, it's easy enough to change the argument from ``0``
to ``1`` by hand and leave the rest of the arguments alone. This
technique of manually applying changes is mostly useful if the conflict
pulled in a lot of unrelated context that you don't really need to care
about.
For particularly nasty conflicts with many conflict markers, you can use
``git add`` or ``git add -i`` to selectively stage your resolutions to
get them out of the way; this also lets you use ``git diff HEAD`` to
always see what remains to be resolved or ``git diff --cached`` to see
what your patch looks like so far.
Dealing with file renames
~~~~~~~~~~~~~~~~~~~~~~~~~
One of the most annoying things that can happen while backporting a
patch is discovering that one of the files being patched has been
renamed, as that typically means git won't even put in conflict markers,
but will just throw up its hands and say (paraphrased): "Unmerged path!
You do the work..."
There are generally a few ways to deal with this. If the patch to the
renamed file is small, like a one-line change, the easiest thing is to
just go ahead and apply the change by hand and be done with it. On the
other hand, if the change is big or complicated, you definitely don't
want to do it by hand.
As a first pass, you can try something like this, which will lower the
rename detection threshold to 30% (by default, git uses 50%, meaning
that two files need to have at least 50% in common for it to consider
an add-delete pair to be a potential rename)::
git cherry-pick -strategy=recursive -Xrename-threshold=30
Sometimes the right thing to do will be to also backport the patch that
did the rename, but that's definitely not the most common case. Instead,
what you can do is to temporarily rename the file in the branch you're
backporting to (using ``git mv`` and committing the result), restart the
attempt to cherry-pick the patch, rename the file back (``git mv`` and
committing again), and finally squash the result using ``git rebase -i``
(see the `rebase tutorial`_) so it appears as a single commit when you
are done.
.. _rebase tutorial: https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec
Gotchas
-------
Function arguments
~~~~~~~~~~~~~~~~~~
Pay attention to changing function arguments! It's easy to gloss over
details and think that two lines are the same but actually they differ
in some small detail like which variable was passed as an argument
(especially if the two variables are both a single character that look
the same, like i and j).
Error handling
~~~~~~~~~~~~~~
If you cherry-pick a patch that includes a ``goto`` statement (typically
for error handling), it is absolutely imperative to double check that
the target label is still correct in the branch you are backporting to.
The same goes for added ``return``, ``break``, and ``continue``
statements.
Error handling is typically located at the bottom of the function, so it
may not be part of the conflict even though could have been changed by
other patches.
A good way to ensure that you review the error paths is to always use
``git diff -W`` and ``git show -W`` (AKA ``--function-context``) when
inspecting your changes. For C code, this will show you the whole
function that's being changed in a patch. One of the things that often
go wrong during backports is that something else in the function changed
on either of the branches that you're backporting from or to. By
including the whole function in the diff you get more context and can
more easily spot problems that might otherwise go unnoticed.
Refactored code
~~~~~~~~~~~~~~~
Something that happens quite often is that code gets refactored by
"factoring out" a common code sequence or pattern into a helper
function. When backporting patches to an area where such a refactoring
has taken place, you effectively need to do the reverse when
backporting: a patch to a single location may need to be applied to
multiple locations in the backported version. (One giveaway for this
scenario is that a function was renamed -- but that's not always the
case.)
To avoid incomplete backports, it's worth trying to figure out if the
patch fixes a bug that appears in more than one place. One way to do
this would be to use ``git grep``. (This is actually a good idea to do
in general, not just for backports.) If you do find that the same kind
of fix would apply to other places, it's also worth seeing if those
places exist upstream -- if they don't, it's likely the patch may need
to be adjusted. ``git log`` is your friend to figure out what happened
to these areas as ``git blame`` won't show you code that has been
removed.
If you do find other instances of the same pattern in the upstream tree
and you're not sure whether it's also a bug, it may be worth asking the
patch author. It's not uncommon to find new bugs during backporting!
Verifying the result
====================
colordiff
---------
Having committed a conflict-free new patch, you can now compare your
patch to the original patch. It is highly recommended that you use a
tool such as `colordiff`_ that can show two files side by side and color
them according to the changes between them::
colordiff -yw -W 200 <(git diff -W <upstream commit>^-) <(git diff -W HEAD^-) | less -SR
.. _colordiff: https://www.colordiff.org/
Here, ``-y`` means to do a side-by-side comparison; ``-w`` ignores
whitespace, and ``-W 200`` sets the width of the output (as otherwise it
will use 130 by default, which is often a bit too little).
The ``rev^-`` syntax is a handy shorthand for ``rev^..rev``, essentially
giving you just the diff for that single commit; also see
the official `git rev-parse documentation`_.
.. _git rev-parse documentation: https://git-scm.com/docs/git-rev-parse#_other_rev_parent_shorthand_notations
Again, note the inclusion of ``-W`` for ``git diff``; this ensures that
you will see the full function for any function that has changed.
One incredibly important thing that colordiff does is to highlight lines
that are different. For example, if an error-handling ``goto`` has
changed labels between the original and backported patch, colordiff will
show these side-by-side but highlighted in a different color. Thus, it
is easy to see that the two ``goto`` statements are jumping to different
labels. Likewise, lines that were not modified by either patch but
differ in the context will also be highlighted and thus stand out during
a manual inspection.
Of course, this is just a visual inspection; the real test is building
and running the patched kernel (or program).
Build testing
-------------
We won't cover runtime testing here, but it can be a good idea to build
just the files touched by the patch as a quick sanity check. For the
Linux kernel you can build single files like this, assuming you have the
``.config`` and build environment set up correctly::
make path/to/file.o
Note that this won't discover linker errors, so you should still do a
full build after verifying that the single file compiles. By compiling
the single file first you can avoid having to wait for a full build *in
case* there are compiler errors in any of the files you've changed.
Runtime testing
---------------
Even a successful build or boot test is not necessarily enough to rule
out a missing dependency somewhere. Even though the chances are small,
there could be code changes where two independent changes to the same
file result in no conflicts, no compile-time errors, and runtime errors
only in exceptional cases.
One concrete example of this was a pair of patches to the system call
entry code where the first patch saved/restored a register and a later
patch made use of the same register somewhere in the middle of this
sequence. Since there was no overlap between the changes, one could
cherry-pick the second patch, have no conflicts, and believe that
everything was fine, when in fact the code was now scribbling over an
unsaved register.
Although the vast majority of errors will be caught during compilation
or by superficially exercising the code, the only way to *really* verify
a backport is to review the final patch with the same level of scrutiny
as you would (or should) give to any other patch. Having unit tests and
regression tests or other types of automatic testing can help increase
the confidence in the correctness of a backport.
Submitting backports to stable
==============================
As the stable maintainers try to cherry-pick mainline fixes onto their
stable kernels, they may send out emails asking for backports when
encountering conflicts, see e.g.
<https://lore.kernel.org/stable/2023101528-jawed-shelving-071a@gregkh/>.
These emails typically include the exact steps you need to cherry-pick
the patch to the correct tree and submit the patch.
One thing to make sure is that your changelog conforms to the expected
format::
<original patch title>
[ Upstream commit <mainline rev> ]
<rest of the original changelog>
[ <summary of the conflicts and their resolutions> ]
Signed-off-by: <your name and email>
The "Upstream commit" line is sometimes slightly different depending on
the stable version. Older version used this format::
commit <mainline rev> upstream.
It is most common to indicate the kernel version the patch applies to
in the email subject line (using e.g.
``git send-email --subject-prefix='PATCH 6.1.y'``), but you can also put
it in the Signed-off-by:-area or below the ``---`` line.
The stable maintainers expect separate submissions for each active
stable version, and each submission should also be tested separately.
A few final words of advice
===========================
1) Approach the backporting process with humility.
2) Understand the patch you are backporting; this means reading both
the changelog and the code.
3) Be honest about your confidence in the result when submitting the
patch.
4) Ask relevant maintainers for explicit acks.
Examples
========
The above shows roughly the idealized process of backporting a patch.
For a more concrete example, see this video tutorial where two patches
are backported from mainline to stable:
`Backporting Linux Kernel Patches`_.
.. _Backporting Linux Kernel Patches: https://youtu.be/sBR7R1V2FeA
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Patch를 Git commit으로 만든 뒤 destination에 옮긴다
1-85Daily work에서 patch backport, branch merge, conflict resolution을 거의 하지 않는 developer에게 merge conflict는 부담스러울 수 있다. 하지만 conflict resolution도 연습 가능한 기술이며 과정을 매끄럽게 하고 결과 신뢰도를 높이는 여러 technique이 있다. 이 문서는 backport와 conflict resolution을 단계별로 설명한다. Author는 Vegard Nossum이다.
Backport할 patch가 이미 Git commit이면 git cherry-pick을 직접 사용한다. Linux kernel처럼 email에서 온 patch라면 먼저 git am으로 tree에 적용해야 한다.
git am은 patch가 source tree에 완벽히 맞기를 요구한다. .rej file을 직접 고치는 방식보다 patch가 cleanly apply되는 적절한 base version을 찾아 먼저 적용한 뒤 destination tree로 cherry-pick하는 것을 강하게 권장한다. 그러면 Git이 conflict marker를 만들고 Git 및 merge tool의 도움을 받아 해결할 수 있다.
예를 들어 LKML에 막 도착한 patch를 오래된 stable kernel로 backport하려면 recent mainline kernel에 먼저 적용한 다음 stable branch로 cherry-pick한다. Patch가 생성된 정확한 base가 가장 좋지만 clean apply되고 original base와 너무 멀지 않으면 큰 문제는 없다. Wrong base를 쓰면 old branch로 cherry-pick할 때 diff context에 unrelated change가 더 들어올 수 있다.
git am보다 git cherry-pick이 유리한 이유는 Git이 existing commit의 정확한 history를 알기 때문이다. Code가 이동해 line number가 달라진 것도 추적하므로 patch를 잘못된 위치에 적용해 silent error나 복잡한 conflict를 만드는 가능성이 낮다.
Email에서 b4로 직접 적용한다면 b4 am의 -g/--guess-base와 -3/--prep-3way option이 일부 과정을 자동화한다. 이 문서의 나머지는 plain git cherry-pick을 전제로 한다.
b4 am --guess-base --prep-3way <message-id>
git cherry-pick -x <commit>
Patch를 Git에 넣은 뒤 destination source tree로 cherry-pick한다. Origin 기록을 commit message에 남기려면 -x를 잊지 않는다.
Stable 제출 형식은 조금 다르다. Subject 다음 첫 line에 다음 둘 중 하나를 넣는다.
commit <upstream commit> upstream
[ Upstream commit <upstream commit> ]
Conflict가 생기는 이유와 merge tool
86-134CONFLICT (content): Merge conflict
Conflict는 patch context, 즉 바꿀 line이나 주변 line이 적용 대상 tree와 맞지 않을 때 생긴다. Backport source branch에는 있고 destination branch에는 없는 patch가 원인인 경우가 많지만 반대도 가능하다.
Cherry-pick이 conflict로 실패하면 Git은 file에 conflict marker를 넣어 위치와 두 branch의 divergence를 보여 준다. Resolution은 다른 commit의 영향을 고려한 final result를 편집하는 일이다.
Regular text editor에서 직접 해결하거나 전용 conflict resolution tool을 사용할 수 있다. 직접 편집은 동작을 이해하고 final result를 통제하기 쉬운 장점이 있으며 상황에 따라 두 방식을 함께 쓸 가치가 있다.
- Emacs Ediff
https://www.emacswiki.org/emacs/EdiffMode - vimdiff
https://linux.die.net/man/1/vimdiff - KDiff3
http://kdiff3.sourceforge.net/ - TortoiseMerge
https://tortoisesvn.net/TortoiseMerge.html - Meld
https://meldmerge.org/help/ - P4Merge
https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge - Beyond Compare
https://www.scootersoftware.com/ - IntelliJ conflict resolution
https://www.jetbrains.com/help/idea/resolve-conflicts.html - VSCode version control
https://code.visualstudio.com/docs/editor/versioncontrol - git-mergetool
https://git-scm.com/docs/git-mergetool
git mergetool --help
Conflict를 만든 prerequisite commit 찾기: git log
136-193대부분 conflict는 source branch에 있는 일부 patch가 destination branch에 없어서 생긴다. 독립 branch merge처럼 일반적인 경우에는 양쪽에서 development가 진행됐거나 old branch에 다른 conflict resolution backport가 들어가 divergence가 생겼을 수 있다.
Resolution 정확성을 신뢰하려면 conflict를 만든 commit을 항상 식별해야 한다. 익숙하지 않은 code라면 해당 commit changelog가 code context와 resolution의 잠재 problem을 이해하게 해 준다.
첫 단계는 conflict file의 git log를 보는 것이다. File 변경이 적으면 충분하지만 크고 자주 바뀌는 file이면 복잡해질 수 있다. Current checked-out branch HEAD와 cherry-pick commit parent 사이 range를 조회한다.
git log HEAD..<commit>^ -- <path>
git log -L:'\<function\>':<path> HEAD..<commit>^
git log -G'regex' HEAD..<commit>^ -- <path>
git log -G'\->index\>.*='
-L은 conflict가 난 function 하나로 history를 제한한다. Function 이름 주위의 \<와 \>는 word boundary에 match시킨다. 이 부분은 regex이고 Git은 첫 match만 따라가므로 boundary 없이 thread_stack을 찾으면 실제 target 대신 try_release_thread_stack_to_cache history만 나올 수 있다.
-G는 listed commit diff에 특정 regex가 나타나는 경우만 filter한다. Function call이나 variable이 언제 변경·추가·삭제되었는지 빠르게 찾거나 특정 struct member assignment처럼 더 구체적인 pattern을 검색할 수 있다.
git blame과 prerequisite·incidental 판정
194-245주어진 conflict에서 가장 recent prerequisite commit을 찾는 다른 방법은 git blame이다. Cherry-pick할 patch의 parent commit과 conflict file을 대상으로 실행한다.
git blame <commit>^ -- <path>
git blame -L:'\<function\>' <commit>^ -- <path>
Conflict 위치로 이동하면 blame output 첫 column이 각 line을 추가한 commit ID다. git show로 이 commit이 conflict source인지 검토한다. 같은 area의 다른 line을 여러 commit이 바꾸거나 같은 line을 여러 차례 바꿨다면 원인이 여러 개일 수 있다. 더 오래된 file version을 지정해 git blame을 반복하면 history를 더 거슬러 올라갈 수 있다.
Conflict 원인 patch를 찾았으면 backport prerequisite인지 단순 incidental인지 판단한다. Incidental patch는 같은 code를 만지지만 의미를 실질적으로 바꾸지 않는다. Whitespace cleanup, function·variable rename만 하는 patch가 예다.
반대로 target branch에 변경 대상 function 자체가 없다면 incidental이 아니다. Function을 추가한 patch를 먼저 cherry-pick해야 하는지 신중히 판단한다.
필수 prerequisite가 있으면 current cherry-pick을 중단하고 prerequisite부터 적용한다. 다른 file conflict를 이미 해결해 다시 하기 싫다면 그 file을 임시 copy해 둘 수 있다.
git cherry-pick --abort
git cherry-pick -x <prerequisite-commit>
Conflict marker와 combined diff 읽기
247-312추가 patch를 pick하거나 revert하지 않고 conflict를 직접 해결하기로 했다면 file에는 기본 merge-style marker가 들어 있다.
<<<<<<< HEAD
this is what's in your current tree before cherry-picking
=======
this is what the patch wants it to be after cherry-picking
>>>>>>> <commit>... title
Editor에서 보는 형태다. Argument 없이 git diff를 실행하면 일반 diff marker 한 column이 아니라 두 column인 combined diff가 나온다.
$ git diff
[...]
++<<<<<<<< HEAD
+this is what's in your current tree before cherry-picking
++========
+ this is what the patch wants it to be after cherry-picking
++>>>>>>>> <commit>... title
이 3-way diff 또는 diff-of-diffs는 current branch와 current working directory 차이, 그리고 current branch와 original patch 적용 후 file 사이 차이를 함께 보여 준다.
Combined diff는 source와 destination 사이의 다른 변경까지 보여 주어 고려할 내용을 찾는 데 유용하지만 읽기 어렵다. git diff HEAD 또는 git diff --ours는 cherry-pick 전 current branch와 current working directory 사이만 보여 준다.
$ git diff HEAD
[...]
+<<<<<<<< HEAD
this is what's in your current tree before cherry-picking
+========
+this is what the patch wants it to be after cherry-picking
+>>>>>>>> <commit>... title
일반 diff처럼 읽을 수 있어 current branch line과 merge conflict 또는 cherry-picked patch 때문에 추가되는 line을 구분하기 쉽다.
diff3와 zdiff3 conflict style
314-344기본 marker는 merge style이다. Diff3 style은 두 부분 대신 세 부분을 보여 준다.
<<<<<<< HEAD
this is what is in your current tree before cherry-picking
||||||| parent of <commit> (title)
this is what the patch expected to find there
=======
this is what the patch wants it to be after being applied
>>>>>>> <commit> (title)
Diff3에는 Git이 찾을 것으로 예상했지만 실제 destination에는 없던 original context가 들어간다. Cherry-pick commit의 before와 after를 직접 비교할 수 있어 patch가 실제로 바꾼 내용을 명확히 이해하고 더 나은 resolution을 결정하게 하므로 강하게 권장된다.
git config merge.conflictStyle diff3
Git 2.35에서 추가된 zdiff3도 같은 세 section을 제공하지만 공통 line을 잘라 conflict area를 더 작게 만든다.
Resolution 반복과 rename conflict 처리
346-411Conflict resolution의 첫 단계는 backport patch를 이해하는 것이다. Kernel에서는 잘못된 변경이 system crash나 발견되지 않은 security vulnerability로 이어질 수 있어 특히 중요하다. 모든 change와 hunk에 왜 이 hunk가 필요한지 질문해야 답이 resolution을 안내한다.
때로는 conflict의 첫 부분만 남겨 file을 사실상 unchanged로 만든 뒤 변경을 수동 적용하는 것이 쉽다. 예를 들어 patch가 function argument 0을 1로 바꾸는데 다른 change가 argument list 끝에 중요하지 않은 parameter를 추가했다면 해당 argument만 손으로 바꾸고 나머지를 유지한다. Unrelated context가 많이 들어온 conflict에 유용하다.
Marker가 많은 어려운 conflict는 git add 또는 git add -i로 해결한 부분을 선택적으로 stage해 치운다. git diff HEAD로 남은 resolution을 보고 git diff --cached로 지금까지 완성한 patch를 볼 수 있다.
git add -i
git diff HEAD
git diff --cached
Patch 대상 file이 rename되면 Git이 marker도 넣지 않고 unmerged path로 남길 수 있다. One-line처럼 작은 변경은 수동 적용하는 것이 가장 쉽지만 크고 복잡한 변경은 그렇게 해서는 안 된다.
첫 시도로 rename detection threshold를 기본 50%에서 30%로 낮출 수 있다. 두 file content가 최소 threshold만큼 같아야 add-delete pair를 rename 후보로 본다.
git cherry-pick -strategy=recursive -Xrename-threshold=30
Rename patch 자체를 backport해야 하는 경우도 있지만 흔하지 않다. 대안은 destination branch에서 git mv로 잠시 rename하고 commit한 뒤 cherry-pick을 다시 시도하고, git mv로 original name을 복원해 다시 commit한 다음 git rebase -i로 결과를 하나의 commit으로 squash하는 것이다.
Function argument, error path와 refactoring 함정
413-471Function argument 변경을 주의한다. 두 line이 같아 보여도 전달한 variable이 i인지 j인지처럼 작은 차이가 있을 수 있다.
Cherry-pick patch에 보통 error handling용 goto가 있으면 destination branch에서도 target label이 올바른지 반드시 재확인한다. 새 return, break, continue도 마찬가지다. Error handling은 보통 function 끝에 있어 다른 patch로 바뀌었더라도 conflict에 포함되지 않을 수 있다.
git diff -W와 git show -W, 즉 --function-context로 변경을 검토하면 C code의 entire function을 보여 준다. Source 또는 destination branch에서 function의 다른 부분이 바뀐 문제를 더 쉽게 발견할 수 있다.
git diff -W
git show -W <commit>
Common sequence를 helper function으로 factor out하는 refactoring이 있었다면 backport에서는 반대 작업이 필요할 수 있다. Upstream helper 한 곳을 고친 patch를 old version의 여러 call-site pattern에 각각 적용해야 한다. Function rename은 이런 상황의 단서일 수 있지만 항상 그런 것은 아니다.
Incomplete backport를 피하려면 bug pattern이 여러 곳에 있는지 git grep으로 찾는다. 같은 fix가 적용될 다른 위치가 old branch에는 있지만 upstream에는 없다면 patch 조정이 필요할 가능성이 높다. Removed code는 git blame에 나오지 않으므로 git log로 해당 영역 history를 조사한다.
Upstream tree에 같은 pattern이 더 있고 bug인지 확신할 수 없다면 original patch author에게 묻는다. Backport 과정에서 새 bug를 발견하는 일은 드물지 않다.
Original patch와 backport를 side-by-side 검증
473-511Conflict-free patch를 commit한 뒤 original patch와 비교한다. 두 file을 side-by-side로 놓고 차이를 color로 표시하는 colordiff 같은 tool을 강하게 권장한다.
colordiff -yw -W 200 <(git diff -W <upstream commit>^-) \
<(git diff -W HEAD^-) | less -SR
-y는 side-by-side, -w는 whitespace ignore, -W 200은 output width 200을 뜻한다. 기본 130은 보통 좁다. rev^-는 rev^..rev의 shorthand로 single commit diff만 선택한다. git diff의 -W는 변경 function 전체를 보여 준다.
Colordiff는 서로 다른 line을 강조한다. Original과 backport에서 error goto label이 다르면 나란히 다른 color로 보여 주고, 두 patch가 직접 수정하지 않았지만 context가 다른 line도 강조해 manual inspection에서 눈에 띄게 한다.
이는 visual inspection일 뿐이며 실제 test는 patched kernel 또는 program을 build하고 실행하는 것이다.
Build와 runtime 검증
513-550Runtime test 자체는 이 문서 범위 밖이지만 빠른 sanity check로 patch가 만진 file만 build할 수 있다. .config와 build environment가 올바르게 설정되어 있다고 가정한다.
make path/to/file.o
Single-file build는 linker error를 찾지 못하므로 성공 뒤에도 full build를 해야 한다. 먼저 file 하나를 compile하면 changed file에 compiler error가 있을 때 full build를 기다리는 시간을 아낀다.
Build나 boot 성공만으로 missing dependency를 배제할 수 없다. 같은 file의 독립된 두 change가 conflict와 compile error 없이 합쳐지지만 exceptional runtime path에서만 error를 만들 수 있다.
실제 예로 syscall entry code의 첫 patch가 register를 save·restore하고 나중 patch가 그 sequence 중간에서 같은 register를 사용했다. Diff 영역이 겹치지 않아 두 번째 patch만 conflict 없이 cherry-pick되지만 결과 code는 save되지 않은 register를 덮어썼다.
대부분 error는 compile 또는 간단한 execution에서 잡히지만 backport를 정말 검증하려면 일반 patch와 같은 수준으로 final patch를 review해야 한다. Unit test, regression test와 기타 automated test가 correctness confidence를 높인다.
Stable tree에 backport 제출
552-585Stable maintainer가 mainline fix를 stable kernel로 cherry-pick하다 conflict를 만나면 backport를 요청하는 email을 보낼 수 있다. 보통 correct tree에 patch를 cherry-pick하고 제출하는 정확한 step을 포함한다.
Changelog는 다음 expected format을 따라야 한다.
<original patch title>
[ Upstream commit <mainline rev> ]
<rest of the original changelog>
[ <summary of the conflicts and their resolutions> ]
Signed-off-by: <your name and email>
Older stable version은 Upstream commit line 대신 다음 형식을 사용했다.
commit <mainline rev> upstream.
Patch 적용 kernel version은 email subject에 표시하는 것이 가장 일반적이다. Signed-off-by 영역이나 --- 아래에 적을 수도 있다.
git send-email --subject-prefix='PATCH 6.1.y' <patch-files>
Stable maintainer는 active stable version마다 별도 submission을 기대하며 각각 따로 test해야 한다.
마지막 조언과 예제
586-604- 겸손한 태도로 backport에 접근한다.
- Changelog와 code를 모두 읽어 backport 대상 patch를 이해한다.
- 제출할 때 결과에 대한 confidence를 솔직하게 밝힌다.
- 관련 maintainer에게 명시적 ack를 요청한다.
이 문서는 이상적인 backport process를 설명했다. Mainline patch 두 개를 stable로 실제 backport하는 구체적인 예는 Backporting Linux Kernel Patches video tutorial에서 볼 수 있다.
먼저 clean base에 적용한 뒤 cherry-pick한다
backporting.rst:14-84Mail patch를 old stable tree에서 곧바로 git am하면 context mismatch와 .rej 처리에 갇히기 쉽다. Patch가 cleanly 적용되는 original 또는 가까운 mainline base에 먼저 git am으로 commit을 만든 뒤 destination branch에 git cherry-pick하는 방식이 권장된다.
Git은 commit history를 알고 있어 code 이동과 줄 번호 변경을 추적하고 3-way conflict marker를 만든다. Raw patch의 fuzzy context matching보다 잘못된 위치에 조용히 적용될 위험이 낮다. b4 am --guess-base --prep-3way도 base 추정과 3-way 준비를 자동화할 수 있다.
-x는 원본 commit identity를 commit message에 남긴다. Stable submission은 subject 다음 첫 줄에 'commit <sha> upstream' 또는 '[ Upstream commit <sha> ]' 형식을 요구한다.