mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
202 lines
7.8 KiB
Python
202 lines
7.8 KiB
Python
# Copyright (C) 2013 Google Inc. All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are
|
|
# met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above
|
|
# copyright notice, this list of conditions and the following disclaimer
|
|
# in the documentation and/or other materials provided with the
|
|
# distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import logging
|
|
import re
|
|
import threading
|
|
import time
|
|
|
|
from webkitpy.common.checkout.scm.git import Git
|
|
from webkitpy.common.config.irc import server, port, channel, nickname
|
|
from webkitpy.common.config.irc import update_wait_seconds, retry_attempts
|
|
from webkitpy.common.system.executive import ScriptError
|
|
from webkitpy.thirdparty.irc.ircbot import SingleServerIRCBot
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
|
|
class CommitAnnouncer(SingleServerIRCBot):
|
|
_commit_detail_format = "%H\n%cn\n%s\n%b" # commit-sha1, author, subject, body
|
|
|
|
def __init__(self, tool, irc_password):
|
|
SingleServerIRCBot.__init__(self, [(server, port, irc_password)], nickname, nickname)
|
|
self.git = Git(cwd=tool.scm().checkout_root, filesystem=tool.filesystem, executive=tool.executive)
|
|
self.commands = {
|
|
'help': self.help,
|
|
'quit': self.stop,
|
|
}
|
|
|
|
def start(self):
|
|
if not self._update():
|
|
return
|
|
self.last_commit = self.git.latest_git_commit()
|
|
SingleServerIRCBot.start(self)
|
|
|
|
def post_new_commits(self):
|
|
if not self.connection.is_connected():
|
|
return
|
|
if not self._update(force_clean=True):
|
|
self.stop("Failed to update repository!")
|
|
return
|
|
new_commits = self.git.git_commits_since(self.last_commit)
|
|
if new_commits:
|
|
self.last_commit = new_commits[-1]
|
|
for commit in new_commits:
|
|
commit_detail = self._commit_detail(commit)
|
|
if commit_detail:
|
|
_log.info('%s Posting commit %s' % (self._time(), commit))
|
|
_log.info('%s Posted message: %s' % (self._time(), repr(commit_detail)))
|
|
self._post(commit_detail)
|
|
else:
|
|
_log.error('Malformed commit log for %s' % commit)
|
|
|
|
# Bot commands.
|
|
|
|
def help(self):
|
|
self._post('Commands available: %s' % ' '.join(self.commands.keys()))
|
|
|
|
def stop(self, message=""):
|
|
self.connection.execute_delayed(0, lambda: self.die(message))
|
|
|
|
# IRC event handlers.
|
|
|
|
def on_nicknameinuse(self, connection, event):
|
|
connection.nick('%s_' % connection.get_nickname())
|
|
|
|
def on_welcome(self, connection, event):
|
|
connection.join(channel)
|
|
|
|
def on_pubmsg(self, connection, event):
|
|
message = event.arguments()[0]
|
|
command = self._message_command(message)
|
|
if command:
|
|
command()
|
|
|
|
def _update(self, force_clean=False):
|
|
if not self.git.is_cleanly_tracking_remote_master():
|
|
if not force_clean:
|
|
confirm = raw_input('This repository has local changes, continue? (uncommitted changes will be lost) y/n: ')
|
|
if not confirm.lower() == 'y':
|
|
return False
|
|
try:
|
|
self.git.ensure_cleanly_tracking_remote_master()
|
|
except ScriptError, e:
|
|
_log.error('Failed to clean repository: %s' % e)
|
|
return False
|
|
|
|
attempts = 1
|
|
while attempts <= retry_attempts:
|
|
if attempts > 1:
|
|
# User may have sent a keyboard interrupt during the wait.
|
|
if not self.connection.is_connected():
|
|
return False
|
|
wait = int(update_wait_seconds) << (attempts - 1)
|
|
if wait < 120:
|
|
_log.info('Waiting %s seconds' % wait)
|
|
else:
|
|
_log.info('Waiting %s minutes' % (wait / 60))
|
|
time.sleep(wait)
|
|
_log.info('Pull attempt %s out of %s' % (attempts, retry_attempts))
|
|
try:
|
|
self.git.pull()
|
|
return True
|
|
except ScriptError, e:
|
|
_log.error('Error pulling from server: %s' % e)
|
|
_log.error('Output: %s' % e.output)
|
|
attempts += 1
|
|
_log.error('Exceeded pull attempts')
|
|
_log.error('Aborting at time: %s' % self._time())
|
|
return False
|
|
|
|
def _time(self):
|
|
return time.strftime('[%x %X %Z]', time.localtime())
|
|
|
|
def _message_command(self, message):
|
|
prefix = '%s:' % self.connection.get_nickname()
|
|
if message.startswith(prefix):
|
|
command_name = message[len(prefix):].strip()
|
|
if command_name in self.commands:
|
|
return self.commands[command_name]
|
|
return None
|
|
|
|
def _commit_detail(self, commit):
|
|
return self._format_commit_detail(self.git.git_commit_detail(commit, self._commit_detail_format))
|
|
|
|
def _format_commit_detail(self, commit_detail):
|
|
if commit_detail.count('\n') < self._commit_detail_format.count('\n'):
|
|
return ''
|
|
|
|
commit, email, subject, body = commit_detail.split('\n', 3)
|
|
review_string = 'Review URL: '
|
|
svn_string = 'git-svn-id: svn://svn.chromium.org/blink/trunk@'
|
|
red_flag_strings = ['NOTRY=true', 'TBR=']
|
|
review_url = ''
|
|
svn_revision = ''
|
|
red_flags = []
|
|
|
|
for line in body.split('\n'):
|
|
if line.startswith(review_string):
|
|
review_url = line[len(review_string):]
|
|
if line.startswith(svn_string):
|
|
tokens = line[len(svn_string):].split()
|
|
if not tokens:
|
|
continue
|
|
revision = tokens[0]
|
|
if not revision.isdigit():
|
|
continue
|
|
svn_revision = 'r%s' % revision
|
|
for red_flag_string in red_flag_strings:
|
|
if line.lower().startswith(red_flag_string.lower()):
|
|
red_flags.append(line.strip())
|
|
|
|
if review_url:
|
|
match = re.search(r'(?P<review_id>\d+)', review_url)
|
|
if match:
|
|
review_url = 'http://crrev.com/%s' % match.group('review_id')
|
|
first_url = review_url if review_url else 'https://chromium.googlesource.com/chromium/blink/+/%s' % commit[:8]
|
|
|
|
red_flag_message = '\x037%s\x03' % (' '.join(red_flags)) if red_flags else ''
|
|
|
|
return ('%s %s %s committed "%s" %s' % (svn_revision, first_url, email, subject, red_flag_message)).strip()
|
|
|
|
def _post(self, message):
|
|
self.connection.execute_delayed(0, lambda: self.connection.privmsg(channel, self._sanitize_string(message)))
|
|
|
|
def _sanitize_string(self, message):
|
|
return message.encode('ascii', 'backslashreplace')
|
|
|
|
|
|
class CommitAnnouncerThread(threading.Thread):
|
|
def __init__(self, tool, irc_password):
|
|
threading.Thread.__init__(self)
|
|
self.bot = CommitAnnouncer(tool, irc_password)
|
|
|
|
def run(self):
|
|
self.bot.start()
|
|
|
|
def stop(self):
|
|
self.bot.stop()
|
|
self.join()
|