Ejemplo n.º 1
0
    def commit(self,
               hash=None,
               revision=None,
               identifier=None,
               branch=None,
               tag=None,
               include_log=True,
               include_identifier=True):
        # Only git-svn checkouts can convert revisions to fully qualified commits
        if revision and not self.is_svn:
            raise self.Exception(
                'This git checkout does not support SVN revisions')

        # Determine the hash for a provided Subversion revision
        elif revision:
            if hash:
                raise ValueError('Cannot define both hash and revision')

            revision = Commit._parse_revision(revision, do_assert=True)
            revision_log = run(
                [self.executable(), 'svn', 'find-rev', 'r{}'.format(revision)],
                cwd=self.root_path,
                capture_output=True,
                encoding='utf-8',
                timeout=3,
            )
            if revision_log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for 'r{}'".format(
                        revision))
            hash = revision_log.stdout.rstrip()
            if not hash:
                raise self.Exception("Failed to find 'r{}'".format(revision))

        default_branch = self.default_branch
        parsed_branch_point = None
        log_format = ['-1'] if include_log else ['-1', '--format=short']

        # Determine the `git log` output and branch for a given identifier
        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if hash:
                raise ValueError('Cannot define both hash and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(
                identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})"
                        .format(
                            branch,
                            parsed_branch,
                        ), )
                branch = parsed_branch

            baseline = branch or 'HEAD'
            is_default = baseline == default_branch
            if baseline == 'HEAD':
                is_default = default_branch in self._branches_for(baseline)

            if is_default and parsed_branch_point:
                raise self.Exception(
                    'Cannot provide a branch point for a commit on the default branch'
                )

            base_count = self._commit_count(
                baseline if is_default else '{}..{}'.
                format(default_branch, baseline))

            if identifier > base_count:
                raise self.Exception(
                    'Identifier {} cannot be found on the specified branch in the current checkout'
                    .format(identifier))
            log = run(
                [
                    self.executable(), 'log', '{}~{}'.format(
                        branch or 'HEAD', base_count - identifier)
                ] + log_format,
                cwd=self.root_path,
                capture_output=True,
                encoding='utf-8',
            )
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for 'i{}@{}'".
                    format(identifier, branch or 'HEAD'))

            # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
            if identifier < 0 and is_default:
                raise self.Exception(
                    'Illegal negative identifier on the default branch')
            if identifier < 0:
                identifier = None

        # Determine the `git log` output for a given branch or tag
        elif branch or tag:
            if hash:
                raise ValueError('Cannot define both tag/branch and hash')
            if branch and tag:
                raise ValueError('Cannot define both tag and branch')

            log = run([self.executable(), 'log', branch or tag] + log_format,
                      cwd=self.root_path,
                      capture_output=True,
                      encoding='utf-8')
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for '{}'".format(
                        branch or tag))

        # Determine the `git log` output for a given hash
        else:
            hash = Commit._parse_hash(hash, do_assert=True)
            log = run([self.executable(), 'log', hash or 'HEAD'] + log_format,
                      cwd=self.root_path,
                      capture_output=True,
                      encoding='utf-8')
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for '{}'".format(
                        hash or 'HEAD'))

        # Fully define the hash from the `git log` output
        match = self.GIT_COMMIT.match(log.stdout.splitlines()[0])
        if not match:
            raise self.Exception('Invalid commit hash in git log')
        hash = match.group('hash')

        # A commit is often on multiple branches, the canonical branch is the one with the highest priority
        branch = self.prioritize_branches(self._branches_for(hash))

        # Compute the identifier if the function did not receive one and we were asked to
        if not identifier and include_identifier:
            identifier = self._commit_count(
                hash if branch ==
                default_branch else '{}..{}'.format(default_branch, hash))

        # Only compute the branch point we're on something other than the default branch
        branch_point = None if not include_identifier or branch == default_branch else self._commit_count(
            hash) - identifier
        if branch_point and parsed_branch_point and branch_point != parsed_branch_point:
            raise ValueError(
                "Provided 'branch_point' does not match branch point of specified branch"
            )

        # Check the commit log for a git-svn revision
        logcontent = '\n'.join(line[4:]
                               for line in log.stdout.splitlines()[4:])
        matches = self.GIT_SVN_REVISION.findall(logcontent)
        revision = int(matches[-1].split('@')[0]) if matches else None

        # We only care about when a commit was commited
        commit_time = run(
            [self.executable(), 'show', '-s', '--format=%ct', hash],
            cwd=self.root_path,
            capture_output=True,
            encoding='utf-8',
        )
        if commit_time.returncode:
            raise self.Exception(
                'Failed to retrieve commit time for {}'.format(hash))
        timestamp = int(commit_time.stdout.lstrip())

        # Comparing commits in different repositories involves comparing timestamps. This is problematic because it git,
        # it's possible for a series of commits to share a commit time. To handle this case, we assign each commit a
        # zero-indexed "order" within it's timestamp.
        order = 0
        while not identifier or order + 1 < identifier + (branch_point or 0):
            commit_time = run(
                [
                    self.executable(), 'show', '-s', '--format=%ct',
                    '{}~{}'.format(hash, order + 1)
                ],
                cwd=self.root_path,
                capture_output=True,
                encoding='utf-8',
            )
            if commit_time.returncode:
                break
            if int(commit_time.stdout.lstrip()) != timestamp:
                break
            order += 1

        return Commit(
            repository_id=self.id,
            hash=hash,
            revision=revision,
            identifier=identifier if include_identifier else None,
            branch_point=branch_point,
            branch=branch,
            timestamp=timestamp,
            order=order,
            author=Contributor.from_scm_log(log.stdout.splitlines()[1],
                                            self.contributors),
            message=logcontent if include_log else None,
        )
Ejemplo n.º 2
0
    def commit(self, hash=None, revision=None, identifier=None, branch=None, tag=None, include_log=True, include_identifier=True):
        if revision:
            raise self.Exception('Cannot map revisions to commits on GitHub')

        # Determine the commit data and branch for a given identifier
        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if hash:
                raise ValueError('Cannot define both hash and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})".format(
                            branch, parsed_branch,
                        ),
                    )
                branch = parsed_branch

            branch = branch or self.default_branch
            is_default = branch == self.default_branch

            if is_default and parsed_branch_point:
                raise self.Exception('Cannot provide a branch point for a commit on the default branch')

            if is_default:
                base_count, base_ref = self._count_for_ref(ref=self.default_branch)
            else:
                _, base_ref = self._count_for_ref(ref=branch)
                base_count = self._difference(self.default_branch, base_ref)

            if identifier > base_count:
                raise self.Exception('Identifier {} cannot be found on {}'.format(identifier, branch))

            # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
            if identifier < 0 and is_default:
                raise self.Exception('Illegal negative identifier on the default branch')

            commit_data = self.request('commits/{}~{}'.format(base_ref, base_count - identifier))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}@{}'".format(identifier, branch or 'HEAD'))

            # If an identifier is negative, unset it so we re-compute before constructing the commit.
            if identifier <= 0:
                identifier = None

        # Determine the commit data for a given branch or tag
        elif branch or tag:
            if hash:
                raise ValueError('Cannot define both tag/branch and hash')
            if branch and tag:
                raise ValueError('Cannot define both tag and branch')
            commit_data = self.request('commits/{}'.format(branch or tag))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(branch or tag))

        # Determine the commit data for a given hash
        else:
            hash = Commit._parse_hash(hash, do_assert=True)
            commit_data = self.request('commits/{}'.format(hash or self.default_branch))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(hash or 'HEAD'))

        # A commit is often on multiple branches, the canonical branch is the one with the highest priority
        branches = self._branches_for(commit_data['sha'])
        if branches:
            branch = self.prioritize_branches(branches)
        else:
            # A commit not on any branches cannot have an identifier
            identifier = None
            branch = None

        # Define identifiers on default branch
        branch_point = None
        if include_identifier and branch and branch == self.default_branch:
            if not identifier:
                result = self._count_for_ref(ref=commit_data['sha'])
                if not result:
                    raise Exception('{} {}'.format(result, commit_data['sha']))
                identifier, _ = result

        # Define identifiers on branches diverged from the default branch
        elif include_identifier and branch:
            if not identifier:
                identifier = self._difference(self.default_branch, commit_data['sha'])
            branch_point = self._count_for_ref(ref=commit_data['sha'])[0] - identifier

        # Check the commit log for a git-svn revision
        matches = self.GIT_SVN_REVISION.findall(commit_data['commit']['message'])
        revision = int(matches[-1].split('@')[0]) if matches else None

        email_match = self.EMAIL_RE.match(commit_data['commit']['author']['email'])
        timestamp = int(calendar.timegm(datetime.strptime(
            commit_data['commit']['committer']['date'], '%Y-%m-%dT%H:%M:%SZ',
        ).timetuple()))

        # Comparing commits in different repositories involves comparing timestamps. This is problematic because it git,
        # it's possible for a series of commits to share a commit time. To handle this case, we assign each commit a
        # zero-indexed "order" within it's timestamp.
        order = 0
        lhash = commit_data['sha']
        while lhash:
            response = self.request('commits', paginate=False, params=dict(sha=lhash, per_page=20))
            if len(response) <= 1:
                break
            for c in response:
                if lhash == c['sha']:
                    continue
                parent_timestamp = int(calendar.timegm(datetime.strptime(
                    c['commit']['committer']['date'], '%Y-%m-%dT%H:%M:%SZ',
                ).timetuple()))
                if parent_timestamp != timestamp:
                    lhash = None
                    break
                lhash = c['sha']
                order += 1

        return Commit(
            repository_id=self.id,
            hash=commit_data['sha'],
            revision=revision,
            branch_point=branch_point,
            identifier=identifier if include_identifier else None,
            branch=branch,
            timestamp=timestamp,
            order=order,
            author=self.contributors.create(
                commit_data['commit']['author']['name'],
                email_match.group('email') if email_match else None,
            ), message=commit_data['commit']['message'] if include_log else None,
        )
Ejemplo n.º 3
0
    def commit(self, hash=None, revision=None, identifier=None, branch=None, tag=None, include_log=True):
        if hash:
            raise ValueError('SVN does not support Git hashes')

        parsed_branch_point = None
        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})".format(
                            branch, parsed_branch,
                        ),
                    )
                branch = parsed_branch
            branch = branch or self.branch

            if branch == self.default_branch and parsed_branch_point:
                raise self.Exception('Cannot provide a branch point for a commit on the default branch')

            if not self._metadata_cache.get(branch, []) or identifier >= len(self._metadata_cache.get(branch, [])):
                if branch != self.default_branch:
                    self._cache_revisions(branch=self.default_branch)
                self._cache_revisions(branch=branch)
            if identifier > len(self._metadata_cache.get(branch, [])):
                raise self.Exception('Identifier {} cannot be found on the specified branch in the current checkout'.format(identifier))

            if identifier <= 0:
                if branch == self.default_branch:
                    raise self.Exception('Illegal negative identifier on the default branch')
                identifier = self._commit_count(branch=branch) + identifier
                if identifier < 0:
                    raise self.Exception('Identifier does not exist on the specified branch')

                branch = self.default_branch

            revision = self._metadata_cache[branch][identifier]
            info = self.info(cached=True, branch=branch, revision=revision)
            branch = self._branch_for(revision)
            if not self._metadata_cache.get(branch, []) or identifier >= len(self._metadata_cache.get(branch, [])):
                self._cache_revisions(branch=branch)

        elif revision:
            if branch:
                raise ValueError('Cannot define both branch and revision')
            if tag:
                raise ValueError('Cannot define both tag and revision')
            revision = Commit._parse_revision(revision, do_assert=True)
            branch = self._branch_for(revision)
            info = self.info(cached=True, revision=revision)

        else:
            if branch and tag:
                raise ValueError('Cannot define both branch and tag')

            branch = None if tag else branch or self.branch
            info = self.info(tag=tag) if tag else self.info(branch=branch)
            if not info:
                raise self.Exception("'{}' is not a recognized {}".format(
                    tag or branch,
                    'tag' if tag else 'branch',
                ))
            revision = int(info['Last Changed Rev'])
            if branch != self.default_branch:
                branch = self._branch_for(revision)

        date = info['Last Changed Date'].split(' (')[0]
        tz_diff = date.split(' ')[-1]
        date = datetime.strptime(date[:-len(tz_diff)], '%Y-%m-%d %H:%M:%S ')
        date += timedelta(
            hours=int(tz_diff[1:3]),
            minutes=int(tz_diff[3:5]),
        ) * (1 if tz_diff[0] == '-' else -1)

        if not identifier:
            if branch != self.default_branch and revision > self._metadata_cache.get(self.default_branch, [0])[-1]:
                self._cache_revisions(branch=self.default_branch)
            if revision not in self._metadata_cache.get(branch, []):
                self._cache_revisions(branch=branch)
            identifier = self._commit_count(revision=revision, branch=branch)

        branch_point = None if branch == self.default_branch else self._commit_count(branch=branch)
        if branch_point and parsed_branch_point and branch_point != parsed_branch_point:
            raise ValueError("Provided 'branch_point' does not match branch point of specified branch")

        if branch == self.default_branch or '/' in branch:
            branch_arg = '^/{}'.format(branch)
        else:
            branch_arg = '^/branches/{}'.format(branch)

        log = run(
            [self.executable(), 'log', '-l', '1', '-r', str(revision), branch_arg], cwd=self.root_path,
            capture_output=True, encoding='utf-8',
        ) if include_log else None
        split_log = log.stdout.splitlines() if log else []
        if log and (not log.returncode or len(split_log) >= 3):
            author_line = split_log[1]
            for line in split_log[2:8]:
                if Contributor.SVN_PATCH_FROM_RE.match(line):
                    author_line = line
                    break

            author = Contributor.from_scm_log(author_line, self.contributors)
            message = '\n'.join(split_log[3:-1])
        else:
            if include_log:
                self.log('Failed to connect to remote, cannot compute commit message')
            email = info.get('Last Changed Author')
            author = self.contributors.create(email, email) if '@' in email else self.contributors.create(email)
            message = None

        return Commit(
            revision=int(revision),
            branch=branch,
            identifier=identifier,
            branch_point=branch_point,
            timestamp=int(calendar.timegm(date.timetuple())),
            author=author,
            message=message,
        )
Ejemplo n.º 4
0
    def commit(self, hash=None, revision=None, identifier=None, branch=None, tag=None, include_log=True, include_identifier=True):
        if revision:
            raise self.Exception('Cannot map revisions to commits on BitBucket')

        # Determine the commit data and branch for a given identifier
        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if hash:
                raise ValueError('Cannot define both hash and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})".format(
                            branch, parsed_branch,
                        ),
                    )
                branch = parsed_branch

            branch = branch or self.default_branch
            is_default = branch == self.default_branch

            if is_default and parsed_branch_point:
                raise self.Exception('Cannot provide a branch point for a commit on the default branch')

            commit_data = self.request('commits/{}'.format(branch), params=dict(limit=1))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(branch))
            base_ref = commit_data['id']

            if is_default:
                base_count = self._distance(base_ref)
            else:
                base_count = self._distance(base_ref, magnitude=256, condition=lambda val: self.default_branch not in val)

            if identifier > base_count:
                raise self.Exception('Identifier {} cannot be found on {}'.format(identifier, branch))

            # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
            if identifier < 0 and is_default:
                raise self.Exception('Illegal negative identifier on the default branch')

            commit_data = self.request('commits/{}~{}'.format(base_ref, base_count - identifier))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}@{}'".format(identifier, branch or 'HEAD'))

            # If an identifier is negative, unset it so we re-compute before constructing the commit.
            if identifier <= 0:
                identifier = None

        # Determine the commit data for a given branch or tag
        elif branch or tag:
            if hash:
                raise ValueError('Cannot define both tag/branch and hash')
            if branch and tag:
                raise ValueError('Cannot define both tag and branch')
            commit_data = self.request('commits/{}'.format(branch or tag))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(branch or tag))

        # Determine the commit data for a given hash
        else:
            hash = Commit._parse_hash(hash, do_assert=True)
            commit_data = self.request('commits/{}'.format(hash or self.default_branch))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(hash or 'HEAD'))

        # A commit is often on multiple branches, the canonical branch is the one with the highest priority
        branches = self._branches_for(commit_data['id'])
        if branches:
            branch = self.prioritize_branches(branches)
        else:
            # A commit not on any branches cannot have an identifier
            identifier = None
            branch = None

        # Define identifiers on default branch
        branch_point = None
        if include_identifier and branch and branch == self.default_branch:
            if not identifier:
                identifier = self._distance(commit_data['id'])

        # Define identifiers on branches diverged from the default branch
        elif include_identifier and branch:
            if not identifier:
                identifier = self._distance(commit_data['id'], magnitude=256, condition=lambda val: self.default_branch not in val)
            branch_point = self._distance(commit_data['id']) - identifier

        # Check the commit log for a git-svn revision
        matches = self.GIT_SVN_REVISION.findall(commit_data['message'])
        revision = int(matches[-1].split('@')[0]) if matches else None

        # Comparing commits in different repositories involves comparing timestamps. This is problematic because it git,
        # it's possible for a series of commits to share a commit time. To handle this case, we assign each commit a
        # zero-indexed "order" within it's timestamp.
        timestamp = int(commit_data['committerTimestamp'] / 1000)
        order = 0
        while not identifier or order + 1 < identifier + (branch_point or 0):
            response = self.request('commits/{}'.format('{}~{}'.format(commit_data['id'], order + 1)))
            if not response:
                break
            parent_timestamp = int(response['committerTimestamp'] / 1000)
            if parent_timestamp != timestamp:
                break
            order += 1

        return Commit(
            repository_id=self.id,
            hash=commit_data['id'],
            revision=revision,
            branch_point=branch_point,
            identifier=identifier if include_identifier else None,
            branch=branch,
            timestamp=timestamp,
            order=order,
            author=self.contributors.create(
                commit_data.get('committer', {}).get('displayName', None),
                commit_data.get('committer', {}).get('emailAddress', None),
            ), message=commit_data['message'] if include_log else None,
        )
Ejemplo n.º 5
0
    def commit(self,
               hash=None,
               revision=None,
               identifier=None,
               branch=None,
               tag=None,
               include_log=True,
               include_identifier=True):
        if hash:
            raise ValueError('SVN does not support Git hashes')

        parsed_branch_point = None
        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(
                identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})"
                        .format(
                            branch,
                            parsed_branch,
                        ), )
                branch = parsed_branch
            branch = branch or self.default_branch

            if branch == self.default_branch and parsed_branch_point:
                raise self.Exception(
                    'Cannot provide a branch point for a commit on the default branch'
                )

            if not self._metadata_cache.get(branch, []) or identifier >= len(
                    self._metadata_cache.get(branch, [])):
                if branch != self.default_branch:
                    self._cache_revisions(branch=self.default_branch)
                self._cache_revisions(branch=branch)
            if identifier > len(self._metadata_cache.get(branch, [])):
                raise self.Exception(
                    'Identifier {} cannot be found on the specified branch in the current checkout'
                    .format(identifier))

            if identifier <= 0:
                if branch == self.default_branch:
                    raise self.Exception(
                        'Illegal negative identifier on the default branch')
                identifier = self._commit_count(branch=branch) + identifier
                if identifier < 0:
                    raise self.Exception(
                        'Identifier does not exist on the specified branch')

                branch = self.default_branch

            revision = self._metadata_cache[branch][identifier]
            info = self.info(cached=True, branch=branch, revision=revision)
            branch = self._branch_for(revision)
            if not self._metadata_cache.get(branch, []) or identifier >= len(
                    self._metadata_cache.get(branch, [])):
                self._cache_revisions(branch=branch)

        elif revision:
            if branch:
                raise ValueError('Cannot define both branch and revision')
            if tag:
                raise ValueError('Cannot define both tag and revision')
            revision = Commit._parse_revision(revision, do_assert=True)
            branch = self._branch_for(revision) or self.default_branch
            info = self.info(cached=True, branch=branch, revision=revision)

        else:
            if branch and tag:
                raise ValueError('Cannot define both branch and tag')

            branch = None if tag else branch or self.default_branch
            info = self.info(tag=tag) if tag else self.info(branch=branch)
            if not info:
                raise self.Exception("'{}' is not a recognized {}".format(
                    tag or branch,
                    'tag' if tag else 'branch',
                ))
            revision = int(info['Last Changed Rev'])
            if branch != self.default_branch:
                branch = self._branch_for(revision)

        date = datetime.strptime(
            info['Last Changed Date'],
            '%Y-%m-%d %H:%M:%S') if info.get('Last Changed Date') else None

        if include_identifier and not identifier:
            if branch != self.default_branch and revision > self._metadata_cache.get(
                    self.default_branch, [0])[-1]:
                self._cache_revisions(branch=self.default_branch)
            if revision not in self._metadata_cache.get(branch, []):
                self._cache_revisions(branch=branch)
            identifier = self._commit_count(revision=revision, branch=branch)

        branch_point = None if not include_identifier or branch == self.default_branch else self._commit_count(
            branch=branch)
        if branch_point and parsed_branch_point and branch_point != parsed_branch_point:
            raise ValueError(
                "Provided 'branch_point' does not match branch point of specified branch"
            )

        response = requests.request(
            method='REPORT',
            url='{}!svn/rvr/{}'.format(self.url, revision),
            headers={
                'Content-Type': 'text/xml',
                'Accept-Encoding': 'gzip',
                'DEPTH': '1',
            },
            data='<S:log-report xmlns:S="svn:">\n'
            '<S:start-revision>{revision}</S:start-revision>\n'
            '<S:end-revision>{revision}</S:end-revision>\n'
            '<S:limit>1</S:limit>\n'
            '</S:log-report>\n'.format(revision=revision),
        ) if include_log else None

        if response and response.status_code == 200:
            response = xmltodict.parse(response.text)
            response = response.get('S:log-report', {}).get('S:log-item')

            name = response.get('D:creator-displayname')
            message = response.get('D:comment', None)

        else:
            if include_log:
                self.log(
                    'Failed to connect to remote, cannot compute commit message'
                )
            message = None
            name = info.get('Last Changed Author')

        author = self.contributors.create(
            name,
            name) if name and '@' in name else self.contributors.create(name)

        return Commit(
            repository_id=self.id,
            revision=int(revision),
            branch=branch,
            identifier=identifier if include_identifier else None,
            branch_point=branch_point,
            timestamp=int(calendar.timegm(date.timetuple())) if date else None,
            author=author,
            message=message,
        )
Ejemplo n.º 6
0
    def commit(self, hash=None, revision=None, identifier=None, branch=None, tag=None, include_log=True, include_identifier=True):
        if revision:
            raise self.Exception('Cannot map revisions to commits on GitHub')

        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if hash:
                raise ValueError('Cannot define both hash and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})".format(
                            branch, parsed_branch,
                        ),
                    )
                branch = parsed_branch

            branch = branch or self.default_branch
            is_default = branch == self.default_branch

            if is_default and parsed_branch_point:
                raise self.Exception('Cannot provide a branch point for a commit on the default branch')

            if is_default:
                base_count, base_ref = self._count_for_ref(ref=self.default_branch)
            else:
                _, base_ref = self._count_for_ref(ref=branch)
                base_count = self._difference(self.default_branch, base_ref)

            if identifier > base_count:
                raise self.Exception('Identifier {} cannot be found on {}'.format(identifier, branch))

            # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
            if identifier < 0 and is_default:
                raise self.Exception('Illegal negative identifier on the default branch')

            commit_data = self.request('commits/{}~{}'.format(base_ref, base_count - identifier))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}@{}'".format(identifier, branch or 'HEAD'))

            # If an identifier is negative, unset it so we re-compute before constructing the commit.
            if identifier <= 0:
                identifier = None

        elif branch or tag:
            if hash:
                raise ValueError('Cannot define both tag/branch and hash')
            if branch and tag:
                raise ValueError('Cannot define both tag and branch')
            commit_data = self.request('commits/{}'.format(branch or tag))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(branch or tag))

        else:
            hash = Commit._parse_hash(hash, do_assert=True)
            commit_data = self.request('commits/{}'.format(hash or self.default_branch))
            if not commit_data:
                raise self.Exception("Failed to retrieve commit information for '{}'".format(hash or 'HEAD'))

        branches = self._branches_for(commit_data['sha'])
        if branches:
            branch = self.prioritize_branches(branches)

        else:
            # A commit not on any branches cannot have an identifier
            identifier = None
            branch = None

        branch_point = None
        if include_identifier and branch and branch == self.default_branch:
            if not identifier:
                result = self._count_for_ref(ref=commit_data['sha'])
                if not result:
                    raise Exception('{} {}'.format(result, commit_data['sha']))
                identifier, _ = result

        elif include_identifier and branch:
            if not identifier:
                identifier = self._difference(self.default_branch, commit_data['sha'])
            branch_point = self._count_for_ref(ref=commit_data['sha'])[0] - identifier

        matches = self.GIT_SVN_REVISION.findall(commit_data['commit']['message'])
        revision = int(matches[-1].split('@')[0]) if matches else None

        email_match = self.EMAIL_RE.match(commit_data['commit']['author']['email'])
        timestamp = int(calendar.timegm(datetime.strptime(
            commit_data['commit']['committer']['date'], '%Y-%m-%dT%H:%M:%SZ',
        ).timetuple()))

        order = 0
        while not identifier or order + 1 < identifier + (branch_point or 0):
            response = self.request('commits/{}'.format('{}~{}'.format(commit_data['sha'], order + 1)))
            if not response:
                break
            parent_timestamp = int(calendar.timegm(datetime.strptime(
                response['commit']['committer']['date'], '%Y-%m-%dT%H:%M:%SZ',
            ).timetuple()))
            if parent_timestamp != timestamp:
                break
            order += 1

        return Commit(
            repository_id=self.id,
            hash=commit_data['sha'],
            revision=revision,
            branch_point=branch_point,
            identifier=identifier if include_identifier else None,
            branch=branch,
            timestamp=timestamp,
            order=order,
            author=self.contributors.create(
                commit_data['commit']['author']['name'],
                email_match.group('email') if email_match else None,
            ), message=commit_data['commit']['message'] if include_log else None,
        )
Ejemplo n.º 7
0
    def commit(self,
               hash=None,
               revision=None,
               identifier=None,
               branch=None,
               tag=None,
               include_log=True):
        if revision and not self.is_svn:
            raise self.Exception(
                'This git checkout does not support SVN revisions')
        elif revision:
            if hash:
                raise ValueError('Cannot define both hash and revision')

            revision = Commit._parse_revision(revision, do_assert=True)
            revision_log = run(
                [self.executable(), 'svn', 'find-rev', 'r{}'.format(revision)],
                cwd=self.root_path,
                capture_output=True,
                encoding='utf-8',
                timeout=3,
            )
            if revision_log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for 'r{}'".format(
                        revision))
            hash = revision_log.stdout.rstrip()
            if not hash:
                raise self.Exception("Failed to find 'r{}'".format(revision))

        default_branch = self.default_branch
        parsed_branch_point = None
        log_format = ['-1'] if include_log else ['-1', '--format=short']

        if identifier is not None:
            if revision:
                raise ValueError('Cannot define both revision and identifier')
            if hash:
                raise ValueError('Cannot define both hash and identifier')
            if tag:
                raise ValueError('Cannot define both tag and identifier')

            parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(
                identifier, do_assert=True)
            if parsed_branch:
                if branch and branch != parsed_branch:
                    raise ValueError(
                        "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})"
                        .format(
                            branch,
                            parsed_branch,
                        ), )
                branch = parsed_branch

            baseline = branch or 'HEAD'
            is_default = baseline == default_branch
            if baseline == 'HEAD':
                is_default = default_branch in self._branches_for(baseline)

            if is_default and parsed_branch_point:
                raise self.Exception(
                    'Cannot provide a branch point for a commit on the default branch'
                )

            base_count = self._commit_count(
                baseline if is_default else '{}..{}'.
                format(default_branch, baseline))

            if identifier > base_count:
                raise self.Exception(
                    'Identifier {} cannot be found on the specified branch in the current checkout'
                    .format(identifier))
            log = run(
                [
                    self.executable(), 'log', '{}~{}'.format(
                        branch or 'HEAD', base_count - identifier)
                ] + log_format,
                cwd=self.root_path,
                capture_output=True,
                encoding='utf-8',
            )
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for 'i{}@{}'".
                    format(identifier, branch or 'HEAD'))

            # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
            if identifier < 0 and is_default:
                raise self.Exception(
                    'Illegal negative identifier on the default branch')
            if identifier < 0:
                identifier = None

        elif branch or tag:
            if hash:
                raise ValueError('Cannot define both tag/branch and hash')
            if branch and tag:
                raise ValueError('Cannot define both tag and branch')

            log = run([self.executable(), 'log', branch or tag] + log_format,
                      cwd=self.root_path,
                      capture_output=True,
                      encoding='utf-8')
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for '{}'".format(
                        branch or tag))

        else:
            hash = Commit._parse_hash(hash, do_assert=True)
            log = run([self.executable(), 'log', hash or 'HEAD'] + log_format,
                      cwd=self.root_path,
                      capture_output=True,
                      encoding='utf-8')
            if log.returncode:
                raise self.Exception(
                    "Failed to retrieve commit information for '{}'".format(
                        hash or 'HEAD'))

        match = self.GIT_COMMIT.match(log.stdout.splitlines()[0])
        if not match:
            raise self.Exception('Invalid commit hash in git log')
        hash = match.group('hash')

        branch = self.prioritize_branches(self._branches_for(hash))

        if not identifier:
            identifier = self._commit_count(
                hash if branch ==
                default_branch else '{}..{}'.format(default_branch, hash))
        branch_point = None if branch == default_branch else self._commit_count(
            hash) - identifier
        if branch_point and parsed_branch_point and branch_point != parsed_branch_point:
            raise ValueError(
                "Provided 'branch_point' does not match branch point of specified branch"
            )

        match = self.GIT_SVN_REVISION.search(log.stdout)
        revision = int(match.group('revision')) if match else None

        commit_time = run(
            [self.executable(), 'show', '-s', '--format=%ct', hash],
            cwd=self.root_path,
            capture_output=True,
            encoding='utf-8',
        )
        if commit_time.returncode:
            raise self.Exception(
                'Failed to retrieve commit time for {}'.format(hash))

        return Commit(
            hash=hash,
            revision=revision,
            identifier=identifier,
            branch_point=branch_point,
            branch=branch,
            timestamp=int(commit_time.stdout.lstrip()),
            author=Contributor.from_scm_log(log.stdout.splitlines()[1],
                                            self.contributors),
            message='\n'.join(line[4:] for line in log.stdout.splitlines()[4:])
            if include_log else None,
        )
Ejemplo n.º 8
0
    def test_parse_identifier(self):
        self.assertEqual((None, 1234, None), Commit._parse_identifier('1234'))
        self.assertEqual((None, 1234, None), Commit._parse_identifier(1234))
        self.assertEqual((None, 1234, None), Commit._parse_identifier('1234@'))

        self.assertEqual((None, 1234, 'main'),
                         Commit._parse_identifier('1234@main'))
        self.assertEqual((None, 1234, 'eng/bug'),
                         Commit._parse_identifier('1234@eng/bug'))
        self.assertEqual((1234, 1, 'eng/bug'),
                         Commit._parse_identifier('1234.1@eng/bug'))

        self.assertEqual((None, 0, None), Commit._parse_identifier('0'))
        self.assertEqual((None, -1, None), Commit._parse_identifier('-1'))

        self.assertEqual((None, 0, 'eng/bug'),
                         Commit._parse_identifier('0@eng/bug'))
        self.assertEqual((None, -1, 'eng/bug'),
                         Commit._parse_identifier('-1@eng/bug'))

        self.assertEqual(None, Commit._parse_identifier('1234-invalid'))
        self.assertEqual(None, Commit._parse_identifier('r266896'))
        self.assertEqual(None, Commit._parse_identifier('c3bd784f8b88bd03'))
        self.assertEqual(None, Commit._parse_identifier(3.141592))