In a legacy software project with a long and storied history, it can be difficult to determine why a given class, method, or line was added in the first place.
The git blame
command is a common tool for uncovering the history of a file on
a line-by-line basis. However, if a section of code was moved over from a
different file, the results from git blame
can be less than useful.
Let’s say I’m trying to get to the original commit message relating to code in a
method called #fulfill
:
git blame app/models/subscription_fulfillment.rb
ad36d88b (Ian 2015-09-25 1) class SubscriptionFulfillment
...
ad36d88b (Ian 2015-09-25 6) def fulfill
ad36d88b (Ian 2015-09-25 7) ActiveRecord::Base.transaction do
ad36d88b (Ian 2015-09-25 8) subscription = Subscription.create!(subscription_params)
ad36d88b (Ian 2015-09-25 9) UserRole.create!(user: current_user, role_type: UserRole::SUBSCRIBER)
ad36d88b (Ian 2015-09-25 10) BillingSchedule.create!(user: current_user, subscription: subscription)
ad36d88b (Ian 2015-09-25 11) end
ad36d88b (Ian 2015-09-25 12) end
...
ad36d88b (Ian 2015-09-25 17) end
Let’s look at the commit provided by the blame command:
git show ad36d88b
commit ad36d88b8eabf76e70af1f47fa9f82ac0a2959f8
Author: Ian C. Anderson <iancanderson@thoughtbot.com>
Date: Fri Sep 25 11:42:22 2015 -0400
Refactor to SubscriptionFulfillment class
diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb
index 10c7632..12dca61 100644
--- a/app/controllers/subscriptions_controller.rb
+++ b/app/controllers/subscriptions_controller.rb
@@ -1,9 +1,5 @@
class SubscriptionsController < ActionController::Base
def create
- ActiveRecord::Base.transaction do
- subscription = Subscription.create!(subscription_params)
- UserRole.create!(user: current_user, role_type: UserRole::SUBSCRIBER)
- BillingSchedule.create!(user: current_user, subscription: subscription)
- end
+ SubscriptionFulfillment.new(subscription_params).fulfill
end
end
diff --git a/app/models/subscription_fulfillment.rb b/app/models/subscription_fulfillment.rb
new file mode 100644
index 0000000..40c2a43
--- /dev/null
+++ b/app/models/subscription_fulfillment.rb
@@ -0,0 +1,21 @@
+class SubscriptionFulfillment
+ def initialize(subscription_params)
+ @subscription_params = subscription_params
+ end
+
+ def fulfill
+ ActiveRecord::Base.transaction do
+ subscription = Subscription.create!(subscription_params)
+ UserRole.create!(user: current_user, role_type: UserRole::SUBSCRIBER)
+ BillingSchedule.create!(user: current_user, subscription: subscription)
+ end
+ end
+
+ private
+
+ attr_reader :subscription_params
+end
This doesn’t give us a useful commit message about the code’s origin, since this
commit simply moved the code out of a controller action into a separate
SubscriptionFulfillment class. At this point, we could run git blame
on the
SubscriptionsController to follow back to the original commit. But in a legacy
application, this can lead you on a wild goose chase. Luckily there’s a better
way!
git blame -C app/models/subscription_fulfillment.rb
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 1) class SubscriptionFulfillment
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 2) def initialize(subscription_params)
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 3) @subscription_params = subscription_params
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 4) end
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 5)
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 6) def fulfill
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 7) ActiveRecord::Base.transaction do
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 8) subscription = Subscription.create!(subscription_params)
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 9) UserRole.create!(user: current_user, role_type: UserRole::SUBSCRIBER)
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 10) BillingSchedule.create!(user: current_user, subscription: subscription)
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 11) end
ac97cf4a app/controllers/subscriptions_controller.rb (Kat 2014-05-05 12) end
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 13)
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 14) private
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 15)
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 16) attr_reader :subscription_params
ad36d88b app/models/subscription_fulfillment.rb (Ian 2015-09-25 17) end
By providing the -C
option to git blame
, git shows us the original commits
that added those lines, even if they were originally added to a different file.
Then we can directly view the original commit to get some more context around the code:
git show ac97cf4
commit ac97cf4a8ef12650bbcd80e93f0d45f01675574a
Author: Kat <kat@example.com>
Date: Mon May 5 11:34:51 2014 -0400
User subscribes to our service
When a user subscribes to our super-stealth SaaS app,
- Create their Subscription record
- Give the user subscriber rights by assigning them a UserRole
- Kick off recurring billing by creating a related BillingSchedule
diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb
new file mode 100644
index 0000000..10c7632
--- /dev/null
+++ b/app/controllers/subscriptions_controller.rb
@@ -0,0 +1,9 @@
+class SubscriptionsController < ActionController::Base
+ def create
+ ActiveRecord::Base.transaction do
+ subscription = Subscription.create!(subscription_params)
+ UserRole.create!(user: current_user, role_type: UserRole::SUBSCRIBER)
+ BillingSchedule.create!(user: current_user, subscription: subscription)
+ end
+ end
+end
This option can be particularly useful when investigating a file that has been partially moved from a previous file, e.g. breaking up a Rails routes file, or code refactoring and extractions like the above example.