Packaging workflow, patch management and git magic in Fedoraland

October 15, 2010 in en, fedora, git, howto, linux, packaging

Big part of my job is packaging for Fedora Linux (I am pretty sure I haven’t mentioned this before :-) ). I have spent last 6 months working on various Java packages, adding new packages to Fedora, updating dependencies etc. I have developed certain workflow which I believe might be of interest to other packagers. So here goes. Most of these hints are about managing patches for your packages. I’ll also try to work on concrete package so it won’t be completely theoretical.

Let’s assume your project already has some history and patches. Let’s fix velocity bug 640660 for example. I’ll start with steps I took and what they meant, and I’ll summarize in the end with rationale what I have gained by using my workflow (and what could be improved).

After modifying BuildRequires and Requires to tomcat6 servlet api I tried to build velocity:

$ fedpkg mock

This is what I got:

---snip----
compile-test:
[javac] Compiling 125 source files to /builddir/build/BUILD/velocity-1.6.3/bin/test-classes
[javac] /builddir/build/BUILD/velocity-1.6.3/bin/test-src/org/apache/velocity/test/VelocityServletTestCase.java:135: org.apache.velocity.test.VelocityServletTestCase.MockServletContext is not abstract and does not override abstract method getContextPath() in javax.servlet.ServletContext
[javac] static class MockServletContext implements ServletContext
[javac] ^
[javac] Note: /builddir/build/BUILD/velocity-1.6.3/bin/test-src/org/apache/velocity/test/VelocityServletTestCase.java uses or overrides a deprecated API.
[javac] Note: Recompile with -Xlint:deprecation for details.
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
[javac] 1 error
BUILD FAILED
/builddir/build/BUILD/velocity-1.6.3/build/build.xml:251: Compile failed; see the compiler error output for details.
Total time: 47 seconds
---snip---

The issue seems simple to fix, just missing stub function in a test case, right? So what now?

$ fedpkg prep
$ mv velocity-1.6.3 velocity-1.6.3.git
$ cd velocity-1.6.3.git
$ git init && git add . && git commit -m 'init'

This effectively created my small git repository for sources and populated it with all files. Using fedpkg prep step we extracted the tarball and applied already existing patches to unpacked sources. I suggest you create shell alias for last three commands as you’ll be using it a lot. We moved directory to velocity-1.6.3.git so that next (accidental?) fedpkg prep won’t erase our complicated changes (yes it happened to me once. I’ve had better days). Note that velocity-1.6.3.git is not a temporary directory. I will keep it around after fixing this bug so that I can use git history, diffs and other features in the future. It is especially nice when you have packages with lot of patches on top.

Now we can easily work in our new git repository, edit source file in question and do:

$ git add src/test/org/apache/velocity/test/VelocityServletTestCase.java
$ git commit -m 'Fix test for servlet api 2.5'
$ git format-patch HEAD~1

This created commit with descriptive message and generated a patch file 0001-Fix-test-for-servlet-api-2.5.patch in our current directory. This is how the patch looks like:

From 8758e3c83411ffadc084d241217fc25f1fd31f42 Mon Sep 17 00:00:00 2001
From: Stanislav Ochotnicky
Date: Thu, 14 Oct 2010 10:20:52 +0200
Subject: [PATCH] Fix test for servlet api 2.5

---
.../velocity/test/VelocityServletTestCase.java | 7 ++++++-
1 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/src/test/org/apache/velocity/test/VelocityServletTestCase.java b/src/test/org/apache/velocity/test/VelocityServletTestCase.java
index 824583e..ac0ab5c 100644
--- a/src/test/org/apache/velocity/test/VelocityServletTestCase.java
+++ b/src/test/org/apache/velocity/test/VelocityServletTestCase.java
@@ -16,7 +16,7 @@ package org.apache.velocity.test;
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
- * under the License.
+ * under the License.
*/

import java.io.IOException;
@@ -149,6 +149,11 @@ public class VelocityServletTestCase extends TestCase
return this;
}

+ public String getContextPath()
+ {
+ return "";
+ }
+
public String getServletContextName()
{
return "VelocityTestContext";
--
1.7.2.3

Now that we have patch prepared for velocity we need to use it in the spec file and we’re done.

Let’s say our first attempted patch wouldn’t work as expected and build (or test) still failed. We modify the sources again and do another commit. What we have now is:

$ git log --format=oneline
c15f7e02eaae93b755cc0bfde6def3d6e67d2b0f (HEAD, master) Fix previous commit
3e3d654c142c7028c9c7895579fba204c4c4bf08 Fix test for servlet api 2.5
2f32554ddf892f4cca3f78b1f82a7c3ab169c357 init

We don’t want two patches in the spec file for one fix so: time for git magic. You’ve probably heard of git rebase if you’ve been using git for a while. What we want to do now is merge last two commits into one, or in git-speak “squash” them. To do this you have to do:

$ git rebase -i HEAD~2

Now your editor should pop-up with this text:

pick 3e3d654 Fix test for servlet api 2.5
pick c15f7e0 Fix previous commit

# Rebase 2f32554..c15f7e0 onto 2f32554
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

So we just need to change “pick c15f7e0 Fix previous commit” into “squash c15f7e0 Fix previous commit” (you can also use just ‘s’ instead of ‘squash’). Save. Close. Another editor window will open with something like this:

# This is a combination of 2 commits.
# The first commit's message is:

Fix test for servlet api 2.5

# This is the 2nd commit message:

Fix previous commit

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.

For this case we will delete second message because we just want to pretend our first attempt was perfect :-). Save. Close. Now we have:

$ git log --format=oneline
cbabb6ac43f7bdb8e52ccd09c25cfd0a032b553c (HEAD, master) Fix test for servlet api 2.5
2f32554ddf892f4cca3f78b1f82a7c3ab169c357 init

Repeat as many times as you want. You can also re-order commits and change commit messages with rebase (note that if you just want to change last commit message you can do “git commit –amend”). I generally don’t create commits until I have working patch though.

So why do I think all this mumbo-jumbo improves my workflow? Let’s see:

  • I can have long comments for every patch I create (instead a line or two in spec file)
  • I can use the same patches to send directly to upstream
  • I don’t have to juggle around with diff and remember what files I changed where
  • Probably several other things I haven’t even realized

I have a few things that bother me of course. git format-patch generates filenames that are different from standard practice of %{name}-%{version}-message.patch. This is not a git problem. For packages where only my patches exist I stick with git naming, but when there are different patches I stick with naming they started. Another thing that is bothering me is that creating initial repository by using “fedpkg prep” hides patches that were applied to sources. That’s why I am thinking about re-working my packages so that all patches will be in my git repositories as commits with descriptive messages. No need for comments in the spec file anymore. Perhaps someone can suggest other improvements to my approach.