Skip to content

Instantly share code, notes, and snippets.

@markjaquith
Last active October 13, 2015 15:48
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save markjaquith/4219135 to your computer and use it in GitHub Desktop.
Save markjaquith/4219135 to your computer and use it in GitHub Desktop.
Script for applying WordPress patches. Provide an ID (prompts you to select patch), a Trac patch URL, or a raw Trac patch URL
#!/usr/bin/ruby
#
# The MIT License (MIT)
# Copyright (c) 2013 Mark Jaquith
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
require 'open-uri'
# Extend String so we can do a_string.bold
class String
def bold
"\033[1m#{self}\033[0m"
end
end
def prompt(default, *args)
print(*args)
result = STDIN.gets.strip
return result.empty? ? default : result
end
def quit_with message
puts message
exit
end
def apply_patch patch_url
temp_file = 'wppatch.diff'
`curl -s '#{patch_url}' > #{temp_file}`
patch_level = catch :level do
File.open temp_file do |file|
file.each_line do |line|
if line.start_with? 'diff --git a/'
throw :level, 1
elsif line.start_with? 'Index: src/'
throw :level, 1
elsif line.start_with? '+++ src/'
throw :level, 1
end
end
end
throw :level, 0
end
command = "patch -p#{patch_level} < #{temp_file} && rm #{temp_file}"
command = "cd src; patch -p#{patch_level} < #{temp_file} && rm #{temp_file}" if File.directory? 'src'
puts `#{command}`
end
def patch_from_ticket ticket
# Craft the ticket URL
ticket_url = "https://core.trac.wordpress.org/attachment/ticket/#{ticket}/"
# Set up the regexp
attachment_matcher = %r{/raw-attachment/ticket/#{ticket}/([^"]+)"\s+class="trac-rawlink"\s+title="Download">​</a>\s+\(<span title="[^"]+">[^<]+</span>\)\s+-\s+added by <em>([^<]+)</em>\s+<a class="timeline"[^>]+>([^<]+) ago</a>\.}
patches = []
# Grab the ticket
open ticket_url do |t|
html = t.read
html.scan(attachment_matcher) do |match|
# zip the match into a hash
patches << Hash[[:file, :author, :ago].zip(match)]
end
end
quit_with "No patches found on that ticket" if patches.count == 0
patch = choose_from patches
quit_with "Not a valid patch choice" if patch.nil?
"https://core.trac.wordpress.org/raw-attachment/ticket/#{ticket}/#{patch[:file]}"
end
def choose_from patches
return patches.pop if patches.count == 1
longest_file = patches.max {|a, b| a[:file].length <=> b[:file].length }[:file].length
longest_author = patches.max {|a, b| a[:author].length <=> b[:author].length }[:author].length
line_length = 5 + longest_file + 2 + longest_author + 2 + 13
puts "#{'=' * line_length}".bold
patches.each_with_index do |patch, index|
index = index + 1
puts "#{index.to_s.rjust( patches.count.to_s.length + 2 ).bold}: #{patch[:file].ljust(longest_file).bold} #{patch[:author].ljust(longest_author)} #{patch[:ago]} ago"
end
puts "#{'=' * line_length}".bold
choice = prompt nil, "Patch number to apply [latest by default]: "
choice = patches.length if choice.nil?
return nil if (choice.to_i < 1 || patches[choice.to_i - 1].nil? )
patches[choice.to_i - 1]
end
ticket = ( ARGV[0].nil? ) ? nil : ARGV[0].chomp;
# No ticket provided? Check the current git branch for the pattern: ticket-12345
ticket = `git symbolic-ref HEAD 2>/dev/null`[%r{/ticket-([0-9]+)\Z}, 1] if ticket.nil?
# They must pass a ticket number as the first arg
quit_with "You must supply a ticket number" if ticket.nil?
# Ticket must be an integer or one of a series of valid Trac URLs
if ! ticket.match( %r{\A[0-9]+\Z} ).nil?
# Ticket number
patch_url = patch_from_ticket ticket
elsif ! ticket.match( %r{https?://core.trac.wordpress.org/raw-attachment/} ).nil?
# Raw patch
patch_url = ticket
elsif ! ticket.match( %r{https?://core.trac.wordpress.org/attachment/} ).nil?
# Link to patch's page
patch_url = ticket.sub 'wordpress.org/attachment/', 'wordpress.org/raw-attachment/'
else
quit_with "Ticket number must be an integer or a Trac patch URL"
end
# Still here?
apply_patch patch_url
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment