示例#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'])

            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
示例#2
0
    def test_get_attr(self):
        write_string('a.txt', 'data')
        write_string('b.txt', 'data')
        write_string('c.txt', 'data')
        write_string('.gitattributes', 'a.txt binary\nb.txt text')
        git(['add', 'a.txt', 'b.txt', 'c.txt', '.gitattributes'])
        git(['commit', '-m', 'initial commit'])
        git_call = git_async(['push', '-u', 'origin', 'master'], self.repo)
        request = self.get_request()

        import hookutil

        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'a.txt', 'binary'), 'set')
        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'a.txt', 'text'), 'unset')
        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'b.txt', 'binary'),
            'unspecified')
        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'b.txt', 'text'), 'set')
        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'c.txt', 'binary'),
            'unspecified')
        self.assertEquals(
            hookutil.get_attr(self.repo, request[2], 'c.txt', 'text'),
            'unspecified')

        self.write_response(0, 'success')
        git_async_result(git_call)
示例#3
0
    def test_get_attr(self):
        write_string('a.txt', 'data')
        write_string('b.txt', 'data')
        write_string('c.txt', 'data')
        write_string('.gitattributes', 'a.txt binary\nb.txt text')
        git(['add', 'a.txt', 'b.txt', 'c.txt', '.gitattributes'])
        git(['commit', '-m', 'initial commit'])
        git_call = git_async(['push', '-u', 'origin', 'master'], self.repo)
        request = self.get_request()

        import hookutil

        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'a.txt', 'binary'),
                          'set')
        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'a.txt', 'text'),
                          'unset')
        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'b.txt', 'binary'),
                          'unspecified')
        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'b.txt', 'text'),
                          'set')
        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'c.txt', 'binary'),
                          'unspecified')
        self.assertEquals(hookutil.get_attr(self.repo, request[2], 'c.txt', 'text'),
                          'unspecified')

        self.write_response(0, 'success')
        git_async_result(git_call)
示例#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
示例#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
    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
示例#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)

        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, []