Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commit_announcer.py

Issue 2797913002: Remove the commit-announcer command from webkit-patch (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (C) 2013 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #
14 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 import logging
27 import re
28 import threading
29 import time
30
31 from webkitpy.common.system.executive import ScriptError
32 from webkitpy.thirdparty.irc.ircbot import SingleServerIRCBot
33
34 _log = logging.getLogger(__name__)
35
36 SERVER = 'irc.freenode.net'
37 PORT = 6667
38 CHANNEL = '#blink'
39 NICKNAME = 'commit-bot'
40
41 PULL_TIMEOUT_SECONDS = 60 * 5
42 UPDATE_WAIT_SECONDS = 10
43 RETRY_ATTEMPTS = 8
44
45
46 class CommitAnnouncer(SingleServerIRCBot):
47 _commit_detail_format = '%H\n%ae\n%s\n%b' # commit-sha1, author email, subj ect, body
48
49 def __init__(self, tool, announce_path, irc_password):
50 SingleServerIRCBot.__init__(self, [(SERVER, PORT, irc_password)], NICKNA ME, NICKNAME)
51 self.announce_path = announce_path
52 self.git = tool.git(path=tool.git().checkout_root)
53 self.commands = {
54 'help': self.help,
55 'ping': self.ping,
56 'quit': self.stop,
57 }
58 self.last_commit = None
59
60 def start(self):
61 if not self._update():
62 return
63 self.last_commit = self.git.latest_git_commit()
64 SingleServerIRCBot.start(self)
65
66 def post_new_commits(self):
67 if not self.connection.is_connected():
68 return
69 if not self._update(force_clean=True):
70 self.stop('Failed to update repository!')
71 return
72 new_commits = self.git.git_commits_since(self.last_commit)
73 if not new_commits:
74 return
75 self.last_commit = new_commits[-1]
76 for commit in new_commits:
77 if not self._should_announce_commit(commit):
78 continue
79 commit_detail = self._commit_detail(commit)
80 if commit_detail:
81 _log.info('%s Posting commit %s', self._time(), commit)
82 _log.info('%s Posted message: %s', self._time(), repr(commit_det ail))
83 self._post(commit_detail)
84 else:
85 _log.error('Malformed commit log for %s', commit)
86
87 # Bot commands.
88
89 def help(self):
90 self._post('Commands available: %s' % ' '.join(self.commands.keys()))
91
92 def ping(self):
93 self._post('Pong.')
94
95 def stop(self, message=''):
96 self.connection.execute_delayed(0, lambda: self.die(message))
97
98 # IRC event handlers. Methods' arguments are determined by superclass
99 # and some arguments maybe unused - pylint: disable=unused-argument
100
101 def on_nicknameinuse(self, connection, event):
102 connection.nick('%s_' % connection.get_nickname())
103
104 def on_welcome(self, connection, event):
105 connection.join(CHANNEL)
106
107 def on_pubmsg(self, connection, event):
108 message = event.arguments()[0]
109 command = self._message_command(message)
110 if command:
111 command()
112
113 def _update(self, force_clean=False):
114 if not self.git.is_cleanly_tracking_remote_master():
115 if not force_clean:
116 confirm = raw_input('This repository has local changes, continue ? (uncommitted changes will be lost) y/n: ')
117 if not confirm.lower() == 'y':
118 return False
119 try:
120 self.git.ensure_cleanly_tracking_remote_master()
121 except ScriptError as error:
122 _log.error('Failed to clean repository: %s', error)
123 return False
124
125 attempts = 1
126 while attempts <= RETRY_ATTEMPTS:
127 if attempts > 1:
128 # User may have sent a keyboard interrupt during the wait.
129 if not self.connection.is_connected():
130 return False
131 wait = int(UPDATE_WAIT_SECONDS) << (attempts - 1)
132 if wait < 120:
133 _log.info('Waiting %s seconds', wait)
134 else:
135 _log.info('Waiting %s minutes', wait / 60)
136 time.sleep(wait)
137 _log.info('Pull attempt %s out of %s', attempts, RETRY_ATTEMPTS)
138 try:
139 self.git.pull(timeout_seconds=PULL_TIMEOUT_SECONDS)
140 return True
141 except ScriptError as error:
142 _log.error('Error pulling from server: %s', error)
143 _log.error('Output: %s', error.output)
144 attempts += 1
145 _log.error('Exceeded pull attempts')
146 _log.error('Aborting at time: %s', self._time())
147 return False
148
149 def _time(self):
150 return time.strftime('[%x %X %Z]', time.localtime())
151
152 def _message_command(self, message):
153 prefix = '%s:' % self.connection.get_nickname()
154 if message.startswith(prefix):
155 command_name = message[len(prefix):].strip()
156 if command_name in self.commands:
157 return self.commands[command_name]
158 return None
159
160 def _should_announce_commit(self, commit):
161 return any(path.startswith(self.announce_path) for path in self.git.affe cted_files(commit))
162
163 def _commit_detail(self, commit):
164 return self._format_commit_detail(self.git.git_commit_detail(commit, sel f._commit_detail_format))
165
166 def _format_commit_detail(self, commit_detail):
167 if commit_detail.count('\n') < self._commit_detail_format.count('\n'):
168 return ''
169
170 commit, email, subject, body = commit_detail.split('\n', 3)
171 commit_position_re = r'^Cr-Commit-Position: refs/heads/master@\{#(?P<com mit_position>\d+)\}'
172 commit_position = None
173 red_flag_strings = ['NOTRY=true', 'TBR=']
174 red_flags = []
175
176 for line in body.split('\n'):
177 match = re.search(commit_position_re, line)
178 if match:
179 commit_position = match.group('commit_position')
180
181 for red_flag_string in red_flag_strings:
182 if line.lower().startswith(red_flag_string.lower()):
183 red_flags.append(line.strip())
184
185 url = 'https://crrev.com/%s' % (commit_position if commit_position else commit[:8])
186 red_flag_message = '\x037%s\x03' % (' '.join(red_flags)) if red_flags el se ''
187
188 return ('%s %s committed "%s" %s' % (url, email, subject, red_flag_messa ge)).strip()
189
190 def _post(self, message):
191 self.connection.execute_delayed(0, lambda: self.connection.privmsg(CHANN EL, self._sanitize_string(message)))
192
193 def _sanitize_string(self, message):
194 return message.encode('ascii', 'backslashreplace')
195
196
197 class CommitAnnouncerThread(threading.Thread):
198
199 def __init__(self, tool, announce_path, irc_password):
200 threading.Thread.__init__(self)
201 self.bot = CommitAnnouncer(tool, announce_path, irc_password)
202
203 def run(self):
204 self.bot.start()
205
206 def stop(self):
207 self.bot.stop()
208 self.join()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698