Skip to content

Commit

Permalink
Allow for custom commit/merge message templates
Browse files Browse the repository at this point in the history
This introduces a new commit message template (`MERGE_COMMIT_TEMPLATE` ), and adds the ability to define `{{ }}` template tags in both this new template and PR templates.  Anything inside of the `{{ }}` is sent to the `GitReflow` module using `public_send`; so any method available to `GitReflow` is available to templates 😄

Merges #223

LGTM given by: @nhance
  • Loading branch information
codenamev committed Apr 27, 2018
1 parent af251a4 commit 8eb3391
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 36 deletions.
54 changes: 44 additions & 10 deletions lib/git_reflow/git_helpers.rb
Expand Up @@ -41,11 +41,18 @@ def pull_request_template
"#{git_root_dir}/#{file}"
end

filename = filenames_to_try.detect do |file|
File.exist? file
parse_first_matching_template_file(filenames_to_try)
end

def merge_commit_template
filenames_to_try = %w( .github/MERGE_COMMIT_TEMPLATE.md
.github/MERGE_COMMIT_TEMPLATE
MERGE_COMMIT_TEMPLATE.md
MERGE_COMMIT_TEMPLATE ).map do |file|
"#{git_root_dir}/#{file}"
end

File.read filename if filename
parse_first_matching_template_file(filenames_to_try)
end

def get_first_commit_message
Expand Down Expand Up @@ -86,23 +93,50 @@ def update_feature_branch(options = {})
run_command_with_label "git merge #{base_branch}"
end

def append_to_squashed_commit_message(message = '')
tmp_squash_message_path = "#{git_root_dir}/.git/tmp_squash_msg"
squash_message_path = "#{git_root_dir}/.git/SQUASH_MSG"
File.open(tmp_squash_message_path, "w") do |file_content|
def append_to_merge_commit_message(message = '', merge_method: "squash")
tmp_merge_message_path = "#{git_root_dir}/.git/tmp_merge_msg"
dest_merge_message_path = merge_message_path(merge_method: merge_method)

run "touch #{tmp_merge_message_path}"

File.open(tmp_merge_message_path, "w") do |file_content|
file_content.puts message
if File.exists?(squash_message_path)
File.foreach(squash_message_path) do |line|
if File.exists? dest_merge_message_path
File.foreach(dest_merge_message_path) do |line|
file_content.puts line
end
end
end

run "mv #{tmp_squash_message_path} #{squash_message_path}"
run "mv #{tmp_merge_message_path} #{dest_merge_message_path}"
end

def merge_message_path(merge_method: nil)
merge_method = merge_method || GitReflow::Config.get("reflow.merge-method")
merge_method = "squash" if "#{merge_method}".length < 1
if merge_method =~ /squash/i
"#{git_root_dir}/.git/SQUASH_MSG"
else
"#{git_root_dir}/.git/MERGE_MSG"
end
end

private

def parse_first_matching_template_file(template_file_names)
filename = template_file_names.detect do |file|
File.exist? file
end

# Thanks to @Shalmezad for contribuiting the template `gsub` snippet :-)
# https://github.com/reenhanced/gitreflow/issues/51#issuecomment-253535093
if filename
template_content = File.read filename
template_content.gsub!(/\{\{([a-zA-Z_]+[a-zA-Z0-9_]*)\}\}/) { GitReflow.public_send($1) }
template_content
end
end

def extract_remote_user_and_repo_from_remote_url(remote_url)
result = { user: '', repo: '' }
return result unless "#{remote_url}".length > 0
Expand Down
17 changes: 8 additions & 9 deletions lib/git_reflow/git_server/git_hub/pull_request.rb
Expand Up @@ -93,20 +93,22 @@ def merge!(options = {})
if deliver?
GitReflow.say "Merging pull request ##{self.number}: '#{self.title}', from '#{self.feature_branch_name}' into '#{self.base_branch_name}'", :notice

merge_method = options[:merge_method] || GitReflow::Config.get("reflow.merge-method")
merge_method = "squash" if "#{merge_method}".length < 1
merge_message_file = GitReflow.merge_message_path(merge_method: merge_method)

unless options[:title] || options[:message]
# prompts user for commit_title and commit_message
squash_merge_message_file = "#{GitReflow.git_root_dir}/.git/SQUASH_MSG"

File.open(squash_merge_message_file, 'w') do |file|
File.open(merge_message_file, 'w') do |file|
file.write("#{self.title}\n#{self.commit_message_for_merge}\n")
end

GitReflow.run("#{GitReflow.git_editor_command} #{squash_merge_message_file}", with_system: true)
merge_message = File.read(squash_merge_message_file).split(/[\r\n]|\r\n/).map(&:strip)
GitReflow.run("#{GitReflow.git_editor_command} #{merge_message_file}", with_system: true)
merge_message = File.read(merge_message_file).split(/[\r\n]|\r\n/).map(&:strip)

title = merge_message.shift

File.delete(squash_merge_message_file)
File.delete(merge_message_file)

unless merge_message.empty?
merge_message.shift if merge_message.first.empty?
Expand All @@ -124,9 +126,6 @@ def merge!(options = {})

options[:body] = "#{options[:message]}\n" if options[:body].nil? and "#{options[:message]}".length > 0

merge_method = options[:merge_method] || GitReflow::Config.get("reflow.merge-method")
merge_method = "squash" if "#{merge_method}".length < 1

merge_response = GitReflow::GitServer::GitHub.connection.pull_requests.merge(
"#{GitReflow.git_server.class.remote_user}",
"#{GitReflow.git_server.class.remote_repo_name}",
Expand Down
6 changes: 4 additions & 2 deletions lib/git_reflow/git_server/pull_request.rb
Expand Up @@ -158,6 +158,8 @@ def method_missing(method_sym, *arguments, &block)
end

def commit_message_for_merge
return GitReflow.merge_commit_template unless GitReflow.merge_commit_template.nil?

message = ""

if "#{self.description}".length > 0
Expand Down Expand Up @@ -204,14 +206,14 @@ def merge!(options = {})
GitReflow.run_command_with_label "git checkout #{self.base_branch_name}"
GitReflow.run_command_with_label "git pull origin #{self.base_branch_name}"

case merge_method
case merge_method.to_s
when /squash/i
GitReflow.run_command_with_label "git merge --squash #{self.feature_branch_name}"
else
GitReflow.run_command_with_label "git merge #{self.feature_branch_name}"
end

GitReflow.append_to_squashed_commit_message(message) if message.length > 0
GitReflow.append_to_merge_commit_message(message) if message.length > 0

if GitReflow.run_command_with_label 'git commit', with_system: true
GitReflow.say "Pull request ##{self.number} successfully merged.", :success
Expand Down
8 changes: 7 additions & 1 deletion lib/git_reflow/workflows/core.rb
Expand Up @@ -123,7 +123,13 @@ def self.load_raw_workflow(workflow_string)
pull_request_msg_file = "#{GitReflow.git_root_dir}/.git/GIT_REFLOW_PR_MSG"

File.open(pull_request_msg_file, 'w') do |file|
file.write(params[:title] || GitReflow.pull_request_template || GitReflow.current_branch)
begin
pr_message = params[:title] || GitReflow.pull_request_template || GitReflow.current_branch
file.write(pr_message)
rescue StandardError => e
GitReflow.logger.error "Unable to parse PR template (#{pull_request_msg_file}): #{e.inspect}"
file.write(params[:title] || GitReflow.current_branch)
end
end

GitReflow.run("#{GitReflow.git_editor_command} #{pull_request_msg_file}", with_system: true)
Expand Down
99 changes: 89 additions & 10 deletions spec/lib/git_reflow/git_helpers_spec.rb
Expand Up @@ -97,6 +97,68 @@ module Gitacular
end
end

describe ".pull_request_template" do
subject { Gitacular.pull_request_template }

context "template file exists" do
let(:root_dir) { "/some_repo" }
let(:template_content) { "Template content" }

before do
allow(Gitacular).to receive(:git_root_dir).and_return(root_dir)
allow(File).to receive(:exist?).with("#{root_dir}/.github/PULL_REQUEST_TEMPLATE.md").and_return(true)
allow(File).to receive(:read).with("#{root_dir}/.github/PULL_REQUEST_TEMPLATE.md").and_return(template_content)
end

it { is_expected.to eq template_content }

context "when template has mustache tags" do
let(:template_content) { "This is the coolest {{current_branch}}" }
before { allow(GitReflow).to receive(:current_branch).and_return("tomato") }
it { is_expected.to eq "This is the coolest tomato" }
end
end

context "template file does not exist" do
before do
allow(File).to receive(:exist?).and_return(false)
end

it { is_expected.to be_nil }
end
end

describe ".merge_commit_template" do
subject { Gitacular.merge_commit_template }

context "template file exists" do
let(:root_dir) { "/some_repo" }
let(:template_content) { "Template content" }

before do
allow(Gitacular).to receive(:git_root_dir).and_return(root_dir)
allow(File).to receive(:exist?).with("#{root_dir}/.github/MERGE_COMMIT_TEMPLATE.md").and_return(true)
allow(File).to receive(:read).with("#{root_dir}/.github/MERGE_COMMIT_TEMPLATE.md").and_return(template_content)
end

it { is_expected.to eq template_content }

context "when template has mustache tags" do
let(:template_content) { "This is the coolest {{current_branch}}" }
before { allow(GitReflow).to receive(:current_branch).and_return("tomato") }
it { is_expected.to eq "This is the coolest tomato" }
end
end

context "template file does not exist" do
before do
allow(File).to receive(:exist?).and_return(false)
end

it { is_expected.to be_nil }
end
end

describe ".get_first_commit_message" do
subject { Gitacular.get_first_commit_message }
it { expect{ subject }.to have_run_command_silently 'git log --pretty=format:"%s" --no-merges -n 1' }
Expand Down Expand Up @@ -157,26 +219,43 @@ module Gitacular
end
end

describe ".append_to_squashed_commit_message(message)" do
let(:original_squash_message) { "Oooooo, SQUASH IT" }
describe ".append_to_merge_commit_message(message)" do
let(:original_commit_message) { "Oooooo, SQUASH IT" }
let(:message) { "do do the voodoo that you do" }
let(:root_dir) { '/home/gitreflow' }
let(:squash_path) { "#{root_dir}/.git/SQUASH_MSG" }
let(:tmp_squash_path) { "#{root_dir}/.git/tmp_squash_msg" }
let(:merge_message_path) { "#{root_dir}/.git/SQUASH_MSG" }
let(:tmp_merge_message_path) { "#{root_dir}/.git/tmp_merge_msg" }
before { allow(Gitacular).to receive(:git_root_dir).and_return(root_dir) }
subject { Gitacular.append_to_squashed_commit_message(message) }
subject { Gitacular.append_to_merge_commit_message(message) }

it "appends the message to git's SQUASH_MSG temp file" do
tmp_file = double('file')
allow(File).to receive(:open).with(tmp_squash_path, "w").and_yield(tmp_file)
allow(File).to receive(:exists?).with(squash_path).and_return(true)
allow(File).to receive(:foreach).with(squash_path).and_yield(original_squash_message)
allow(File).to receive(:open).with(tmp_merge_message_path, "w").and_yield(tmp_file)
allow(File).to receive(:exists?).with(merge_message_path).and_return(true)
allow(File).to receive(:foreach).with(merge_message_path).and_yield(original_commit_message)
expect(tmp_file).to receive(:puts).with(message)
expect(tmp_file).to receive(:puts).with(original_squash_message)
expect(tmp_file).to receive(:puts).with(original_commit_message)

expect { subject }.to have_run_commands_in_order [
"mv #{tmp_squash_path} #{squash_path}"
"mv #{tmp_merge_message_path} #{merge_message_path}"
]
end

context "when doing a direct merge" do
let(:merge_message_path) { "#{root_dir}/.git/MERGE_MSG" }
subject { Gitacular.append_to_merge_commit_message(message, merge_method: "merge") }
it "appends the message to git's MERGE_MSG temp file if using a direct merge" do
tmp_file = double('file')
allow(File).to receive(:open).with(tmp_merge_message_path, "w").and_yield(tmp_file)
allow(File).to receive(:exists?).with(merge_message_path).and_return(true)
allow(File).to receive(:foreach).with(merge_message_path).and_yield(original_commit_message)
expect(tmp_file).to receive(:puts).with(message)
expect(tmp_file).to receive(:puts).with(original_commit_message)

expect { subject }.to have_run_commands_in_order [
"mv #{tmp_merge_message_path} #{merge_message_path}"
]
end
end
end
end
Expand Up @@ -484,7 +484,7 @@
allow(GitReflow.git_server).to receive(:connection).and_return(github_api)
allow(GitReflow.git_server).to receive(:get_build_status).and_return(Struct.new(:state, :description, :target_url).new())
allow_any_instance_of(GitReflow::GitServer::PullRequest).to receive(:commit_message_for_merge).and_return('Bingo')
allow_any_instance_of(GitReflow).to receive(:append_to_squashed_commit_message).and_return(true)
allow_any_instance_of(GitReflow).to receive(:append_to_merge_commit_message).and_return(true)
end

context "and force-merging" do
Expand Down
10 changes: 8 additions & 2 deletions spec/lib/git_reflow/git_server/pull_request_spec.rb
Expand Up @@ -364,7 +364,7 @@
subject { pr.merge! inputs }

before do
allow(GitReflow).to receive(:append_to_squashed_commit_message)
allow(GitReflow).to receive(:append_to_merge_commit_message)
allow(pr).to receive(:commit_message_for_merge).and_return(commit_message_for_merge)
end

Expand All @@ -376,7 +376,7 @@
it "updates both feature and destination branch and squash-merges feature into base branch" do
expect(GitReflow).to receive(:update_current_branch)
expect(GitReflow).to receive(:fetch_destination).with(pr.base_branch_name)
expect(GitReflow).to receive(:append_to_squashed_commit_message).with(pr.commit_message_for_merge)
expect(GitReflow).to receive(:append_to_merge_commit_message).with(pr.commit_message_for_merge)
expect { subject }.to have_run_commands_in_order [
"git checkout #{pr.base_branch_name}",
"git pull origin #{pr.base_branch_name}",
Expand Down Expand Up @@ -470,6 +470,12 @@
before { allow(pr).to receive(:approvals).and_return(['sally', 'joey']) }
specify { expect(subject).to include "\nLGTM given by: @sally, @joey\n" }
end

context "with custom merge commit message template" do
before { allow(GitReflow).to receive(:merge_commit_template).and_return("Super cool changes") }
specify { expect(subject).to include "Super cool changes" }
specify { expect(subject).to_not include "\nMerges ##{pr.number}\n" }
end
end

context "#cleanup_feature_branch?" do
Expand Down
1 change: 0 additions & 1 deletion spec/lib/git_reflow/workflows/core_spec.rb
Expand Up @@ -222,7 +222,6 @@ class CoreWorkflow < GitReflow::Workflows::Core
expect(fake_file).to receive(:write).with(template_content)
subject
end

end

context "providing a base branch" do
Expand Down

0 comments on commit 8eb3391

Please sign in to comment.