Skip to content

Instantly share code, notes, and snippets.

@DaneWeber
Created July 15, 2018 04:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DaneWeber/e9532702434ea366058882be1ff5a3bf to your computer and use it in GitHub Desktop.
Save DaneWeber/e9532702434ea366058882be1ff5a3bf to your computer and use it in GitHub Desktop.
Conflict Manager - check git branches for conflicts early and often
def check_for_rebase
`git checkout master`
master_sha = `git rev-parse HEAD`.strip
@branches.delete_if do |branch, etc|
puts `git checkout #{branch}`
@branches[branch][:sha] = `git rev-parse HEAD`.strip
rebased = `git rebase master 2>&1 > /dev/null`
if rebased == ''
puts '====> ' + branch + ' has no rebase conflicts.'
false
else
puts "====> #{branch} needs to be rebased before comparing with other branches"
`git rebase --abort`
compare_link = branch_compare_url('master', branch)
branch_link = branch_url(branch)
slack_notify(email_1: etc[:author_email],
message: "You might want to <#{compare_link}|rebase> (or <#{branch_link}|delete>) your `#{branch}` branch in *#{@repo}*.",
sha1: @branches[branch][:sha],
sha2: master_sha)
true
end
end
puts "====> #{@branches.length} branches cleanly rebased"
end
def clean_start
puts Dir.pwd
puts `git checkout .`
puts `git checkout master`
puts `git branch --no-color | grep -v master | xargs git branch -D`
puts `git fetch --prune`
puts `git pull`
end
def compare_branches
puts '========================================='
puts 'Ready for combinatorial branch comparison'
puts '========================================='
@branches.keys.combination(2).each do |alpha, beta|
puts "compare #{alpha} with #{beta}"
next true if already_sent?(sha1: @branches[alpha][:sha], sha2: @branches[beta][:sha])
puts `git checkout #{alpha}`
conflicts = `git format-patch $(git merge-base #{alpha} #{beta})..#{beta} --stdout | git apply --check - 2>&1 > /dev/null`.strip
if conflicts.include?('patch does not apply')
link = branch_compare_url(alpha, beta)
slack_notify(email_1: @branches[alpha][:author_email],
email_2: @branches[beta][:author_email],
message: "Are you two working together? Because the `#{alpha}` and `#{beta}` branches of *#{@repo}* will <#{link}|conflict> with each other.",
sha1: @branches[alpha][:sha],
sha2: @branches[beta][:sha])
else
puts "====> #{alpha} and #{beta} are good to merge."
end
end
end
// This defines a cron trigger for a scripted Jenkinsfile:
properties([pipelineTriggers([cron('*/10 8-22 * * 1-5')])])
node {
stage('Checkout Repo with Script') {
checkout scm
}
dir('conflict-checker') {
stage('Check branches for merge conflicts') {
// These were defined in the Jenkinsfile for my particular needs, but don't have to be.
env.webhook_url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
env.slack_channel = '#conflicts'
// If not configured, git will complain about them missing, repeatedly.
sh 'git config --global user.name "Jenkins Read-Only"'
sh 'git config --global user.email "read-only@example.example"'
sh 'ruby branch-conflict-checker.rb'
}
}
}
def store_list_of_sent_messages
return if @new_messages.empty?
Dir.mkdir(CACHE_DIR) unless File.exist?(CACHE_DIR)
Dir.chdir(CACHE_DIR) do
File.write(timestamp + '.yaml', @new_messages.to_yaml)
end
end
def load_lists_of_sent_messages
@new_messages = {}
@old_messages = {}
if File.exist?(CACHE_DIR)
Dir.entries(CACHE_DIR).reject { |entry| ['.', '..'].include?(entry) }.each do |file|
@old_messages.merge!(YAML.load_file(File.join(CACHE_DIR, file)))
puts '====> Loaded message signatures from ' + file
end
end
else
puts '====> No store of sent messages found'
end
end
def already_sent?(sha1:, sha2:)
@old_messages[sha1 + '+' + sha2] == true || @old_messages[sha2 + '+' + sha1] == true
end
def retrieve_branches
@branches = {}
`git for-each-ref --sort=committerdate refs/remotes --format='%(refname:short) %(authoremail)'`.lines[0...BRANCHES_TO_COMPARE].each do |line|
remote_branch, author_email = line.split
branch_name = remote_branch.split('/').last
@branches[branch_name] = { author_email: author_email, remote_branch: remote_branch }
end
@branches.delete('master')
@branches.delete('HEAD')
puts "====> #{@branches.length} branches retrieved"
end
def slack_notify(sha1:, sha2: nil, **args)
if already_sent?(sha1: sha1, sha2: sha2)
puts '====> Conflict previously sent to Slack.'
else
post_to_slack(payload: slack_payload(text: slack_text(args)))
@new_messages[sha1 + '+' + sha2] = true
puts '====> Conflict sent!'
end
end
def post_to_slack(payload:)
uri = URI.parse(ENV['webhook_url'])
object = Net::HTTP.new(uri.host, uri.port)
object.use_ssl = true
object.start do |http|
request = Net::HTTP::Post.new uri
request.set_form_data('payload' => payload)
response = http.request request
end
end
def slack_payload(text:)
<<-JSON
{
"channel": "#{ENV['slack_channel']}",
"username": "Conflict Management",
"text": "#{text}"
}
JSON
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment