[git-webkit] Support ranges in find
https://bugs.webkit.org/show_bug.cgi?id=241806
<rdar://95601346>

Reviewed by Ryan Haddad.

* Tools/Scripts/libraries/webkitscmpy/setup.py: Bump version.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py:
(Git.commits): Use '..'' instead of '...'
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/find.py:
(Info.main): Support .. based range queries.
(Find): Add 'list' alias.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/find_unittest.py:

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


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295741 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Tools/Scripts/libraries/webkitscmpy/setup.py b/Tools/Scripts/libraries/webkitscmpy/setup.py
index 00bda7a..d4d2c69 100644
--- a/Tools/Scripts/libraries/webkitscmpy/setup.py
+++ b/Tools/Scripts/libraries/webkitscmpy/setup.py
@@ -29,7 +29,7 @@
 
 setup(
     name='webkitscmpy',
-    version='5.0.3',
+    version='5.1.0',
     description='Library designed to interact with git and svn repositories.',
     long_description=readme(),
     classifiers=[
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py
index 0f91a65..3a97b61 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py
@@ -46,7 +46,7 @@
         "Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
     )
 
-version = Version(5, 0, 3)
+version = Version(5, 1, 0)
 
 AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
 AutoInstall.register(Package('jinja2', Version(2, 11, 3)))
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py
index 61f7334..fe98c24 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/git.py
@@ -744,7 +744,7 @@
         try:
             log = None
             log = subprocess.Popen(
-                [self.executable(), 'log', '--format=fuller', '--no-decorate', '--date=unix', '{}...{}'.format(end.hash, begin.hash), '--'],
+                [self.executable(), 'log', '--format=fuller', '--no-decorate', '--date=unix', '{}..{}'.format(begin.hash, end.hash), '--'],
                 cwd=self.root_path,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/find.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/find.py
index 93dfbc3..36d8d06 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/find.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/find.py
@@ -57,46 +57,64 @@
             return 1
 
         try:
-            commit = repository.find(reference, include_log=args.include_log)
-        except (local.Scm.Exception, ValueError) as exception:
+            if '..' in reference:
+                if '...' in reference:
+                    sys.stderr.write("'find' sub-command only supports '..' notation\n")
+                    return 1
+                references = reference.split('..')
+                if len(references) > 2:
+                    sys.stderr.write('Can only include two references in a range\n')
+                    return 1
+                commits = [commit for commit in repository.commits(begin=dict(argument=references[0]), end=dict(argument=references[1]))]
+            else:
+                commits = [repository.find(reference, include_log=args.include_log)]
+
+        except (local.Scm.Exception, TypeError, ValueError) as exception:
             # ValueErrors and Scm exceptions usually contain enough information to be displayed
             # to the user as an error
             sys.stderr.write(str(exception) + '\n')
             return 1
 
-        if args.verbose > 0 and not commit.message:
-            sys.stderr.write("Failed to find the  commit message for '{}'\n".format(commit))
-            return 1
+        for commit in commits:
+            if args.verbose > 0 and not commit.message:
+                sys.stderr.write("Failed to find the  commit message for '{}'\n".format(commit))
+                return 1
 
         if args.json:
-            print(json.dumps(commit, cls=Commit.Encoder, indent=4))
+            print(json.dumps(commits if len(commits) > 1 else commits[0], cls=Commit.Encoder, indent=4))
             return 0
 
         if args.verbose < 0:
-            print('{identifier} | {hash}{revision}{title}'.format(
-                identifier=commit,
-                hash=commit.hash[:Commit.HASH_LABEL_SIZE] if commit.hash else '',
-                revision='{}r{}'.format(', ' if commit.hash else '', commit.revision) if commit.revision else '',
-                title=' | {}'.format(commit.message.splitlines()[0]) if commit.message else ''
-            ))
+            for commit in commits:
+                print('{identifier} | {hash}{revision}{title}'.format(
+                    identifier=commit,
+                    hash=commit.hash[:Commit.HASH_LABEL_SIZE] if commit.hash else '',
+                    revision='{}r{}'.format(', ' if commit.hash else '', commit.revision) if commit.revision else '',
+                    title=' | {}'.format(commit.message.splitlines()[0]) if commit.message else ''
+                ))
             return 0
 
-        if commit.message:
-            print(u'Title: {}'.format(commit.message.splitlines()[0]))
-        try:
-            print(u'Author: {}'.format(commit.author))
-        except (UnicodeEncodeError, UnicodeDecodeError):
-            print('Error: Unable to  print commit author name, please file a bug if seeing this locally.')
-        print(datetime.fromtimestamp(commit.timestamp).strftime('Date: %a %b %d %H:%M:%S %Y'))
-        if args.verbose > 0 or commit.revision:
-            print('Revision: {}'.format(commit.revision or 'N/A'))
-        if args.verbose > 0 or commit.hash:
-            print('Hash: {}'.format(commit.hash[:Commit.HASH_LABEL_SIZE] if commit.hash else 'N/A'))
-        print(u'Identifier: {}'.format(commit))
+        previous = False
+        for commit in commits:
+            if previous:
+                print('-' * 20)
+            previous = True
+            if commit.message:
+                print(u'Title: {}'.format(commit.message.splitlines()[0]))
+            try:
+                print(u'Author: {}'.format(commit.author))
+            except (UnicodeEncodeError, UnicodeDecodeError):
+                print('Error: Unable to  print commit author name, please file a bug if seeing this locally.')
+            print(datetime.fromtimestamp(commit.timestamp).strftime('Date: %a %b %d %H:%M:%S %Y'))
+            if args.verbose > 0 or commit.revision:
+                print('Revision: {}'.format(commit.revision or 'N/A'))
+            if args.verbose > 0 or commit.hash:
+                print('Hash: {}'.format(commit.hash[:Commit.HASH_LABEL_SIZE] if commit.hash else 'N/A'))
+            print(u'Identifier: {}'.format(commit))
 
-        if args.verbose > 0:
-            for line in commit.message.splitlines():
-                print(u'    {}'.format(line))
+            if args.verbose > 0:
+                for line in commit.message.splitlines():
+                    print(u'    {}'.format(line))
 
         return 0
 
@@ -104,6 +122,7 @@
 class Find(Command):
     name = 'find'
     help = 'Given an identifier, revision or hash, normalize and print the commit'
+    aliases = ['list']
 
     @classmethod
     def parser(cls, parser, loggers=None):
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/find_unittest.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/find_unittest.py
index a14aad1..1ba3311 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/find_unittest.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/find_unittest.py
@@ -156,6 +156,30 @@
 '''.format(datetime.fromtimestamp(1601663000).strftime('%a %b %d %H:%M:%S %Y')),
         )
 
+    def test_standard_list(self):
+        with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+            self.assertEqual(0, program.main(
+                args=('find', '2@main..4@main'),
+                path=self.path,
+            ))
+        self.assertEqual(
+            captured.stdout.getvalue(),
+            '''Title: 8th commit
+Author: Jonathan Bedard <jbedard@apple.com>
+Date: {}
+Revision: 8
+Hash: bae5d1e90999
+Identifier: 4@main
+--------------------
+Title: 4th commit
+Author: Jonathan Bedard <jbedard@apple.com>
+Date: {}
+Revision: 4
+Hash: 1abe25b443e9
+Identifier: 3@main
+'''.format(datetime.fromtimestamp(1601668000).strftime('%a %b %d %H:%M:%S %Y'), datetime.fromtimestamp(1601663000).strftime('%a %b %d %H:%M:%S %Y')),
+        )
+
     def test_verbose(self):
         with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
             self.assertEqual(0, program.main(
@@ -175,6 +199,39 @@
 '''.format(datetime.fromtimestamp(1601663000).strftime('%a %b %d %H:%M:%S %Y')),
         )
 
+    def test_quiet(self):
+        with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+            self.assertEqual(0, program.main(
+                args=('find', '3@main', '-q'),
+                path=self.path,
+            ))
+        self.assertEqual(
+            captured.stdout.getvalue(),
+            '3@main | 1abe25b443e9, r4 | 4th commit\n',
+        )
+
+    def test_failed_list(self):
+        with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+            self.assertEqual(1, program.main(
+                args=('find', '2@main...4@main', '-q'),
+                path=self.path,
+            ))
+        self.assertEqual(
+            captured.stderr.getvalue(),
+            "'find' sub-command only supports '..' notation\n",
+        )
+
+    def test_quiet_list(self):
+        with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+            self.assertEqual(0, program.main(
+                args=('find', '2@main..4@main', '-q'),
+                path=self.path,
+            ))
+        self.assertEqual(
+            captured.stdout.getvalue(),
+            '4@main | bae5d1e90999, r8 | 8th commit\n3@main | 1abe25b443e9, r4 | 4th commit\n',
+        )
+
     def test_json(self):
         with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
             self.assertEqual(0, program.main(
@@ -197,6 +254,40 @@
                 message='4th commit\ngit-svn-id: https://svn.example.org/repository/repository/trunk@4 268f45cc-cd09-0410-ab3c-d52691b4dbfc',
             ))
 
+    def test_json_list(self):
+        self.maxDiff = None
+        with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+            self.assertEqual(0, program.main(
+                args=('find', '2@main..4@main', '--json'),
+                path=self.path,
+            ))
+
+        decoded = json.loads(captured.stdout.getvalue())
+        self.assertEqual(
+            decoded, [dict(
+                identifier='4@main',
+                hash='bae5d1e90999d4f916a8a15810ccfa43f37a2fd6',
+                revision=8,
+                author=dict(
+                    name='Jonathan Bedard',
+                    emails=['jbedard@apple.com'],
+                ), timestamp=1601668000,
+                order=0,
+                branch='main',
+                message='8th commit\ngit-svn-id: https://svn.example.org/repository/repository/trunk@8 268f45cc-cd09-0410-ab3c-d52691b4dbfc',
+            ), dict(
+                identifier='3@main',
+                hash='1abe25b443e985f93b90d830e4a7e3731336af4d',
+                revision=4,
+                author=dict(
+                    name='Jonathan Bedard',
+                    emails=['jbedard@apple.com'],
+                ), timestamp=1601663000,
+                order=0,
+                branch='main',
+                message='4th commit\ngit-svn-id: https://svn.example.org/repository/repository/trunk@4 268f45cc-cd09-0410-ab3c-d52691b4dbfc',
+            )])
+
     def test_tag_svn(self):
         with OutputCapture() as captured, mocks.local.Git(), mocks.local.Svn(self.path), MockTime:
             self.assertEqual(0, program.main(