Ejemplo n.º 1
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s",
                      branch, old_sha, new_sha)
        logging.debug("params=%s", self.params)

        permit = True

        # Do not run the hook if the branch is being deleted
        if new_sha == '0' * 40:
            logging.debug("Deleting the branch, skip the hook")
            return True, []

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha, this_branch_only=False)

        messages = []
        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'], ['.py'])

            def has_mixed_indent(file_contents):
                '''
                Check if file lines start with tabs and spaces
                file_contents = open(file).read()
                '''
                has_tab = False
                has_space = False
                for line in file_contents.split('\n'):
                    if line.startswith('\t'):
                        has_tab = True
                    elif line.startswith(' '):
                        has_space = True
                    if has_tab and has_space:
                        return True
                return False

            # Get the files from the repo and check indentation.
            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted '%s', skip", modfile['path'])
                    continue

                cmd = ['git', 'show', modfile['new_blob']]
                _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                permit_file = not has_mixed_indent(file_contents)
                logging.debug("modfile=%s, permit_file=%s", modfile['path'], permit_file)

                if not permit_file:
                    messages.append({'at': commit['commit'],
                        'text': "Error: file '%s' has mixed indentation" % modfile['path']})

                permit = permit and permit_file

        logging.debug("Permit: %s", permit)

        return permit, messages
Ejemplo n.º 2
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s",
                      branch, old_sha, new_sha)
        logging.debug("params=%s", self.params)

        if not self.settings:
            return True

        permit = True

        # Do not run the hook if the branch is being deleted
        if new_sha == '0' * 40:
            logging.debug("Deleting the branch, skip the hook")
            return True, []

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha, this_branch_only=False)

        messages = []
        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'])

            def has_good_copyright(file_contents, copyrights):
                '''
                Check if file contains good copyright string
                '''
                for (start, full) in copyrights:
                    if re.search(start, file_contents):
                        if not re.search(full, file_contents):
                            return False
                return True

            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted %s, skip", modfile['path'])
                    continue

                cmd = ['git', 'show', modfile['new_blob']]
                _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                permit_file = has_good_copyright(file_contents, self.settings)
                logging.debug("modfile='%s', permit_file='%s'", modfile['path'], permit_file)

                if not permit_file:
                    messages.append({'at': commit['commit'],
                        'text': "Error: Bad copyright in file '%s'!" % modfile['path']})
                permit = permit and permit_file

        if not permit:
            text = 'Please update the copyright strings to match one of the following:\n\n\t- ' + '\n\t- '.join([full for (start, full) in self.settings])
            messages.append({'at': new_sha, 'text': text + '\n'})

        logging.debug("Permit: %s", permit)

        return permit, messages
Ejemplo n.º 3
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s",
                      branch, old_sha, new_sha)
        logging.debug("params=%s", self.params)

        permit = True

        # Do not run the hook if the branch is being deleted
        if new_sha == '0' * 40:
            logging.debug("Deleting the branch, skip the hook")
            return True, []

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha, this_branch_only=False)

        messages = []
        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'])

            def has_mixed_le(file_contents):
                '''
                Check if file contains both lf and crlf
                file_contents = open(file).read()
                '''
                if ('\r\n' in file_contents and
                        '\n' in file_contents.replace('\r\n', '')):
                    return True
                return False

            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted %s, skip", modfile['path'])
                    continue

                binary_attr = hookutil.get_attr(
                    self.repo_dir, new_sha, modfile['path'], 'binary')

                if binary_attr != 'set':
                    cmd = ['git', 'show', modfile['new_blob']]
                    _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                    permit_file = not has_mixed_le(file_contents)
                    logging.debug("modfile='%s', permit_file='%s'", modfile['path'], permit_file)

                    if not permit_file:
                        messages.append({'at': commit['commit'],
                            'text': "Error: file '%s' has mixed line endings (CRLF/LF)" % modfile['path']})

                    permit = permit and permit_file

        logging.debug("Permit: %s", permit)

        return permit, messages
Ejemplo n.º 4
0
    def compose_mail(self, branch, old_sha, new_sha):
        pusher = self.params['user_name']
        base_url = self.params['base_url']
        proj_key = self.params['proj_key']
        repo_name = self.params['repo_name']

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha)

        files = []
        for commit in log:
            show = hookutil.parse_git_show(self.repo_dir, commit['commit'])
            for modfile in show:
                owners_attr = hookutil.get_attr(self.repo_dir, new_sha, modfile['path'], 'owners')
                if owners_attr == 'unspecified' or owners_attr == 'unset':
                    continue
                for owner in set(owners_attr.split(',')):
                    files.append({'owner':owner, 'commit':commit, 'path':modfile})

        files = sorted(files, key=lambda ko: ko['owner'])

        mails = {}
        for owner, commits in itertools.groupby(files, key=lambda ko: ko['owner']):
            text = '<b>Branch:</b> %s\n' % branch.replace('refs/heads/', '')
            text += '<b>By user:</b> %s\n' % pusher
            text += '\n'

            # No need to sort by commit hash because it is in order
            for commit, paths in itertools.groupby(commits, key=lambda kc: kc['commit']):
                link = base_url + \
                    "/projects/%s/repos/%s/commits/%s\n" % (proj_key, repo_name, commit['commit'])

                text += 'Commit: %s (%s)\n' % (commit['commit'], "<a href=%s>View in Stash</a>" % link)
                text += 'Author: %s %s\n' % (commit['author_name'], commit['author_email'])
                text += 'Date: %s\n' % commit['date']
                text += '\n'

                text += '\t%s' % '\n\t'.join(wrap(commit['message'][:100], width=70))
                if len(commit['message']) > 100:
                    text += '...'
                text += '\n\n'

                for path in paths:
                    text += '\t%s  %s\n' % (path['path']['status'], path['path']['path'])

                text += '\n\n'

            mails[owner] = text

        return mails
Ejemplo n.º 5
0
    def compose_mail(self, branch, old_sha, new_sha):
        pusher = self.params['user_name']
        base_url = self.params['base_url']
        proj_key = self.params['proj_key']
        repo_name = self.params['repo_name']

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha)

        files = []
        for commit in log:
            show = hookutil.parse_git_show(self.repo_dir, commit['commit'])
            for modfile in show:
                owners_attr = hookutil.get_attr(self.repo_dir, new_sha,
                                                modfile['path'], 'owners')
                if owners_attr == 'unspecified' or owners_attr == 'unset':
                    continue
                for owner in set(owners_attr.split(',')):
                    files.append({
                        'owner': owner,
                        'commit': commit,
                        'path': modfile
                    })

        files = sorted(files, key=lambda ko: ko['owner'])

        mails = {}
        for owner, commits in itertools.groupby(files,
                                                key=lambda ko: ko['owner']):
            text = '<b>Branch:</b> %s\n' % branch.replace('refs/heads/', '')
            text += '<b>By user:</b> %s\n' % pusher
            text += '\n'

            # No need to sort by commit hash because it is in order
            for commit, paths in itertools.groupby(
                    commits, key=lambda kc: kc['commit']):
                link = base_url + \
                    "/projects/%s/repos/%s/commits/%s\n" % (proj_key, repo_name, commit['commit'])

                text += 'Commit: %s (%s)\n' % (
                    commit['commit'], "<a href=%s>View in Stash</a>" % link)
                text += 'Author: %s %s\n' % (commit['author_name'],
                                             commit['author_email'])
                text += 'Date: %s\n' % commit['date']
                text += '\n'

                text += '\t%s' % '\n\t'.join(
                    wrap(commit['message'][:100], width=70))
                if len(commit['message']) > 100:
                    text += '...'
                text += '\n\n'

                for path in paths:
                    text += '\t%s  %s\n' % (path['path']['status'],
                                            path['path']['path'])

                text += '\n\n'

            mails[owner] = text

        return mails
Ejemplo n.º 6
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s",
                      branch, old_sha, new_sha)
        logging.debug("params=%s", self.params)

        permit = True


        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha, this_branch_only=False)

        for commit in log:
            print "Checking commit %s ..." % commit['commit']

            # Filter python scripts from the files modified in new_sha
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'], ['.py'])

            # Exit early if there are no modified python scripts in the changeset
            if not modfiles:
                return permit

            # Set up a working directory for pycodestyle and fill it with the blobs to be checked
            pycheck_workdir = tempfile.mkdtemp(suffix='pycheck')

            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted '%s', skip", modfile['path'])
                    continue

                cmd = ['git', 'show', modfile['new_blob']]
                _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                file_path = os.path.join(pycheck_workdir, modfile['path'])
                assert(not os.path.exists(file_path))

                file_dir = os.path.join(pycheck_workdir, os.path.dirname(modfile['path']))
                if not os.path.exists(file_dir):
                    os.makedirs(os.path.join(pycheck_workdir, os.path.dirname(modfile['path'])))

                with open(file_path, 'w') as fd:
                    fd.write(file_contents)

            # Copy setup.cfg to the working directory
            shutil.copy(os.path.join(os.path.dirname(__file__), 'setup.cfg'), pycheck_workdir)

            # Get the commit's diff; pycodestyle needs it to report only against modified lines
            cmd = ['git', 'show', commit['commit']]
            _, diff, _ = hookutil.run(cmd, self.repo_dir)

            local_dir = os.curdir
            os.chdir(pycheck_workdir)
            # Run pycodestyle in the working directory we have just prepared.
            selected_lines = pycodestyle.parse_udiff(diff, patterns=['*.py'], parent='')

            pep8style = pycodestyle.StyleGuide(
                {
                    'diff'          : True,
                    'paths'         : sorted(selected_lines),
                    'selected_lines': selected_lines,
                    'reporter'      : pycodestyle.DiffReport
                }
            )

            report = pep8style.check_files()
            os.chdir(local_dir)

            if report.total_errors:
                permit = False

            # Clean up
            shutil.rmtree(pycheck_workdir)

        logging.debug("Permit: %s" % permit)
        return permit, []
Ejemplo n.º 7
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s", branch,
                      old_sha, new_sha)
        logging.debug("params=%s", self.params)

        permit = True

        # Do not run the hook if the branch is being deleted
        if new_sha == '0' * 40:
            logging.debug("Deleting the branch, skip the hook")
            return True, []

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir,
                                     branch,
                                     old_sha,
                                     new_sha,
                                     this_branch_only=False)

        messages = []
        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'])

            def has_mixed_le(file_contents):
                '''
                Check if file contains both lf and crlf
                file_contents = open(file).read()
                '''
                if ('\r\n' in file_contents
                        and '\n' in file_contents.replace('\r\n', '')):
                    return True
                return False

            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted %s, skip", modfile['path'])
                    continue

                binary_attr = hookutil.get_attr(self.repo_dir, new_sha,
                                                modfile['path'], 'binary')

                if binary_attr != 'set':
                    cmd = ['git', 'show', modfile['new_blob']]
                    _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                    permit_file = not has_mixed_le(file_contents)
                    logging.debug("modfile='%s', permit_file='%s'",
                                  modfile['path'], permit_file)

                    if not permit_file:
                        messages.append({
                            'at':
                            commit['commit'],
                            'text':
                            "Error: file '%s' has mixed line endings (CRLF/LF)"
                            % modfile['path']
                        })

                    permit = permit and permit_file

        logging.debug("Permit: %s", permit)

        return permit, messages
Ejemplo n.º 8
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s",
                      branch, old_sha, new_sha)
        logging.debug("params=%s", self.params)

        try:
            base_url = self.params['base_url']
            proj_key = self.params['proj_key']
            repo_name = self.params['repo_name']
            pull_id = self.params['pull_id']
            pull_request_author_email = self.params['pull_request_author_email']
        except KeyError as err:
            logging.error("%s not in hook settings", err)
            raise RuntimeError("%s not in hook settings, check githooks configuration" % err)

        auth = None
        try:
            auth_user = self.params['auth_user']
            try:
                auth = TokenAuth(auth_user, self.params['auth_token'])
            except Exception:
                logging.warning("Could not read auth token, trying basic auth")
                try:
                    auth = requests.auth.HTTPBasicAuth(auth_user, self.params['auth_password'])
                except Exception:
                    logging.error("Could not read token and password for user %s", auth_user)
                    logging.warning("Continue with no auth")
        except Exception:
            logging.error("Could not read auth_user")
            logging.warning("Continue with no auth")


        permit = False

        # Fetch pull request reviewers
        try:
            pr = requests.get("%s/rest/api/1.0/projects/%s/repos/%s/pull-requests/%s" % (base_url, proj_key, repo_name, pull_id), auth=auth)
            pr.raise_for_status()
        except Exception as err:
            err_msg = "Failed to fetch pull request data (%s)" % str(err)
            logging.error(err_msg)
            raise RuntimeError(err_msg)

        try:
            pr_reviewers = pr.json()['reviewers']

            reviewers = []
            for reviewer in pr_reviewers:
                if reviewer['role'] != 'REVIEWER':
                    continue

                email = reviewer['user']['emailAddress']
                if reviewer['approved']:
                    reviewers.append(email)
        except KeyError as key:
            logging.error("Failed to parse %s from pull request # %s data" % (pull_id, str(key)))
            raise RuntimeError("Failed to parse pull request # %s data (%s: no such key)" % (pull_id, str(key)))

        logging.debug("Pull request reviewers who approved: %s", reviewers)

        # Parse modified files per commit
        log = hookutil.parse_git_log(self.repo_dir, branch, old_sha, new_sha)

        files = []

        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'])

            for modfile in modfiles:
                owners_attr = hookutil.get_attr(self.repo_dir, new_sha, modfile['path'], 'owners')
                if owners_attr == 'unspecified' or owners_attr == 'unset':
                    continue
                for owner in owners_attr.split(','):
                    # Skip this path as it is owned by the guy who merges the pull request
                    # Go to next modfile processing
                    if pull_request_author_email == owner:
                        break

                    # Avoid mail groups here -- check if Bitbucket user exists
                    #
                    # Do not fail if a mail group found in the owners list;
                    # Those mail groups are valid for the change notification hook
                    try:
                        ru = requests.get("%s/rest/api/1.0/users/%s" % (base_url, owner.split('@')[0]), auth=auth)
                        ru.raise_for_status()
                    except Exception as err:
                        logging.error("Failed to fetch user %s data (%s)" % (owner, str(err)))
                        continue

                    if owner not in reviewers:
                        files.append({'commit':commit['commit'], 'owner':owner, 'path':modfile['path']})

        if not files:
            permit = True
            logging.debug("files = []; either all approved or no approve required, permit = %s", permit)
            return permit, []


        logging.debug("Unapproved files: %s", files)

        all_owners = list(set([f['owner'] for f in files]))
        if len(all_owners) > 1:
            all_owners_str = ', '.join(all_owners[:-1] + ' and ', all_owners[-1])
        else:
            all_owners_str = ''.join(all_owners)

        print '\n'.join(wrap("<h4>This pull request must be approved by %s!</h4>" % all_owners_str, width=80))
        print '\n'.join(wrap('Changes to the following files must be approved ' \
                             'by their owners as specified in .gitattributes. ' \
                             'Please add those people to the pull request reviewers '
                             'if you haven\'t done so and wait for them to approve.', width=80))
        print "<p>"
        print '\n'.join(wrap("List of files that require approval:", width=80))

        for path, files_by_path in itertools.groupby(sorted(files, key=lambda k: k['path']), key=lambda ko: ko['path']):
            path_owners = list(set([f['owner'] for f in files_by_path]))

            text = "<i>%s</i> by " % path

            if len(path_owners) > 1:
                text += ', '.join(path_owners[:-1] + ' or ', path_owners[-1])
            else:
                text += ''.join(path_owners)

            print '\n'.join(wrap(text, width=80))

        print "</p>"

        logging.debug("Permit: %s", permit)
        return permit, []
Ejemplo n.º 9
0
    def check(self, branch, old_sha, new_sha):
        logging.debug("Run: branch=%s, old_sha=%s, new_sha=%s", branch,
                      old_sha, new_sha)
        logging.debug("params=%s", self.params)

        if not self.settings:
            return True, []

        permit = True

        # Do not run the hook if the branch is being deleted
        if new_sha == '0' * 40:
            logging.debug("Deleting the branch, skip the hook")
            return True, []

        # Before the hook is run git has already created
        # a new_sha commit object

        log = hookutil.parse_git_log(self.repo_dir,
                                     branch,
                                     old_sha,
                                     new_sha,
                                     this_branch_only=False)

        messages = []
        for commit in log:
            modfiles = hookutil.parse_git_show(self.repo_dir, commit['commit'])

            def has_good_copyright(file_contents, copyrights):
                '''
                Check if file contains good copyright string
                '''
                for (start, full) in copyrights:
                    if re.search(start, file_contents):
                        if not re.search(full, file_contents):
                            return False
                return True

            for modfile in modfiles:
                # Skip deleted files
                if modfile['status'] == 'D':
                    logging.debug("Deleted %s, skip", modfile['path'])
                    continue

                cmd = ['git', 'show', modfile['new_blob']]
                _, file_contents, _ = hookutil.run(cmd, self.repo_dir)

                permit_file = has_good_copyright(file_contents, self.settings)
                logging.debug("modfile='%s', permit_file='%s'",
                              modfile['path'], permit_file)

                if not permit_file:
                    messages.append({
                        'at':
                        commit['commit'],
                        'text':
                        "Error: Bad copyright in file '%s'!" % modfile['path']
                    })
                permit = permit and permit_file

        if not permit:
            text = 'Please update the copyright strings to match one of the following:\n\n\t- ' + '\n\t- '.join(
                [full for (start, full) in self.settings])
            messages.append({'at': new_sha, 'text': text + '\n'})

        logging.debug("Permit: %s", permit)

        return permit, messages