Git Best Practices: Upgrading the Patch Process

If you're struggling to get your bearings in the new Git world, this article should help with the transition.

For close to a decade, the CVS version control system has been an integral part of every Drupal developer's workflow. Many site builders could get by downloading release versions of Drupal and assorted modules, but using bleeding-edge code, contributing modules, and submitting bug fixes or enhancements to existing projects all meant getting comfortable with CVS. In March of 2011, that all changed: all of the projects hosted on Drupal.org were migrated to the Git version control system! If you're struggling to get your bearings in the new Git world, this article should help with the transition. Before the Great Git Migration, there were two options for creating patches.

Using diff: $ diff -up system.module.orig system.module > 12345_issue_name.patch

Or, using CVS: $ cvs diff -up > 12345_issue_name.patch

Now that we are using Git for our day to day work, we face a different problem. Not only are there multiple ways to create a patch, but they can produce different results that require different commands to apply. Let's take a look at the different commands and see how they can be used.

Basic Patches with "git diff"

git diff is the command that is most similar to diff or cvs diff. By default, it will create a patch of all unstaged changes against the current commit. Compared to the output of cvs diff, the diff header is slightly different. Let's generate a patch between two commits in Drupal 7:

$ git clone --branch=7.x git://git.drupalcode.org/project/drupal.git drupal-7.x
$ cd drupal-7.x
$ git diff 66f93d7..f1ba363
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 4319dbf4c2..0981438c20 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
 /**
  * Perform periodic actions.
  *
- * This hook will only be called if cron.php is run (e.g. by crontab).
- *
  * Modules that require some commands to be executed periodically can
  * implement hook_cron(). The engine will then call the hook whenever a cron
  * run happens, as defined by the administrator. Typical tasks managed by

What are the first two lines of the diff output telling us?

  • --git is a helpful note that this patch was generated with Git.
  • a/ and b/ are prefixes added to the paths by Git.
  • index 4319dbf..0981438 100644 provides three useful pieces of metadata:
    1. index indicates that the line shows Git index metadata.
    2. 4319dbf..0981438 shows that the first blob hash was 4319dbf and the resulting blob hash was 0981438. One interesting note about the second hash is that if you are running diff against uncommitted changes, the hash represents the hash of the resulting file if you actually commit the change.
    3. Finally, 100644 indicates the permissions set on the resulting file in octal format.

Classic Patches with "git diff --no-prefix"

This command removes the "a" and "b" prefixes from the diff header. Specifying this option allows patch to be run using patch -p0, just like with CVS. This was commonly used by those using Git before Drupal itself switched over to Git.

$ git diff --no-prefix 66f93d7..f1ba363​
diff --git modules/system/system.api.php modules/system/system.api.php
index 4319dbf4c2..0981438c20 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
 /**
  * Perform periodic actions.
  *
- * This hook will only be called if cron.php is run (e.g. by crontab).
- *
  * Modules that require some commands to be executed periodically can
  * implement hook_cron(). The engine will then call the hook whenever a cron
  * run happens, as defined by the administrator. Typical tasks managed by

Unless you are working with other version control systems as well (such as Subversion), this option should no longer be needed.

Module Patches with "git diff --relative"

Using --relative tells Git to generate the patch relative to the current directory. This is especially useful when generating a patch for a contributed module from within a Drupal instance. For example, here's a patch (issue #466134 on drupal.org) against the Date module from within an existing Drupal project:

$ pwd ~/workspace/drupal6/sites/all/modules/date

$ git diff --relative 1bc4aa..1c7b4e
diff --git a/date_api_elements.inc b/date_api_elements.inc
index 440bcfd..39332bf 100644
--- a/date_api_elements.inc
+++ b/date_api_elements.inc
@@ -252,7 +252,6 @@ function date_parts_element($element, $date, $format) {
     $part_type = in_array($field, $element['#date_text_parts']) ? 'textfield' : 'select';
     $sub_element[$field] = array(
       '#weight' => $order[$field],
-      '#required' => $element['#required'],
       '#attributes' => array('class' => (isset($element['#attributes']['class']) ? $element['#attributes']['class'] : '') .' date-'. $field),
       );
     switch ($field) {
@@ -665,4 +664,5 @@ function date_convert_from_custom($date, $format) {
   // Don't test for valid date, we might use this to extract
   // incomplete date part info from user input.
   return date_convert($final_date, DATE_ARRAY, DATE_DATETIME);
-}
\ No newline at end of file
+}
+

This patch would easily apply to a checkout of the date module by itself.

Apply Patches with "git apply"

Now that a patch file has been generated, we can use git apply to apply the patch. If the patch was generated with plain git diff, then applying the patch is as simple as running git apply <filename>:

$ git apply 1041440_hook_cron_phpdoc.patch
$ git diff
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 4319dbf..0981438 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
/**
 * Perform periodic actions.
 *
-* This hook will only be called if cron.php is run (e.g. by crontab).
-*
 * Modules that require some commands to be executed periodically can
 * implement hook_cron(). The engine will then call the hook whenever a cron
 * run happens, as defined by the administrator. Typical tasks managed by

If the patch was generated with no prefix (such as from cvs diff), use the -p flag just like you would with patch. git apply has two key differences from patch. First, it will not apply a patch if you have other uncommitted changes in your code. Either commit your changes, or stash them with git stash. The other significant difference is that by default, git apply will not apply a patch that does not apply cleanly. When reviewing large patches, this is a great way to determine if the patch cleanly applies without needing to worry about cleaning up the incomplete patch. To force git apply to apply the patch anyways, use the --reject flag.

Creating Better Patches with "git format-patch"

While git diff and git apply are significantly improved over cvs diff and patch, they pale in comparison to the power of git format-patch. This command doesn't just generate a diff, but provides all of the metadata needed to replicate a series of commits. The command originated from the Linux community's need to share patches over email. We can do the same thing by uploading format-patches to issue queues.

To use this command, run it with git format-patch <source-branch>, where the source branch is the branch your code branched from. For example, to generate patches that make up Drupal 8.x as compared to Drupal 7.x, checkout the 8.x branch, and run git format-patch 7.x. This will generate numbered files, with each corresponding to a single commit. It is also possible to put all of the commits into a single file, using git format-patch --stdout > 12345_fix_issues.patch. The --stdout flag is recommended for all patches uploaded to issues on drupal.org.

Let's take a look at what git format-patch will generate. Here's the start of a patch that was submitted against the User Relationships module:

From 9191b50ad0a0f28ab24616e4b4a791ce3d1536a8 Mon Sep 17 00:00:00 2001

This line shows the hash of the commit. This is what will show up in your local Git repository after the patch is applied. The date doesn't mean anything, and is included so that if you're sending patches directly as emails (not an attachment, but the email itself) that email applications recognize it as a valid email.

From: Andrew Berry <deviantintegral@gmail.com>

This is the author of the patch, which will be the information set in your Git configuration.

Date: Tue, 5 Apr 2011 09:56:20 -0400

This is the date the commit was made. It is not the date you created the patch, and will always stay the same.

Subject: [PATCH 1/2] #1116854: Use #access to set visibility of user relationship mailer settings.

The subject line contains the commit message as well as the patch number and the total number of patches generated.

--- .../user_relationship_mailer.module | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-)

These lines provide an easy to read summary of the files changed, and how many lines were modified in each file.

diff --git a/user_relationship_mailer/user_relationship_mailer.module b/user_relationship_mailer/user_relationship_mailer.module
index d423292..b1ac03a 100644
--- a/user_relationship_mailer/user_relationship_mailer.module
+++ b/user_relationship_mailer/user_relationship_mailer.module
@@ -184,13 +184,15 @@ function user_relationship_mailer_form_user_relationships_ui_settings_alter(&$fo
[snip]

Finally, we have the beginning of the patch itself. This part of the file is identical to what would be generated with git diff.

One limitation to keep in mind when generating patches with format-patch is that they don't include the "since" commit that you used to create the patch. When submitting patches, instead of just mentioning the branch it was created against, consider posting the hash of the starting commit. That way, if conflicting commits are made on that branch, others have a starting point to work with instead of having to fix all of the conflicts right away.

Applying format-patches with "git am"

Now that we can generate patches with git format-patch, how do we apply them to our local repository?

  1. Check out a branch (such as 6.x-1.x, or 7.x-1.x), or a specific commit to apply the patch to.
  2. Create a new branch to keep track of the commits for this issue. It is a best practice to include the issue number in your branch name. For example, git checkout -b 1116854/use-access-mailer-settings will create a branch named with the issue number and a description of the issue.
  3. Apply the patch with git am --3way <patch file>

The --3way option tells git am to automatically skip patches that are currently applied to your branch. This is especially useful when it takes more than one attempt to completely address an issue. With the --3way switch, the development workflow becomes something like the following:

  1. An issue is filed with a bug or feature request.
  2. Someone creates a first attempt at a patch, and uploads the results of git format-patch.
  3. Another contributor fixes something in the first patch (such as adding documentation) and creates a new commit on top of the previous commits. They generate a second patch with git format-patch --stdout that contains both their commit and the previous commits created by the first author.
  4. When the original author wants to apply the new commit, they download the second patch, and apply it with git am --3way to your issue branch. Git will automatically skip the first two commits and put the new documentation commit on top.
  5. If anyone new works on the issue, they can download and apply the last patch and get the entire series of commits.

Now, you're ready to create patches, contribute them to issues on drupal.org, and review patches others have created. Have any tips to improve the new patch workflow? Share them with us below!

Get in touch with us

Tell us about your project or drop us a line. We'd love to hear from you!