Using git patchfiles to speed up similar implementation tasks

Updated . Posted . Visible to the public.

Sometimes you'll find yourself with a set of tasks that require similar code for different models. For example, if you start working at a new application that allows CRUDing pears and apples, each commit might look similar to this:

commit 41e3adef10950b324ae09e308f632bef0dee3f87 (HEAD -> ml/add-apples-12345)
Author: Michael Leimstaedtner <michael.leimstaedtner@acme.com>
Date:   Fri Aug 11 09:42:34 2023 +0200

    Add Apples as a new fruit

diff --git a/app/models/apple.rb b/app/models/apple.rb
new file mode 100644
index 0000000..a51cbad
--- /dev/null
+++ b/app/models/apple.rb
@@ -0,0 +1,9 @@
+class Apple < Fruit
+  belongs_to :juice, class_name: 'AppleJuice'
+
+  attr_accessor :type
+
+  def to_s
+    I18n.t(type, scope: 'fruits.apples')
+  end
+end
diff --git a/spec/models/apple_spec.rb b/spec/models/apple_spec.rb
new file mode 100644
index 0000000..6b9a90e
--- /dev/null
+++ b/spec/models/apple_spec.rb
@@ -0,0 +1,10 @@
+describe Apple do
+  subject(:apple) { build(:apple) }
+
+  describe '#to_s' do
+    it "humanizes the apple's type" do
+      subject.type = :golden_delicious
+      expect(subject.to_s).to eq('Golden Delicious')
+    end
+  end
+end

Now usually adding pears will be a monotonous job as they provide no big difference in terms of code. You can however use git to speed it up!

A primer on patches

What you see above is a so called patch, generated by git show 41e3ade. Any diff is a patch, really: for example the output of git diff main will tell you which patches are required to bring main to the same state as your current branch (and staged changes).

Patches can be applied. For example, if you want to cherry-pick the "apples" changes to another branch you can do git show 41e3ade | git apply --reject. Or, a bit more verbose: first write it to a patchfile with git show 41e3ade > apples.patch, then apply it with git apply --reject apples.patch. Changes that cannot be applied will result in .rej files.

Note that this is just a fictional example, you can use git cherry-pick for this specific use case.

Altering patchfiles to speed up tedious work

As mentioned above, we want to support pears in our application. I suggest you the following:

  • git show 41e3ade > pears.patch: Write the apple commit to a patchfile
  • (using your IDE): Search and replace apple with pear, Apple with Pear etc.
  • git apply --reject pears.patch: Apply the modified patchfile above
  • git add -N . Add new files to git
  • git add -p apply suggested changes and fix necessary differences

Take your time for the last step. For example "Golden Delicious" does not make sense for pears!

A gsubed / modified pears.patch file could look like this

commit 41e3adef10950b324ae09e308f632bef0dee3f87 (HEAD -> ml/add-pears-12345)
Author: Michael Leimstaedtner <michael.leimstaedtner@acme.com>
Date:   Fri Aug 11 09:42:34 2023 +0200

    Add Pears as a new fruit

diff --git a/app/models/pear.rb b/app/models/pear.rb
new file mode 100644
index 0000000..a51cbad
--- /dev/null
+++ b/app/models/pear.rb
@@ -0,0 +1,9 @@
+class Pear < Fruit
+  belongs_to :juice, class_name: 'PearJuice'
+
+  attr_accessor :type
+
+  def to_s
+    I18n.t(type, scope: 'fruits.pears')
+  end
+end
diff --git a/spec/models/pear_spec.rb b/spec/models/pear_spec.rb
new file mode 100644
index 0000000..6b9a90e
--- /dev/null
+++ b/spec/models/pear_spec.rb
@@ -0,0 +1,10 @@
+describe Pear do
+  subject(:pear) { build(:pear) }
+
+  describe '#to_s' do
+    it "humanizes the pear's type" do
+      subject.type = :williams
+      expect(subject.to_s).to eq('Williams')
+    end
+  end
+end
Michael Leimstädtner
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2023-08-11 07:28)