class CommitAnnouncer(SingleServerIRCBot): _commit_detail_format = "%H\n%ae\n%s\n%b" # commit-sha1, author email, subject, body def __init__(self, tool, announce_path, irc_password): SingleServerIRCBot.__init__(self, [(server, port, irc_password)], nickname, nickname) self.announce_path = announce_path 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 not new_commits: return self.last_commit = new_commits[-1] for commit in new_commits: if not self._should_announce_commit(commit): continue 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
class CommitAnnouncer(SingleServerIRCBot): _commit_detail_format = "%H\n%ae\n%s\n%b" # commit-sha1, author email, subject, body def __init__(self, tool, announce_path, irc_password): SingleServerIRCBot.__init__(self, [(SERVER, PORT, irc_password)], NICKNAME, NICKNAME) self.announce_path = announce_path self.git = Git(cwd=tool.scm().checkout_root, filesystem=tool.filesystem, executive=tool.executive) self.commands = { 'help': self.help, 'ping': self.ping, 'quit': self.stop, } self.last_commit = None 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 not new_commits: return self.last_commit = new_commits[-1] for commit in new_commits: if not self._should_announce_commit(commit): continue 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 ping(self): self._post('Pong.') def stop(self, message=""): self.connection.execute_delayed(0, lambda: self.die(message)) # IRC event handlers. Methods' arguments are determined by superclass # and some arguments maybe unused - pylint: disable=unused-argument 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 as 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(timeout_seconds=PULL_TIMEOUT_SECONDS) return True except ScriptError as 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 _should_announce_commit(self, commit): return any( path.startswith(self.announce_path) for path in self.git.affected_files(commit)) 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) commit_position_re = r'^Cr-Commit-Position: refs/heads/master@\{#(?P<commit_position>\d+)\}' commit_position = None red_flag_strings = ['NOTRY=true', 'TBR='] red_flags = [] for line in body.split('\n'): match = re.search(commit_position_re, line) if match: commit_position = match.group('commit_position') for red_flag_string in red_flag_strings: if line.lower().startswith(red_flag_string.lower()): red_flags.append(line.strip()) url = 'https://crrev.com/%s' % (commit_position if commit_position else commit[:8]) red_flag_message = '\x037%s\x03' % ( ' '.join(red_flags)) if red_flags else '' return ('%s %s committed "%s" %s' % (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 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)) 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
class CommitAnnouncer(SingleServerIRCBot): _commit_detail_format = "%H\n%ae\n%s\n%b" # commit-sha1, author email, subject, body def __init__(self, tool, announce_path, irc_password): SingleServerIRCBot.__init__(self, [(SERVER, PORT, irc_password)], NICKNAME, NICKNAME) self.announce_path = announce_path self.git = Git(cwd=tool.scm().checkout_root, filesystem=tool.filesystem, executive=tool.executive) self.commands = { 'help': self.help, 'ping': self.ping, 'quit': self.stop, } self.last_commit = None 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 not new_commits: return self.last_commit = new_commits[-1] for commit in new_commits: if not self._should_announce_commit(commit): continue 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 ping(self): self._post('Pong.') def stop(self, message=""): self.connection.execute_delayed(0, lambda: self.die(message)) # IRC event handlers. Methods' arguments are determined by superclass # and some arguments maybe unused - pylint: disable=unused-argument 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 as 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(timeout_seconds=PULL_TIMEOUT_SECONDS) return True except ScriptError as 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 _should_announce_commit(self, commit): return any(path.startswith(self.announce_path) for path in self.git.affected_files(commit)) 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) commit_position_re = r'^Cr-Commit-Position: refs/heads/master@\{#(?P<commit_position>\d+)\}' commit_position = None red_flag_strings = ['NOTRY=true', 'TBR='] red_flags = [] for line in body.split('\n'): match = re.search(commit_position_re, line) if match: commit_position = match.group('commit_position') for red_flag_string in red_flag_strings: if line.lower().startswith(red_flag_string.lower()): red_flags.append(line.strip()) url = 'https://crrev.com/%s' % (commit_position if commit_position else commit[:8]) red_flag_message = '\x037%s\x03' % (' '.join(red_flags)) if red_flags else '' return ('%s %s committed "%s" %s' % (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')