⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 285765 in webkit


Ignore:
Timestamp:
Nov 12, 2021, 5:16:24 PM (5 years ago)
Author:
Jonathan Bedard
Message:

[git-webkit] Checkout pull-requests
https://bugs.webkit.org/show_bug.cgi?id=233042
<rdar://problem/85343364>

Reviewed by Dewei Zhu.

In GitHub, pull-requests are somewhat difficult to checkout because they're
attached to a specific user's mirror of WebKit. Automate this process.

  • Scripts/libraries/webkitscmpy/setup.py: Bump version.
  • Scripts/libraries/webkitscmpy/webkitscmpy/init.py: Ditto.
  • Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py:

(Git): Add username:branch regex.
(Git.checkout): Allow checking out of branches by username:branch.

  • Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py:

(Git.init): Add git checkout -B.
(Git.checkout): -B will force checkout a branch, even if one already exists.

  • Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py:

(GitHub): Embed error message in 404 response.

  • Scripts/libraries/webkitscmpy/webkitscmpy/program/checkout.py:

(Checkout.main): Allow direct checkout of pull-request instead of relying
exclusively on branches.

  • Scripts/libraries/webkitscmpy/webkitscmpy/program/clean.py:

(Clean.main): Add newline.

  • Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py:

(TestCheckout.test_no_pr_github):
(TestCheckout.test_no_pr_bitbucket):
(TestCheckout.test_pr_github):
(TestCheckout.test_pr_bitbucket):

Canonical link: https://commits.webkit.org/244211@main

Location:
trunk/Tools
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r285746 r285765  
     12021-11-12  Jonathan Bedard  <jbedard@apple.com>
     2
     3        [git-webkit] Checkout pull-requests
     4        https://bugs.webkit.org/show_bug.cgi?id=233042
     5        <rdar://problem/85343364>
     6
     7        Reviewed by Dewei Zhu.
     8
     9        In GitHub, pull-requests are somewhat difficult to checkout because they're
     10        attached to a specific user's mirror of WebKit. Automate this process.
     11
     12        * Scripts/libraries/webkitscmpy/setup.py: Bump version.
     13        * Scripts/libraries/webkitscmpy/webkitscmpy/__init.py__: Ditto.
     14        * Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py:
     15        (Git): Add username:branch regex.
     16        (Git.checkout): Allow checking out of branches by username:branch.
     17        * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py:
     18        (Git.__init__): Add `git checkout -B`.
     19        (Git.checkout): `-B` will force checkout a branch, even if one already exists.
     20        * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py:
     21        (GitHub): Embed error message in 404 response.
     22        * Scripts/libraries/webkitscmpy/webkitscmpy/program/checkout.py:
     23        (Checkout.main): Allow direct checkout of pull-request instead of relying
     24        exclusively on branches.
     25        * Scripts/libraries/webkitscmpy/webkitscmpy/program/clean.py:
     26        (Clean.main): Add newline.
     27        * Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py:
     28        (TestCheckout.test_no_pr_github):
     29        (TestCheckout.test_no_pr_bitbucket):
     30        (TestCheckout.test_pr_github):
     31        (TestCheckout.test_pr_bitbucket):
     32
    1332021-11-12  Sihui Liu  <sihui_liu@apple.com>
    234
  • trunk/Tools/Scripts/libraries/webkitscmpy/setup.py

    r285272 r285765  
    3030setup(
    3131    name='webkitscmpy',
    32     version='3.0.0',
     32    version='3.0.1',
    3333    description='Library designed to interact with git and svn repositories.',
    3434    long_description=readme(),
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py

    r285272 r285765  
    4747    )
    4848
    49 version = Version(3, 0, 0)
     49version = Version(3, 0, 1)
    5050
    5151AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py

    r285272 r285765  
    300300    HTTP_REMOTE = re.compile(r'(?P<protocol>https?)://(?P<host>[^\/]+)/(?P<path>.+).git')
    301301    REMOTE_BRANCH = re.compile(r'remotes\/(?P<remote>[^\/]+)\/(?P<branch>.+)')
     302    USER_REMOTE = re.compile(r'(?P<username>[^:/]+):(?P<branch>.+)')
    302303
    303304    @classmethod
     
    821822            log_arg = []
    822823
     824        match = self.USER_REMOTE.match(argument)
     825        rmt = self.remote()
     826        if match and isinstance(rmt, remote.GitHub):
     827            username = match.group('username')
     828            if not self.url(match.group('username')):
     829                url = self.url()
     830                if '://' in url:
     831                    rmt = '{}://{}/{}/{}.git'.format(url.split(':')[0], url.split('/')[2], username, rmt.name)
     832                elif ':' in url:
     833                    rmt = '{}:{}/{}.git'.format(url.split(':')[0], username, rmt.name)
     834                else:
     835                    sys.stderr.write("Failed to convert '{}' to '{}' remote\n".format(url, username))
     836                    return None
     837                if run(
     838                    [self.executable(), 'remote', 'add', username, rmt],
     839                    capture_output=True, cwd=self.root_path,
     840                ).returncode:
     841                    sys.stderr.write("Failed to add remote '{}' as '{}'\n".format(rmt, username))
     842                    return None
     843                self.url.clear()
     844            branch = match.group('branch')
     845            rc = run(
     846                [self.executable(), 'checkout'] + ['-B', branch, '{}/{}'.format(username, branch)] + log_arg,
     847                cwd=self.root_path,
     848            ).returncode
     849            if not rc:
     850                return self.commit()
     851            if rc == 128:
     852                run([self.executable(), 'fetch', username], cwd=self.root_path)
     853            return None if run(
     854                [self.executable(), 'checkout'] + ['-B', branch, '{}/{}'.format(username, branch)] + log_arg,
     855                cwd=self.root_path,
     856            ).returncode else self.commit()
     857
    823858        return None if run(
    824859            [self.executable(), 'checkout'] + [self._to_git_ref(argument)] + log_arg,
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py

    r285272 r285765  
    353353                    mocks.ProcessCompletion(returncode=0) if self.checkout(args[3], create=True) else mocks.ProcessCompletion(returncode=1)
    354354            ), mocks.Subprocess.Route(
     355                self.executable, 'checkout', '-B', re.compile(r'.+'),
     356                cwd=self.path,
     357                generator=lambda *args, **kwargs:
     358                    mocks.ProcessCompletion(returncode=0) if self.checkout(args[3], create=True, force=True) else mocks.ProcessCompletion(returncode=1)
     359            ), mocks.Subprocess.Route(
    355360                self.executable, 'checkout', re.compile(r'.+'),
    356361                cwd=self.path,
     
    589594        return result
    590595
    591     def checkout(self, something, create=False):
     596    def checkout(self, something, create=False, force=False):
    592597        commit = self.find(something)
    593598        if create:
    594599            if commit:
     600                if force:
     601                    self.head = commit
     602                    self.detached = something not in self.commits.keys()
     603                    return True
    595604                return False
    596605            if self.head.branch == self.default_branch:
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py

    r285733 r285765  
    2222
    2323import os
    24 import json
    2524import time
    2625
     26import json as jsonlib
    2727from webkitcorepy import mocks
    2828from webkitscmpy import Commit, remote as scmremote
     
    5151
    5252        with open(datafile or os.path.join(os.path.dirname(os.path.dirname(__file__)), 'git-repo.json')) as file:
    53             self.commits = json.load(file)
     53            self.commits = jsonlib.load(file)
    5454        for key, commits in self.commits.items():
    5555            self.commits[key] = [Commit(**kwargs) for kwargs in commits]
     
    146146                status_code=404,
    147147                url=url,
    148                 text=json.dumps(dict(message='No commit found for SHA: {}'.format(ref))),
     148                text=jsonlib.dumps(dict(message='No commit found for SHA: {}'.format(ref))),
    149149            )
    150150
     
    192192                status_code=404,
    193193                url=url,
    194                 text=json.dumps(dict(message='No commit found for SHA: {}'.format(ref))),
     194                text=jsonlib.dumps(dict(message='No commit found for SHA: {}'.format(ref))),
    195195            )
    196196        return mocks.Response.fromJson({
     
    220220                status_code=404,
    221221                url=url,
    222                 text=json.dumps(dict(message='Not found')),
     222                text=jsonlib.dumps(dict(message='Not found')),
    223223            )
    224224
     
    332332            return self._parents_of_request(url=url, ref=stripped_url.split('/')[-1])
    333333
    334         # Check for existance of forked repo
     334        # Check for existence of forked repo
    335335        if stripped_url.startswith('{}/repos'.format(self.api_remote.split('/')[0])) and stripped_url.split('/')[-1] == self.remote.split('/')[-1]:
    336336            username = stripped_url.split('/')[-2]
     
    382382                        key: value for key, value in candidate.items() if key not in ('requested_reviews', 'reviews')
    383383                    }, url=url)
    384             return mocks.Response.create404(url)
     384            return mocks.Response(
     385                status_code=404,
     386                text=jsonlib.dumps(dict(message='Not found')),
     387                url=url,
     388            )
    385389
    386390        # Create/update pull-request
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/checkout.py

    r277730 r285765  
    2121# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    2222
     23import re
    2324import sys
    2425
    2526from .command import Command
    26 from webkitcorepy import arguments
    27 from webkitscmpy import local
     27from webkitscmpy import local, log, remote
    2828
    2929
    3030class Checkout(Command):
    3131    name = 'checkout'
    32     help = 'Given an identifier, revision or hash, normalize and checkout that commit'
     32    help = "Given an identifier, revision, hash or pull-request, normalize and checkout that commit." \
     33           " Pull requests expected in the form 'PR-#'"
     34
     35    PR_RE = re.compile(r'^\[?[Pp][Rr][ -](?P<number>\d+)]?$')
    3336
    3437    @classmethod
     
    4346    def main(cls, args, repository, **kwargs):
    4447        if not repository.path:
    45             sys.stderr.write("Cannot checkout on remote repository")
     48            sys.stderr.write('Cannot checkout on remote repository\n')
    4649            return 1
    4750
     51        target = args.argument[0]
     52        match = cls.PR_RE.match(target)
     53        if match:
     54            rmt = repository.remote()
     55            if not rmt:
     56                sys.stderr.write('Repository does not have associated remote\n')
     57                return 1
     58            if not rmt.pull_requests:
     59                sys.stderr.write('No pull-requests associated with repository\n')
     60                return 1
     61            pr = rmt.pull_requests.get(number=int(match.group('number')))
     62            if not pr:
     63                sys.stderr.write("Failed to find 'PR-{}' associated with this repository\n".format(match.group('number')))
     64                return 1
     65            if isinstance(rmt, remote.GitHub) and pr.author.github:
     66                target = '{}:{}'.format(pr.author.github, pr.head)
     67            else:
     68                target = pr.head
     69            log.warning("Found associated branch '{}' for '{}'".format(target, pr))
     70
    4871        try:
    49             commit = repository.checkout(args.argument[0])
     72            commit = repository.checkout(target)
    5073        except (local.Scm.Exception, ValueError) as exception:
    5174            # ValueErrors and Scm exceptions usually contain enough information to be displayed
     
    5578
    5679        if not commit:
    57             sys.stderr.write("Failed to map '{}'\n".format(args.argument[0]))
     80            sys.stderr.write("Failed to checkout '{}'\n".format(args.argument[0]))
    5881            return 1
    5982        return 0
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/clean.py

    r273142 r285765  
    3535    def main(cls, args, repository, **kwargs):
    3636        if not repository.path:
    37             sys.stderr.write('Cannot clean on remote repository')
     37            sys.stderr.write('Cannot clean on remote repository\n')
    3838            return 1
    3939
  • trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py

    r279347 r285765  
    2222
    2323import os
     24import time
    2425
    2526from webkitcorepy import OutputCapture, testing
    2627from webkitcorepy.mocks import Time as MockTime
    27 from webkitscmpy import program, mocks, local
     28from webkitscmpy import program, mocks, local, Contributor, Commit
    2829
    2930
     
    5859            self.assertEqual('621652add7fc416099bd2063366cc38ff61afe36', local.Git(self.path).commit().hash)
    5960
     61    def test_no_pr_github(self):
     62        with OutputCapture() as captured, mocks.remote.GitHub() as remote, \
     63                mocks.local.Git(self.path, remote='https://{}'.format(remote.remote)), mocks.local.Svn():
     64            self.assertEqual(1, program.main(
     65                args=('checkout', 'PR-1'),
     66                path=self.path,
     67            ))
     68
     69        self.assertEqual(
     70            "Request to 'https://api.github.example.com/repos/WebKit/WebKit/pulls/1' returned status code '404'\n"
     71            "Message: Not found\n"
     72            "Failed to find 'PR-1' associated with this repository\n",
     73            captured.stderr.getvalue(),
     74        )
     75
     76    def test_no_pr_bitbucket(self):
     77        with OutputCapture() as captured, mocks.remote.BitBucket() as remote, mocks.local.Git(self.path, remote='ssh://git@{}/{}/{}.git'.format(
     78            remote.hosts[0], remote.project.split('/')[1], remote.project.split('/')[3],
     79        )), mocks.local.Svn():
     80            self.assertEqual(1, program.main(
     81                args=('checkout', 'PR-1'),
     82                path=self.path,
     83            ))
     84
     85        self.assertEqual(
     86            "Request to 'https://bitbucket.example.com/rest/api/1.0/projects/WEBKIT/repos/webkit/pull-requests/1' returned status code '404'\n"
     87            "Failed to find 'PR-1' associated with this repository\n",
     88            captured.stderr.getvalue(),
     89        )
     90
     91    def test_pr_github(self):
     92        with OutputCapture(), mocks.remote.GitHub() as remote, \
     93                mocks.local.Git(self.path, remote='https://{}'.format(remote.remote)) as repo, mocks.local.Svn():
     94            remote.users = dict(
     95                rreviewer=Contributor('Ricky Reviewer', ['rreviewer@webkit.org'], github='rreviewer'),
     96                tcontributor=Contributor('Tim Contributor', ['tcontributor@webkit.org'], github='tcontributor'),
     97            )
     98            remote.issues = {
     99                1: dict(
     100                    comments=[],
     101                    assignees=[],
     102                )
     103            }
     104            remote.pull_requests = [dict(
     105                number=1,
     106                state='open',
     107                title='Example Change',
     108                user=dict(login='tcontributor'),
     109                body='''#### a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd
     110<pre>
     111To Be Committed
     112
     113Reviewed by NOBODY (OOPS!).
     114</pre>
     115''',
     116                head=dict(ref='tcontributor:eng/example'),
     117                base=dict(ref='main'),
     118                requested_reviews=[dict(login='rreviewer')],
     119                reviews=[dict(user=dict(login='rreviewer'), state='CHANGES_REQUESTED')],
     120            )]
     121            repo.commits['eng/example'] = [Commit(
     122                hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
     123                identifier='3.1@eng/example',
     124                timestamp=int(time.time()) - 60,
     125                author=Contributor('Tim Committer', ['tcommitter@webkit.org']),
     126                message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
     127            )]
     128
     129            self.assertEqual(0, program.main(
     130                args=('checkout', 'PR-1'),
     131                path=self.path,
     132            ))
     133
     134            self.assertEqual('a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd', local.Git(self.path).commit().hash)
     135
     136    def test_pr_bitbucket(self):
     137        with OutputCapture(), mocks.remote.BitBucket() as remote, mocks.local.Git(self.path, remote='ssh://git@{}/{}/{}.git'.format(
     138            remote.hosts[0], remote.project.split('/')[1], remote.project.split('/')[3],
     139        )) as repo, mocks.local.Svn():
     140            remote.pull_requests = [dict(
     141                id=1,
     142                state='OPEN',
     143                open=True,
     144                closed=False,
     145                activities=[],
     146                title='Example Change',
     147                author=dict(
     148                    user=dict(
     149                        name='tcontributor',
     150                        emailAddress='tcontributor@apple.com',
     151                        displayName='Tim Contributor',
     152                    ),
     153                ), body='''#### a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd
     154```
     155To Be Committed
     156
     157Reviewed by NOBODY (OOPS!).
     158```
     159''',
     160                fromRef=dict(displayId='eng/example', id='refs/heads/eng/example'),
     161                toRef=dict(displayId='main', id='refs/heads/main'),
     162                reviewers=[
     163                    dict(
     164                        user=dict(
     165                            displayName='Ricky Reviewer',
     166                            emailAddress='rreviewer@webkit.org',
     167                        ), approved=False,
     168                        status='NEEDS_WORK',
     169                    ),
     170                ],
     171            )]
     172            repo.commits['eng/example'] = [Commit(
     173                hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
     174                identifier='3.1@eng/example',
     175                timestamp=int(time.time()) - 60,
     176                author=Contributor('Tim Committer', ['tcommitter@webkit.org']),
     177                message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
     178            )]
     179
     180            self.assertEqual(0, program.main(
     181                args=('checkout', 'PR-1'),
     182                path=self.path,
     183            ))
     184
     185            self.assertEqual('a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd', local.Git(self.path).commit().hash)
     186
    60187    def test_checkout_svn(self):
    61188        with OutputCapture(), mocks.local.Git(), mocks.local.Svn(self.path), MockTime:
     
    69196            self.assertEqual(4, local.Svn(self.path).commit().revision)
    70197
     198    def test_svn_pr(self):
     199        with OutputCapture() as captured, mocks.local.Git(), mocks.local.Svn(self.path), MockTime:
     200            self.assertEqual(1, program.main(
     201                args=('checkout', 'PR-1'),
     202                path=self.path,
     203            ))
     204
     205            self.assertEqual(
     206                'No pull-requests associated with repository\n',
     207                captured.stderr.getvalue(),
     208            )
     209
    71210    def test_checkout_remote(self):
    72211        with OutputCapture(), mocks.remote.Svn():
Note: See TracChangeset for help on using the changeset viewer.