Beispiel #1
0
    def detect_scm_system(self, path, patch_directories=None):
        real_path = self._filesystem.realpath(path)

        if patch_directories == []:
            patch_directories = None

        if SVN.in_working_directory(real_path, executive=self._executive):
            return SVN(cwd=real_path,
                       patch_directories=patch_directories,
                       filesystem=self._filesystem,
                       executive=self._executive)

        if Git.in_working_directory(real_path, executive=self._executive):
            return Git(cwd=real_path,
                       patch_directories=patch_directories,
                       filesystem=self._filesystem,
                       executive=self._executive)

        if StubRepository.in_working_directory(real_path,
                                               filesystem=self._filesystem):
            return StubRepository(cwd=real_path,
                                  patch_directories=patch_directories,
                                  filesystem=self._filesystem,
                                  executive=self._executive)

        return None
Beispiel #2
0
    def setUp(self):
        self.executive = Executive()
        self.filesystem = FileSystem()
        self.original_cwd = self.filesystem.getcwd()

        # Set up fresh git repository with one commit.
        self.untracking_checkout_path = self._mkdtemp(
            suffix='-git_unittest_untracking')
        self._run(['git', 'init', self.untracking_checkout_path])
        self._chdir(self.untracking_checkout_path)
        self._write_text_file('foo_file', 'foo')
        self._run(['git', 'add', 'foo_file'])
        self._run(['git', 'commit', '-am', 'dummy commit'])
        self.untracking_scm = Git(cwd=self.untracking_checkout_path,
                                  filesystem=self.filesystem,
                                  executive=self.executive)

        # Then set up a second git repo that tracks the first one.
        self.tracking_git_checkout_path = self._mkdtemp(
            suffix='-git_unittest_tracking')
        self._run([
            'git', 'clone', '--quiet', self.untracking_checkout_path,
            self.tracking_git_checkout_path
        ])
        self._chdir(self.tracking_git_checkout_path)
        self.tracking_scm = Git(cwd=self.tracking_git_checkout_path,
                                filesystem=self.filesystem,
                                executive=self.executive)
 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,
     }
Beispiel #4
0
 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
Beispiel #5
0
 def checkout_test_repository(self, revision, url, directory):
     git = None
     if not self._filesystem.exists(directory):
         _log.info('Cloning %s into %s...' % (url, directory))
         Git.clone(url, directory, self._host.executive)
         git = self.git(directory)
     elif self._options.fetch is True:
         git = self.git(directory)
         _log.info('Fetching %s...' % url)
         git.fetch()
     else:
         git = self.git(directory)
     _log.info('Checking out revision ' + revision)
     git.checkout(revision, not self._options.verbose)
Beispiel #6
0
 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,
     }
Beispiel #7
0
 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
Beispiel #8
0
    def initialize_scm(self):
        # TODO(qyearsley): Refactor this so that scm is initialized
        # when self.scm() is called the first time; put any initialization
        # code in the git module.
        if sys.platform == 'win32':
            self._engage_awesome_windows_hacks()

        cwd = self.filesystem.abspath(self.filesystem.getcwd())
        if Git.in_working_directory(cwd, executive=self.executive):
            self._scm = Git(cwd=cwd, filesystem=self.filesystem, executive=self.executive)
            return

        script_directory = self.filesystem.abspath(
            self.filesystem.dirname(self.filesystem.path_to_module(self.__module__)))
        _log.info('The current directory (%s) is not in a git repo, trying script directory %s.', cwd, script_directory)
        if Git.in_working_directory(script_directory, executive=self.executive):
            self._scm = Git(cwd=script_directory, filesystem=self.filesystem, executive=self.executive)
            return

        raise Exception('FATAL: Failed to find Git repo for %s or %s' % (cwd, script_directory))
Beispiel #9
0
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
Beispiel #10
0
 def git(self, test_repository):
     return Git(test_repository,
                None,
                executive=self._host.executive,
                filesystem=self._filesystem)
Beispiel #11
0
 def scm_for_path(self, path):
     # FIXME: make scm() be a wrapper around this, and clean up the way
     # callers call initialize_scm() (to remove patch_directories) and scm().
     if sys.platform == "win32":
         self._engage_awesome_windows_hacks()
     return Git(cwd=path, executive=self.executive, filesystem=self.filesystem)
Beispiel #12
0
class GitTestWithRealFilesystemAndExecutive(unittest.TestCase):
    def setUp(self):
        self.executive = Executive()
        self.filesystem = FileSystem()
        self.original_cwd = self.filesystem.getcwd()

        # Set up fresh git repository with one commit.
        self.untracking_checkout_path = self._mkdtemp(
            suffix='-git_unittest_untracking')
        self._run(['git', 'init', self.untracking_checkout_path])
        self._chdir(self.untracking_checkout_path)
        self._write_text_file('foo_file', 'foo')
        self._run(['git', 'add', 'foo_file'])
        self._run(['git', 'commit', '-am', 'dummy commit'])
        self.untracking_scm = Git(cwd=self.untracking_checkout_path,
                                  filesystem=self.filesystem,
                                  executive=self.executive)

        # Then set up a second git repo that tracks the first one.
        self.tracking_git_checkout_path = self._mkdtemp(
            suffix='-git_unittest_tracking')
        self._run([
            'git', 'clone', '--quiet', self.untracking_checkout_path,
            self.tracking_git_checkout_path
        ])
        self._chdir(self.tracking_git_checkout_path)
        self.tracking_scm = Git(cwd=self.tracking_git_checkout_path,
                                filesystem=self.filesystem,
                                executive=self.executive)

    def tearDown(self):
        self._chdir(self.original_cwd)
        self._run(['rm', '-rf', self.tracking_git_checkout_path])
        self._run(['rm', '-rf', self.untracking_checkout_path])

    def _join(self, *comps):
        return self.filesystem.join(*comps)

    def _chdir(self, path):
        self.filesystem.chdir(path)

    def _mkdir(self, path):
        assert not self.filesystem.exists(path)
        self.filesystem.maybe_make_directory(path)

    def _mkdtemp(self, **kwargs):
        return str(self.filesystem.mkdtemp(**kwargs))

    def _remove(self, path):
        self.filesystem.remove(path)

    def _run(self, *args, **kwargs):
        return self.executive.run_command(*args, **kwargs)

    def _run_silent(self, args, **kwargs):
        self.executive.run_command(args, **kwargs)

    def _write_text_file(self, path, contents):
        self.filesystem.write_text_file(path, contents)

    def _write_binary_file(self, path, contents):
        self.filesystem.write_binary_file(path, contents)

    def _make_diff(self, command, *args):
        # We use this wrapper to disable output decoding. diffs should be treated as
        # binary files since they may include text files of multiple different encodings.
        return self._run([command, 'diff'] + list(args), decode_output=False)

    def _git_diff(self, *args):
        return self._make_diff('git', *args)

    def test_add_list(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._mkdir('added_dir')
        self._write_text_file('added_dir/added_file', 'new stuff')
        print self._run(['ls', 'added_dir'])
        print self._run(['pwd'])
        print self._run(['cat', 'added_dir/added_file'])
        git.add_list(['added_dir/added_file'])
        self.assertIn('added_dir/added_file', git.added_files())

    def test_delete_recursively(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._mkdir('added_dir')
        self._write_text_file('added_dir/added_file', 'new stuff')
        git.add_list(['added_dir/added_file'])
        self.assertIn('added_dir/added_file', git.added_files())
        git.delete_list(['added_dir/added_file'])
        self.assertNotIn('added_dir', git.added_files())

    def test_delete_recursively_or_not(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._mkdir('added_dir')
        self._write_text_file('added_dir/added_file', 'new stuff')
        self._write_text_file('added_dir/another_added_file', 'more new stuff')
        git.add_list(['added_dir/added_file', 'added_dir/another_added_file'])
        self.assertIn('added_dir/added_file', git.added_files())
        self.assertIn('added_dir/another_added_file', git.added_files())
        git.delete_list(['added_dir/added_file'])
        self.assertIn('added_dir/another_added_file', git.added_files())

    def test_exists(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._chdir(git.checkout_root)
        self.assertFalse(git.exists('foo.txt'))
        self._write_text_file('foo.txt', 'some stuff')
        self.assertFalse(git.exists('foo.txt'))
        git.add_list(['foo.txt'])
        git.commit_locally_with_message('adding foo')
        self.assertTrue(git.exists('foo.txt'))
        git.delete_list(['foo.txt'])
        git.commit_locally_with_message('deleting foo')
        self.assertFalse(git.exists('foo.txt'))

    def test_move(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._write_text_file('added_file', 'new stuff')
        git.add_list(['added_file'])
        git.move('added_file', 'moved_file')
        self.assertIn('moved_file', git.added_files())

    def test_move_recursive(self):
        self._chdir(self.untracking_checkout_path)
        git = self.untracking_scm
        self._mkdir('added_dir')
        self._write_text_file('added_dir/added_file', 'new stuff')
        self._write_text_file('added_dir/another_added_file', 'more new stuff')
        git.add_list(['added_dir'])
        git.move('added_dir', 'moved_dir')
        self.assertIn('moved_dir/added_file', git.added_files())
        self.assertIn('moved_dir/another_added_file', git.added_files())

    def test_remote_branch_ref(self):
        # This tests a protected method. pylint: disable=protected-access
        self.assertEqual(self.tracking_scm._remote_branch_ref(),
                         'refs/remotes/origin/master')
        self._chdir(self.untracking_checkout_path)
        self.assertRaises(ScriptError, self.untracking_scm._remote_branch_ref)

    def test_create_patch(self):
        self._chdir(self.tracking_git_checkout_path)
        git = self.tracking_scm
        self._write_text_file('test_file_commit1', 'contents')
        self._run(['git', 'add', 'test_file_commit1'])
        git.commit_locally_with_message('message')
        git._patch_order = lambda: ''  # pylint: disable=protected-access
        patch = git.create_patch()
        self.assertNotRegexpMatches(patch, r'Subversion Revision:')

    def test_patches_have_filenames_with_prefixes(self):
        self._chdir(self.tracking_git_checkout_path)
        git = self.tracking_scm
        self._write_text_file('test_file_commit1', 'contents')
        self._run(['git', 'add', 'test_file_commit1'])
        git.commit_locally_with_message('message')

        # Even if diff.noprefix is enabled, create_patch() produces diffs with prefixes.
        self._run(['git', 'config', 'diff.noprefix', 'true'])
        git._patch_order = lambda: ''  # pylint: disable=protected-access
        patch = git.create_patch()
        self.assertRegexpMatches(
            patch, r'^diff --git a/test_file_commit1 b/test_file_commit1')

    def test_rename_files(self):
        self._chdir(self.tracking_git_checkout_path)
        git = self.tracking_scm
        git.move('foo_file', 'bar_file')
        git.commit_locally_with_message('message')

    def test_commit_position_from_git_log(self):
        # This tests a protected method. pylint: disable=protected-access
        git_log = """
commit 624c3081c0
Author: foobarbaz1 <*****@*****.**>
Date:   Mon Sep 28 19:10:30 2015 -0700

    Test foo bar baz qux 123.

    BUG=000000

    Review URL: https://codereview.chromium.org/999999999

    Cr-Commit-Position: refs/heads/master@{#1234567}
"""
        self._chdir(self.tracking_git_checkout_path)
        git = self.tracking_scm
        self.assertEqual(git._commit_position_from_git_log(git_log), 1234567)

    def test_timestamp_of_revision(self):
        # This tests a protected method. pylint: disable=protected-access
        self._chdir(self.tracking_git_checkout_path)
        git = self.tracking_scm
        position_regex = git._commit_position_regex_for_timestamp()
        git.most_recent_log_matching(position_regex, git.checkout_root)
Beispiel #13
0
 def make_scm(self):
     scm = Git(cwd=".",
               executive=MockExecutive(),
               filesystem=MockFileSystem())
     scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE"
     return scm
Beispiel #14
0
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')
Beispiel #15
0
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')
Beispiel #16
0
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
 def make_scm(self):
     scm = Git(cwd=".", executive=MockExecutive(), filesystem=MockFileSystem())
     scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE"
     return scm
Beispiel #18
0
 def make_scm(self):
     git = Git(cwd='.',
               executive=MockExecutive(),
               filesystem=MockFileSystem())
     git.read_git_config = lambda *args, **kw: 'MOCKKEY:MOCKVALUE'
     return git