示例#1
0
    def test_parse_revision(self):
        self.assertEqual(266896, Commit._parse_revision('r266896'))
        self.assertEqual(266896, Commit._parse_revision('R266896'))
        self.assertEqual(266896, Commit._parse_revision('266896'))
        self.assertEqual(266896, Commit._parse_revision(266896))

        self.assertEqual(None, Commit._parse_revision('c3bd784f8b88bd03'))
        self.assertEqual(None, Commit._parse_revision('0'))
        self.assertEqual(None, Commit._parse_revision('-1'))
        self.assertEqual(None, Commit._parse_revision('3.141592'))
        self.assertEqual(None, Commit._parse_revision(3.141592))
示例#2
0
    def info(self, branch=None, revision=None, tag=None):
        if tag and branch:
            raise ValueError('Cannot specify both branch and tag')
        if tag and revision:
            raise ValueError('Cannot specify both branch and tag')

        revision = Commit._parse_revision(revision)
        if branch and branch != self.default_branch and '/' not in branch:
            branch = 'branches/{}'.format(branch)
        additional_args = [
            '^/{}'.format(branch)
        ] if branch and branch != self.default_branch else []
        additional_args += ['^/tags/{}'.format(tag)] if tag else []
        additional_args += ['-r', str(revision)] if revision else []

        info_result = run([self.executable(), 'info'] + additional_args,
                          cwd=self.root_path,
                          capture_output=True,
                          encoding='utf-8')
        if info_result.returncode:
            return {}

        result = {}
        for line in info_result.stdout.splitlines():
            split = line.split(': ')
            result[split[0]] = ': '.join(split[1:])
        return result
示例#3
0
文件: git.py 项目: eocanha/webkit
    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,
        )
示例#4
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,
        )
示例#5
0
文件: svn.py 项目: eocanha/webkit
    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,
        )
示例#6
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,
        )