Diff
Modified: trunk/Tools/ChangeLog (280435 => 280436)
--- trunk/Tools/ChangeLog 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/ChangeLog 2021-07-29 18:32:23 UTC (rev 280436)
@@ -1,5 +1,37 @@
2021-07-29 Jonathan Bedard <jbed...@apple.com>
+ [git-webkit] Add identifiers to 'log' and 'blame'
+ https://bugs.webkit.org/show_bug.cgi?id=228027
+ <rdar://problem/80691164>
+
+ Reviewed by Dewei Zhu.
+
+ * Scripts/libraries/webkitscmpy/setup.py: Bump version.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/local/scm.py:
+ (Scm.from_path): Pass all kwargs to local SCM object.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py:
+ (main): Add Blame and Log commands.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py: Added.
+ (Blame): Invoke pager.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py:
+ (FilteredCommand): Shared command that handles replace hashes/revisions in
+ an SCM command with identifiers.
+ (FilteredCommand.parser): Allow user to change the prefered commit representation.
+ (FilteredCommand.pager): Pass output through more if called from a terminal.
+ (FilteredCommand.main): Modify output of provided command to replace commit representation
+ with the preffered commit representation.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py: Added.
+ (Log): Invoke pager.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py: Added.
+ (TestLog):
+ (TestLog.setUp):
+ (TestLog.test_git):
+ (TestLog.test_git_svn):
+ (TestLog.test_git):
+
+2021-07-29 Jonathan Bedard <jbed...@apple.com>
+
[webkitcorepy] Catch AttributeError when getting password
https://bugs.webkit.org/show_bug.cgi?id=228590
<rdar://problem/81278799>
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/setup.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -29,7 +29,7 @@
setup(
name='webkitscmpy',
- version='0.15.0',
+ version='1.0.0',
description='Library designed to interact with git and svn repositories.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(0, 15, 0)
+version = Version(1, 0, 0)
AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
AutoInstall.register(Package('monotonic', Version(1, 5)))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/scm.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/scm.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/local/scm.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -45,13 +45,13 @@
return os.path.realpath(path)
@classmethod
- def from_path(cls, path, contributors=None):
+ def from_path(cls, path, contributors=None, **kwargs):
from webkitscmpy import local
if local.Git.is_checkout(path):
- return local.Git(path, contributors=contributors)
+ return local.Git(path, contributors=contributors, **kwargs)
if local.Svn.is_checkout(path):
- return local.Svn(path, contributors=contributors)
+ return local.Svn(path, contributors=contributors, **kwargs)
raise OSError("'{}' is not a known SCM type".format(path))
def __init__(self, path, dev_branches=None, prod_branches=None, contributors=None, id=None):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/svn.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/svn.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/svn.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -132,6 +132,15 @@
begin=int(args[3].split(':')[-1]),
) if self.connected else mocks.ProcessCompletion(returncode=1)
), mocks.Subprocess.Route(
+ self.executable, 'log',
+ cwd=self.path,
+ generator=lambda *args, **kwargs:
+ self._log_range(
+ branch='trunk',
+ begin=self.commits['trunk'][0].revision,
+ end=self.commits['trunk'][-1].revision,
+ ) if self.connected else mocks.ProcessCompletion(returncode=1)
+ ), mocks.Subprocess.Route(
self.executable, 'up', '-r', re.compile(r'\d+'),
cwd=self.path,
generator=lambda *args, **kwargs:
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -24,18 +24,20 @@
import logging
import os
-from webkitcorepy import arguments, log as webkitcorepy_log
-from webkitscmpy import local, log, remote
-
+from .blame import Blame
from .canonicalize import Canonicalize
from .clean import Clean
from .command import Command
from .checkout import Checkout
from .find import Find, Info
+from .log import Log
from .pull import Pull
from .setup_git_svn import SetupGitSvn
+from webkitcorepy import arguments, log as webkitcorepy_log
+from webkitscmpy import local, log, remote
+
def main(args=None, path=None, loggers=None, contributors=None, identifier_template=None, subversion=None):
logging.basicConfig(level=logging.WARNING)
@@ -61,7 +63,7 @@
subparsers = parser.add_subparsers(help='sub-command help')
- programs = [Find, Info, Checkout, Canonicalize, Pull, Clean]
+ programs = [Find, Info, Checkout, Canonicalize, Pull, Clean, Log, Blame]
if subversion:
programs.append(SetupGitSvn)
Copied: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py (from rev 280435, trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py) (0 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+
+from webkitscmpy import local
+from webkitscmpy.program.command import FilteredCommand
+
+
+class Blame(FilteredCommand):
+ name = 'blame'
+ help = "Filter raw output of 'git blame' or 'svn blame' to replace native commit representation with identifiers"
+
+ @classmethod
+ def main(cls, args, repository, **kwargs):
+ return cls.pager(args, repository, file=__file__, **kwargs)
+
+
+if __name__ == '__main__':
+ sys.exit(Blame.main(
+ sys.argv[3:],
+ local.Scm.from_path(path=sys.argv[1], cached=True),
+ representation=sys.argv[2],
+ ))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py (280435 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py 2021-07-29 18:18:00 UTC (rev 280435)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -20,9 +20,16 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import os
+import re
+import subprocess
import sys
+import time
+from webkitscmpy.commit import Commit
+from whichcraft import which
+
class Command(object):
name = None
help = None
@@ -38,3 +45,204 @@
def main(cls, args, repository, **kwargs):
sys.stderr.write('No command specified\n')
return -1
+
+
+class FilteredCommand(object):
+ IDENTIFIER = 'identifier'
+ HASH = 'hash'
+ REVISION = 'revision'
+
+ GIT_HEADER_RE = re.compile(r'^commit (?P<hash>[a-f0-9A-F]+)')
+ SVN_HEADER_RE = re.compile(r'^(?P<revision>r/d+) | ')
+
+ REVISION_RES = (re.compile(r'^(?P<revision>\d+)\s'), re.compile(r'(?P<revision>[rR]\d+)'))
+ HASH_RES = (re.compile(r'^(?P<hash>[a-f0-9A-F]{8}[a-f0-9A-F]+)\s'), re.compile(r'(?P<hash>[a-f0-9A-F]{8}[a-f0-9A-F]+)'))
+ IDENTIFIER_RES = (re.compile(r'^(?P<identifier>(\d+\.)?\d+@\S*)'), re.compile(r'(?P<identifier>(\d+\.)?\d+@\S*)'))
+ NO_FILTER_RES = [re.compile(r' Canonical link:'), re.compile(r' git-svn-id:')]
+
+ REPLACE_MODE = 0
+ APPEND_MODE = 1
+ HEADER_MODE = 2
+
+ @classmethod
+ def parser(cls, parser, loggers=None):
+ parser.add_argument(
+ 'args', nargs='*',
+ type=str, default=None,
+ help='Arguments to be passed to tbe native source-code management tool',
+ )
+ parser.add_argument(
+ '--identifier', '-i',
+ help='Represent commits as identifiers',
+ dest='representation',
+ action='', const=cls.IDENTIFIER,
+ default=cls.IDENTIFIER,
+ )
+ parser.add_argument(
+ '--hash',
+ help='Represent commits as hashes',
+ dest='representation',
+ action='', const=cls.HASH,
+ default=cls.IDENTIFIER,
+ )
+ parser.add_argument(
+ '--revision', '-r',
+ help='Represent commits as revisions',
+ dest='representation',
+ action='', const=cls.REVISION,
+ default=cls.IDENTIFIER,
+ )
+ parser.add_argument(
+ '--representation', type=str,
+ help='Change method used to represent a commit (identifier/hash/revision)',
+ dest='representation',
+ default=cls.IDENTIFIER,
+ )
+
+ @classmethod
+ def pager(cls, args, repository, file=None, **kwargs):
+ if not repository.path:
+ sys.stderr.write("Cannot run '{}' on remote repository\n".format(cls.name))
+ return 1
+
+ if not getattr(repository, 'cache', None) and getattr(repository, 'Cache', None):
+ repository.cache = repository.Cache(repository)
+
+ # If we're a terminal, rely on 'more' to display output
+ if sys.stdin.isatty() and not isinstance(args, list) and file:
+ environ = os.environ
+ environ['PYTHONPATH'] = ':'.join(sys.path)
+
+ child = subprocess.Popen(
+ [sys.executable, file, repository.root_path, args.representation] + args.args,
+ env=environ,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ more = subprocess.Popen([which('more'), '-F'], stdin=child.stdout)
+
+ try:
+ while more.poll() is None and not child.poll():
+ time.sleep(0.25)
+ finally:
+ child.kill()
+ more.kill()
+ child_error = child.stderr.read()
+ if child_error:
+ sys.stderr.buffer.write(b'\n' + child_error)
+
+ return child.returncode
+
+ return FilteredCommand.main(args, repository, command=cls.name, **kwargs)
+
+ @classmethod
+ def main(cls, args, repository, command=None, representation=None, **kwargs):
+ if not repository.path:
+ sys.stderr.write("Cannot run '{}' on remote repository\n".format(command))
+ return 1
+
+ cache = getattr(repository, 'cache', None)
+ if not cache:
+ sys.stderr.write('No cache available, cannot performantly map commit references\n')
+ return 1
+
+ if not isinstance(args, list):
+ representation = representation or args.representation
+ args = args.args
+ else:
+ representation = representation or cls.IDENTIFIER
+
+ if representation not in (cls.IDENTIFIER, cls.HASH, cls.REVISION):
+ sys.stderr.write("'{}' is not a valid commit representation\n".format(representation))
+ return 1
+
+ for index in range(len(args)):
+ parsed = Commit.parse(args[index], do_assert=False)
+ if not parsed:
+ continue
+ replacement = None
+ if repository.is_svn:
+ replacement = repository.cache.to_revision(hash=parsed.hash, identifier=str(parsed) if parsed.identifier else None)
+ if repository.is_git:
+ replacement = repository.cache.to_hash(revision=parsed.revision, identifier=str(parsed) if parsed.identifier else None)
+ if replacement:
+ args[index] = replacement
+
+ log_output = subprocess.Popen(
+ [repository.executable(), command] + args,
+ cwd=repository.root_path,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ **(dict(encoding='utf-8') if sys.version_info > (3, 0) else dict())
+ )
+ log_output.poll()
+
+ def replace_line(match, mode=cls.APPEND_MODE, **kwargs):
+ reference = kwargs.get(representation)
+ if not reference:
+ reference = getattr(cache, 'to_{}'.format(representation), lambda **kwargs: None)(**kwargs)
+
+ if reference:
+ if isinstance(reference, int):
+ reference = 'r{}'.format(reference)
+ if representation == 'hash':
+ reference = reference[:Commit.HASH_LABEL_SIZE]
+ if mode == cls.APPEND_MODE:
+ reference = '{} ({})'.format(match.group(1), reference)
+ if mode == cls.HEADER_MODE:
+ alternates = [] if match.group(1).startswith(reference) else [match.group(1)]
+ for repr in {'revision', 'hash', 'identifier'} - {'hash' if repository.is_git else 'revision', representation}:
+ if repr in kwargs:
+ continue
+ other = getattr(cache, 'to_{}'.format(repr), lambda **kwargs: None)(**kwargs)
+ if not other:
+ continue
+ if other == 'hash':
+ other = other[:Commit.HASH_LABEL_SIZE]
+ alternates.append('r{}'.format(other) if isinstance(other, int) else other)
+ reference = '{} ({})'.format(reference, ', '.join(alternates))
+ return match.group(0).replace(match.group(1), reference)
+ return match.group(0)
+
+ res = {}
+ if representation != cls.HASH:
+ res[cls.HASH] = cls.HASH_RES
+ if representation != cls.REVISION:
+ res[cls.REVISION] = cls.REVISION_RES
+ if representation != cls.IDENTIFIER:
+ res[cls.IDENTIFIER] = cls.IDENTIFIER_RES
+
+ header_re = cls.GIT_HEADER_RE if repository.is_git else cls.SVN_HEADER_RE
+
+ try:
+ line = log_output.stdout.readline()
+ while line:
+ header = header_re.sub(
+ lambda match: replace_line(match, mode=cls.HEADER_MODE, **{'hash' if repository.is_git else 'revision': match.group(1)}),
+ line,
+ )
+ if header != line:
+ sys.stdout.write(header)
+ line = log_output.stdout.readline()
+ continue
+
+ for repr, regexs in res.items():
+ line = regexs[0].sub(
+ lambda match: replace_line(match, mode=cls.REPLACE_MODE, **{repr: match.group(1)}),
+ line,
+ )
+
+ if not any(r.match(line) for r in cls.NO_FILTER_RES):
+ for repr, regexs in res.items():
+ line = regexs[1].sub(
+ lambda match: replace_line(match, mode=cls.APPEND_MODE, **{repr: match.group(1)}),
+ line,
+ )
+
+ sys.stdout.write(line)
+
+ line = log_output.stdout.readline()
+
+ finally:
+ log_output.kill()
+ return log_output.returncode
Copied: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py (from rev 280435, trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py) (0 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+
+from webkitscmpy import local
+from webkitscmpy.program.command import FilteredCommand
+
+
+class Log(FilteredCommand):
+ name = 'log'
+ help = "Filter raw output of 'git log' or 'svn log' to replace native commit representation with identifiers"
+
+ @classmethod
+ def main(cls, args, repository, **kwargs):
+ return cls.pager(args, repository, file=__file__, **kwargs)
+
+
+if __name__ == '__main__':
+ sys.exit(Log.main(
+ sys.argv[3:],
+ local.Scm.from_path(path=sys.argv[1], cached=True),
+ representation=sys.argv[2],
+ ))
Added: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py (0 => 280436)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py 2021-07-29 18:32:23 UTC (rev 280436)
@@ -0,0 +1,206 @@
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import json
+import os
+import shutil
+import tempfile
+
+from datetime import datetime
+from webkitcorepy import OutputCapture, testing
+from webkitcorepy.mocks import Time as MockTime
+from webkitscmpy import program, mocks
+
+
+class TestLog(testing.PathTestCase):
+ basepath = 'mock/repository'
+
+ def setUp(self):
+ super(TestLog, self).setUp()
+ os.mkdir(os.path.join(self.path, '.git'))
+ os.mkdir(os.path.join(self.path, '.svn'))
+
+ def test_git(self):
+ with OutputCapture() as captured, mocks.local.Git(self.path), mocks.local.Svn(), MockTime:
+ self.assertEqual(-1, program.main(
+ args=('log', 'main'),
+ path=self.path,
+ ))
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ '''commit 5@main (d8bce26fa65c6fc8f39c17927abb77f69fab82fc)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ Patch Series
+
+commit 4@main (bae5d1e90999d4f916a8a15810ccfa43f37a2fd6)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ 8th commit
+
+commit 3@main (1abe25b443e985f93b90d830e4a7e3731336af4d)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 02:23:20 2020 +0000
+
+ 4th commit
+
+commit 2@main (fff83bb2d9171b4d9196e977eb0508fd57e7a08d)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:50:00 2020 +0000
+
+ 2nd commit
+
+commit 1@main (9b8311f25a77ba14923d9d5a6532103f54abefcb)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:33:20 2020 +0000
+
+ 1st commit
+''',
+ )
+
+ def test_git_svn(self):
+ with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+ self.assertEqual(-1, program.main(
+ args=('log', 'main'),
+ path=self.path,
+ ))
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ '''commit 5@main (d8bce26fa65c6fc8f39c17927abb77f69fab82fc, r9)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ Patch Series
+ git-svn-id: https://svn.example.org/repository/repository/trunk@9 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit 4@main (bae5d1e90999d4f916a8a15810ccfa43f37a2fd6, r8)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ 8th commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@8 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit 3@main (1abe25b443e985f93b90d830e4a7e3731336af4d, r4)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 02:23:20 2020 +0000
+
+ 4th commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@4 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit 2@main (fff83bb2d9171b4d9196e977eb0508fd57e7a08d, r2)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:50:00 2020 +0000
+
+ 2nd commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@2 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit 1@main (9b8311f25a77ba14923d9d5a6532103f54abefcb, r1)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:33:20 2020 +0000
+
+ 1st commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@1 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+''',
+ )
+
+ def test_git_svn_revision(self):
+ with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+ self.assertEqual(-1, program.main(
+ args=('log', 'main', '--revision'),
+ path=self.path,
+ ))
+
+ self.maxDiff = None
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ '''commit r9 (d8bce26fa65c6fc8f39c17927abb77f69fab82fc, 5@main)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ Patch Series
+ git-svn-id: https://svn.example.org/repository/repository/trunk@9 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit r8 (bae5d1e90999d4f916a8a15810ccfa43f37a2fd6, 4@main)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 03:46:40 2020 +0000
+
+ 8th commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@8 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit r4 (1abe25b443e985f93b90d830e4a7e3731336af4d, 3@main)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 02:23:20 2020 +0000
+
+ 4th commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@4 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit r2 (fff83bb2d9171b4d9196e977eb0508fd57e7a08d, 2@main)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:50:00 2020 +0000
+
+ 2nd commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@2 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit r1 (9b8311f25a77ba14923d9d5a6532103f54abefcb, 1@main)
+Author: Jonathan Bedard <jbed...@apple.com>
+Date: Sat Oct 03 01:33:20 2020 +0000
+
+ 1st commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@1 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+''',
+ )
+
+ def test_svn(self):
+ with OutputCapture() as captured, mocks.local.Git(), mocks.local.Svn(self.path), MockTime:
+ self.assertEqual(-1, program.main(
+ args=('log',),
+ path=self.path,
+ ))
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ '''------------------------------------------------------------------------
+r6 (4@trunk) | jbed...@apple.com | 2020-10-02 18:58:20 0000 (Fri, 02 Oct 2020) | 1 lines
+
+6th commit
+
+------------------------------------------------------------------------
+r4 (3@trunk) | jbed...@apple.com | 2020-10-02 18:25:00 0000 (Fri, 02 Oct 2020) | 1 lines
+
+4th commit
+
+------------------------------------------------------------------------
+r2 (2@trunk) | jbed...@apple.com | 2020-10-02 17:51:40 0000 (Fri, 02 Oct 2020) | 1 lines
+
+2nd commit
+
+------------------------------------------------------------------------
+r1 (1@trunk) | jbed...@apple.com | 2020-10-02 17:35:00 0000 (Fri, 02 Oct 2020) | 1 lines
+
+1st commit
+
+''',
+ )