summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/tool/bot/irc_command.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/tool/bot/irc_command.py')
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/irc_command.py235
1 files changed, 152 insertions, 83 deletions
diff --git a/Tools/Scripts/webkitpy/tool/bot/irc_command.py b/Tools/Scripts/webkitpy/tool/bot/irc_command.py
index 1c061a8db..9b9915769 100644
--- a/Tools/Scripts/webkitpy/tool/bot/irc_command.py
+++ b/Tools/Scripts/webkitpy/tool/bot/irc_command.py
@@ -33,6 +33,7 @@ import re
from webkitpy.common.config import irc as config_irc
from webkitpy.common.config import urls
from webkitpy.common.config.committers import CommitterList
+from webkitpy.common.net.web import Web
from webkitpy.common.system.executive import ScriptError
from webkitpy.tool.bot.queueengine import TerminateQueue
from webkitpy.tool.grammar import join_with_separators
@@ -48,19 +49,121 @@ def _post_error_and_check_for_bug_url(tool, nicks_string, exception):
# FIXME: Merge with Command?
class IRCCommand(object):
+ usage_string = None
+ help_string = None
+
+ def execute(self, nick, args, tool, sheriff):
+ raise NotImplementedError("subclasses must implement")
+
+ @classmethod
+ def usage(cls, nick):
+ return "%s: Usage: %s" % (nick, cls.usage_string)
+
+ @classmethod
+ def help(cls, nick):
+ return "%s: %s" % (nick, cls.help_string)
+
+
+class CreateBug(IRCCommand):
+ usage_string = "create-bug BUG_TITLE"
+ help_string = "Creates a Bugzilla bug with the given title."
+
+ def execute(self, nick, args, tool, sheriff):
+ if not args:
+ return self.usage(nick)
+
+ bug_title = " ".join(args)
+ bug_description = "%s\nRequested by %s on %s." % (bug_title, nick, config_irc.channel)
+
+ # There happens to be a committers list hung off of Bugzilla, so
+ # re-using that one makes things easiest for now.
+ requester = tool.bugs.committers.contributor_by_irc_nickname(nick)
+ requester_email = requester.bugzilla_email() if requester else None
+
+ try:
+ bug_id = tool.bugs.create_bug(bug_title, bug_description, cc=requester_email, assignee=requester_email)
+ bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
+ return "%s: Created bug: %s" % (nick, bug_url)
+ except Exception, e:
+ return "%s: Failed to create bug:\n%s" % (nick, e)
+
+
+class Help(IRCCommand):
+ usage_string = "help [COMMAND]"
+ help_string = "Provides help on my individual commands."
+
+ def execute(self, nick, args, tool, sheriff):
+ if args:
+ for command_name in args:
+ if command_name in commands:
+ self._post_command_help(nick, tool, commands[command_name])
+ else:
+ tool.irc().post("%s: Available commands: %s" % (nick, ", ".join(sorted(visible_commands.keys()))))
+ tool.irc().post('%s: Type "%s: help COMMAND" for help on my individual commands.' % (nick, sheriff.name()))
+
+ def _post_command_help(self, nick, tool, command):
+ tool.irc().post(command.usage(nick))
+ tool.irc().post(command.help(nick))
+ aliases = " ".join(sorted(filter(lambda alias: commands[alias] == command and alias not in visible_commands, commands)))
+ if aliases:
+ tool.irc().post("%s: Aliases: %s" % (nick, aliases))
+
+
+class Hi(IRCCommand):
+ usage_string = "hi"
+ help_string = "Responds with hi."
+
+ def execute(self, nick, args, tool, sheriff):
+ if len(args) and re.match(sheriff.name() + r'_*\s*!\s*', ' '.join(args)):
+ return "%s: hi %s!" % (nick, nick)
+ quips = tool.bugs.quips()
+ quips.append('"Only you can prevent forest fires." -- Smokey the Bear')
+ return random.choice(quips)
+
+
+class PingPong(IRCCommand):
+ usage_string = "ping"
+ help_string = "Responds with pong."
+
def execute(self, nick, args, tool, sheriff):
- raise NotImplementedError, "subclasses must implement"
+ return nick + ": pong"
+
+
+class YouThere(IRCCommand):
+ usage_string = "yt?"
+ help_string = "Responds with yes."
+
+ def execute(self, nick, args, tool, sheriff):
+ return "%s: yes" % nick
class Restart(IRCCommand):
+ usage_string = "restart"
+ help_string = "Restarts sherrifbot. Will update its WebKit checkout, and re-join the channel momentarily."
+
def execute(self, nick, args, tool, sheriff):
tool.irc().post("Restarting...")
raise TerminateQueue()
+class RollChromiumDEPS(IRCCommand):
+ usage_string = "roll-chromium-deps REVISION"
+ help_string = "Rolls WebKit's Chromium DEPS to the given revision???"
+
+ def execute(self, nick, args, tool, sheriff):
+ if not len(args):
+ return self.usage(nick)
+ tool.irc().post("%s: Will roll Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Rolling Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Rolled Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Thank You" % nick)
+
+
class Rollout(IRCCommand):
- def _extract_revisions(self, arg):
+ usage_string = "rollout SVN_REVISION [SVN_REVISIONS] REASON"
+ help_string = "Opens a rollout bug, CCing author + reviewer, and attaching the reverse-diff of the given revisions marked as commit-queue=?."
+ def _extract_revisions(self, arg):
revision_list = []
possible_revisions = arg.split(",")
for revision in possible_revisions:
@@ -110,15 +213,28 @@ class Rollout(IRCCommand):
return ", ".join(target_nicks)
def _update_working_copy(self, tool):
- tool.scm().ensure_clean_working_directory(force_clean=True)
- tool.executive.run_and_throw_if_fail(tool.port().update_webkit_command(), quiet=True, cwd=tool.scm().checkout_root)
+ tool.scm().discard_local_changes()
+ tool.executive.run_and_throw_if_fail(tool.deprecated_port().update_webkit_command(), quiet=True, cwd=tool.scm().checkout_root)
+
+ def _check_diff_failure(self, error_log, tool):
+ if not error_log:
+ return None
+
+ revert_failure_message_start = error_log.find("Failed to apply reverse diff for revision")
+ if revert_failure_message_start == -1:
+ return None
+
+ lines = error_log[revert_failure_message_start:].split('\n')[1:]
+ files = itertools.takewhile(lambda line: tool.filesystem.exists(tool.scm().absolute_path(line)), lines)
+ if files:
+ return "Failed to apply reverse diff for file(s): %s" % ", ".join(files)
+ return None
def execute(self, nick, args, tool, sheriff):
svn_revision_list, rollout_reason = self._parse_args(args)
if (not svn_revision_list or not rollout_reason):
- # return is equivalent to an irc().post(), but makes for easier unit testing.
- return "%s: Usage: rollout SVN_REVISION [SVN_REVISIONS] REASON" % nick
+ return self.usage(nick)
revision_urls_string = join_with_separators([urls.view_revision_url(revision) for revision in svn_revision_list])
tool.irc().post("%s: Preparing rollout for %s ..." % (nick, revision_urls_string))
@@ -137,109 +253,60 @@ class Rollout(IRCCommand):
tool.irc().post("%s: Created rollout: %s" % (nicks_string, bug_url))
except ScriptError, e:
tool.irc().post("%s: Failed to create rollout patch:" % nicks_string)
+ diff_failure = self._check_diff_failure(e.output, tool)
+ if diff_failure:
+ return "%s: %s" % (nicks_string, diff_failure)
_post_error_and_check_for_bug_url(tool, nicks_string, e)
-class RollChromiumDEPS(IRCCommand):
- def _parse_args(self, args):
- if not args:
- return
- revision = args[0].lstrip("r")
- if not revision.isdigit():
- return
- return revision
-
- def execute(self, nick, args, tool, sheriff):
- revision = self._parse_args(args)
-
- roll_target = "r%s" % revision if revision else "last-known good revision"
- tool.irc().post("%s: Rolling Chromium DEPS to %s" % (nick, roll_target))
-
- try:
- bug_id = sheriff.post_chromium_deps_roll(revision, roll_target)
- bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
- tool.irc().post("%s: Created DEPS roll: %s" % (nick, bug_url))
- except ScriptError, e:
- match = re.search(r"Current Chromium DEPS revision \d+ is newer than \d+\.", e.output)
- if match:
- tool.irc().post("%s: %s" % (nick, match.group(0)))
- return
- tool.irc().post("%s: Failed to create DEPS roll:" % nick)
- _post_error_and_check_for_bug_url(tool, nick, e)
-
-
-class Help(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- return "%s: Available commands: %s" % (nick, ", ".join(sorted(visible_commands.keys())))
+class Whois(IRCCommand):
+ usage_string = "whois SEARCH_STRING"
+ help_string = "Searches known contributors and returns any matches with irc, email and full name. Wild card * permitted."
+ def _full_record_and_nick(self, contributor):
+ result = ''
-class Hi(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- quips = tool.bugs.quips()
- quips.append('"Only you can prevent forest fires." -- Smokey the Bear')
- return random.choice(quips)
+ if contributor.irc_nicknames:
+ result += ' (:%s)' % ', :'.join(contributor.irc_nicknames)
+ if contributor.can_review:
+ result += ' (r)'
+ elif contributor.can_commit:
+ result += ' (c)'
-class Whois(IRCCommand):
- def _nick_or_full_record(self, contributor):
- if contributor.irc_nicknames:
- return ', '.join(contributor.irc_nicknames)
- return unicode(contributor)
+ return unicode(contributor) + result
def execute(self, nick, args, tool, sheriff):
- if len(args) != 1:
- return "%s: Usage: whois SEARCH_STRING" % nick
- search_string = args[0]
+ if not args:
+ return self.usage(nick)
+ search_string = unicode(" ".join(args))
# FIXME: We should get the ContributorList off the tool somewhere.
contributors = CommitterList().contributors_by_search_string(search_string)
if not contributors:
- return "%s: Sorry, I don't know any contributors matching '%s'." % (nick, search_string)
+ return unicode("%s: Sorry, I don't know any contributors matching '%s'.") % (nick, search_string)
if len(contributors) > 5:
- return "%s: More than 5 contributors match '%s', could you be more specific?" % (nick, search_string)
+ return unicode("%s: More than 5 contributors match '%s', could you be more specific?") % (nick, search_string)
if len(contributors) == 1:
contributor = contributors[0]
if not contributor.irc_nicknames:
- return "%s: %s hasn't told me their nick. Boo hoo :-(" % (nick, contributor)
- if contributor.emails and search_string.lower() not in map(lambda email: email.lower(), contributor.emails):
- formattedEmails = ', '.join(contributor.emails)
- return "%s: %s is %s (%s). Why do you ask?" % (nick, search_string, self._nick_or_full_record(contributor), formattedEmails)
- else:
- return "%s: %s is %s. Why do you ask?" % (nick, search_string, self._nick_or_full_record(contributor))
- contributor_nicks = map(self._nick_or_full_record, contributors)
+ return unicode("%s: %s hasn't told me their nick. Boo hoo :-(") % (nick, contributor)
+ return unicode("%s: %s is %s. Why do you ask?") % (nick, search_string, self._full_record_and_nick(contributor))
+ contributor_nicks = map(self._full_record_and_nick, contributors)
contributors_string = join_with_separators(contributor_nicks, only_two_separator=" or ", last_separator=', or ')
- return "%s: I'm not sure who you mean? %s could be '%s'." % (nick, contributors_string, search_string)
-
-
-class CreateBug(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- if not args:
- return "%s: Usage: create-bug BUG_TITLE" % nick
-
- bug_title = " ".join(args)
- bug_description = "%s\nRequested by %s on %s." % (bug_title, nick, config_irc.channel)
-
- # There happens to be a committers list hung off of Bugzilla, so
- # re-using that one makes things easiest for now.
- requester = tool.bugs.committers.contributor_by_irc_nickname(nick)
- requester_email = requester.bugzilla_email() if requester else None
-
- try:
- bug_id = tool.bugs.create_bug(bug_title, bug_description, cc=requester_email, assignee=requester_email)
- bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
- return "%s: Created bug: %s" % (nick, bug_url)
- except Exception, e:
- return "%s: Failed to create bug:\n%s" % (nick, e)
+ return unicode("%s: I'm not sure who you mean? %s could be '%s'.") % (nick, contributors_string, search_string)
# FIXME: Lame. We should have an auto-registering CommandCenter.
visible_commands = {
+ "create-bug": CreateBug,
"help": Help,
"hi": Hi,
+ "ping": PingPong,
"restart": Restart,
+ "roll-chromium-deps": RollChromiumDEPS,
"rollout": Rollout,
"whois": Whois,
- "create-bug": CreateBug,
- "roll-chromium-deps": RollChromiumDEPS,
+ "yt?": YouThere,
}
# Add revert as an "easter egg" command. Why?
@@ -248,3 +315,5 @@ visible_commands = {
# people to use and it seems silly to have them hunt around for "rollout" instead.
commands = visible_commands.copy()
commands["revert"] = Rollout
+# "hello" Alias for "hi" command for the purposes of testing aliases
+commands["hello"] = Hi