[git-webkit] Redact certain bug details
https://bugs.webkit.org/show_bug.cgi?id=241833
<rdar://problem/95421487>

Reviewed by Ryan Haddad.

* Tools/Scripts/libraries/webkitbugspy/setup.py: Bump version.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py:
(Tracker.__init__): Pass redaction map to base class.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py:
(Tracker.__init__): Ditto.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py:
(Issue.redacted): Check if the issue matches any redaction filters.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py:
(Tracker.__init__): Pass redaction map to base class.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py:
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py:
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py:
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py:
(Tracker.from_json): Pass redaction map if it's defined.
(Tracker.__init__): Populate redaction map with regexes.
* Tools/Scripts/libraries/webkitscmpy/setup.py: Bump version.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/branch.py:
(Branch.main): Redact branch name if the issue is a redacted configuration.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/commit.py:
(Commit.main): Redact issue title if the issue is a redacted configuration.
* Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py:
* metadata/trackers.json: Redact all radars and security bugs.

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


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295745 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Tools/Scripts/libraries/webkitbugspy/setup.py b/Tools/Scripts/libraries/webkitbugspy/setup.py
index e5004d3..258d244 100644
--- a/Tools/Scripts/libraries/webkitbugspy/setup.py
+++ b/Tools/Scripts/libraries/webkitbugspy/setup.py
@@ -30,7 +30,7 @@
 
 setup(
     name='webkitbugspy',
-    version='0.6.4',
+    version='0.7.0',
     description='Library containing a shared API for various bug trackers.',
     long_description=readme(),
     classifiers=[
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
index b2e395d..167079e 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
@@ -46,7 +46,7 @@
         "Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
     )
 
-version = Version(0, 6, 4)
+version = Version(0, 7, 0)
 
 from .user import User
 from .issue import Issue
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py
index 7d0447e..acd11f0 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py
@@ -56,8 +56,8 @@
                 raise TypeError('Cannot invoke parent class when classmethod')
             return super(Tracker.Encoder, context).default(obj)
 
-    def __init__(self, url, users=None, res=None, login_attempts=3):
-        super(Tracker, self).__init__(users=users)
+    def __init__(self, url, users=None, res=None, login_attempts=3, redact=None):
+        super(Tracker, self).__init__(users=users, redact=redact)
 
         self._logins_left = login_attempts + 1 if login_attempts else 1
         match = self.ROOT_RE.match(url)
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py
index 812dd1f..a5c6ebe 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/github.py
@@ -70,9 +70,9 @@
             self, url, users=None, res=None,
             component_color=DEFAULT_COMPONENT_COLOR,
             version_color=DEFAULT_VERSION_COLOR,
-            session=None
+            session=None, redact=None,
     ):
-        super(Tracker, self).__init__(users=users)
+        super(Tracker, self).__init__(users=users, redact=redact)
 
         self.session = session or requests.Session()
         self.component_color = component_color
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py
index 8194e0f..68c782d 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/issue.py
@@ -181,6 +181,16 @@
             self.tracker.populate(self, 'version')
         return self._version
 
+    @property
+    def redacted(self):
+        match_string = 'title:{};project:{};component:{};version:{}'.format(
+            self.title or '', self.project or '', self.component or '', self.version or '',
+        )
+        for key, value in self.tracker._redact.items():
+            if key.search(match_string):
+                return value
+        return False
+
     def set_component(self, project=None, component=None, version=None):
         return self.tracker.set(self, project=project, component=component, version=version)
 
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
index ac76f19..f1bb859 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
@@ -97,8 +97,8 @@
         except ImportError:
             return None
 
-    def __init__(self, users=None, authentication=None, project=None, projects=None):
-        super(Tracker, self).__init__(users=users)
+    def __init__(self, users=None, authentication=None, project=None, projects=None, redact=None):
+        super(Tracker, self).__init__(users=users, redact=redact)
         self._projects = [project] if project else (projects or [])
 
         self.library = self.radarclient()
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py
index e3c5b66..753e1d2 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py
@@ -414,3 +414,11 @@
                 'Exhausted login attempts\n'
                 'Failed to create bug: Login attempts exhausted\n',
             )
+
+    def test_redaction(self):
+        with mocks.Bugzilla(self.URL.split('://')[1], issues=mocks.ISSUES, projects=mocks.PROJECTS):
+            self.assertEqual(bugzilla.Tracker(self.URL, redact=None).issue(1).redacted, False)
+            self.assertEqual(bugzilla.Tracker(self.URL, redact={'.*': True}).issue(1).redacted, True)
+            self.assertEqual(bugzilla.Tracker(self.URL, redact={'project:WebKit': True}).issue(1).redacted, True)
+            self.assertEqual(bugzilla.Tracker(self.URL, redact={'component:Text': True}).issue(1).redacted, True)
+            self.assertEqual(bugzilla.Tracker(self.URL, redact={'version:Other': True}).issue(1).redacted, True)
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py
index 8928857..5379e6f 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/github_unittest.py
@@ -383,3 +383,11 @@
         with mocks.GitHub(self.URL.split('://')[1], issues=mocks.ISSUES, projects=mocks.PROJECTS):
             issue = github.Tracker(self.URL).issue(1)
             self.assertEqual(issue.labels, ['Other', 'Text'])
+
+    def test_redaction(self):
+        with mocks.GitHub(self.URL.split('://')[1], issues=mocks.ISSUES, projects=mocks.PROJECTS):
+            self.assertEqual(github.Tracker(self.URL, redact=None).issue(1).redacted, False)
+            self.assertEqual(github.Tracker(self.URL, redact={'.*': True}).issue(1).redacted, True)
+            self.assertEqual(github.Tracker(self.URL, redact={'project:WebKit': True}).issue(1).redacted, True)
+            self.assertEqual(github.Tracker(self.URL, redact={'component:Text': True}).issue(1).redacted, True)
+            self.assertEqual(github.Tracker(self.URL, redact={'version:Other': True}).issue(1).redacted, True)
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
index d92a603..b44da22 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
@@ -337,3 +337,30 @@
         with wkmocks.Environment(RADAR_USERNAME='tcontributor'), mocks.Radar(issues=mocks.ISSUES):
             issue = radar.Tracker().issue(1)
             self.assertEqual(issue.labels, [])
+
+    def test_redaction(self):
+        with wkmocks.Environment(RADAR_USERNAME='tcontributor'), mocks.Radar(issues=mocks.ISSUES, projects=mocks.PROJECTS):
+            self.assertEqual(radar.Tracker(
+                project='WebKit',
+                redact=None,
+            ).issue(1).redacted, False)
+
+            self.assertEqual(radar.Tracker(
+                project='WebKit',
+                redact={'.*': True},
+            ).issue(1).redacted, True)
+
+            self.assertEqual(radar.Tracker(
+                project='WebKit',
+                redact={'project:WebKit': True},
+            ).issue(1).redacted, True)
+
+            self.assertEqual(radar.Tracker(
+                project='WebKit',
+                redact={'component:Text': True},
+            ).issue(1).redacted, True)
+
+            self.assertEqual(radar.Tracker(
+                project='WebKit',
+                redact={'version:Other': True},
+            ).issue(1).redacted, True)
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
index 82a857e..3ef9ee8 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
@@ -25,7 +25,7 @@
 
 from .user import User
 
-from webkitcorepy import decorators
+from webkitcorepy import decorators, string_utils
 
 
 class Tracker(object):
@@ -57,9 +57,14 @@
             )[data['type']](
                 url=data.get('url'),
                 res=[re.compile(r) for r in data.get('res', [])],
+                redact=data.get('redact'),
             )
         if data.get('type') == 'radar':
-            return radar.Tracker(project=data.get('project', None), projects=data.get('projects', []))
+            return radar.Tracker(
+                project=data.get('project', None),
+                projects=data.get('projects', []),
+                redact=data.get('redact'),
+            )
         raise TypeError("'{}' is not a recognized tracker type".format(data.get('type')))
 
 
@@ -76,8 +81,18 @@
             return cls._trackers[0]
         return None
 
-    def __init__(self, users=None):
+    def __init__(self, users=None, redact=None):
         self.users = users or User.Mapping()
+        if redact is None:
+            self._redact = {re.compile('.*'): False}
+        elif isinstance(redact, dict):
+            self._redact = {}
+            for key, value in redact.items():
+                if not isinstance(key, string_utils.basestring):
+                    raise ValueError("'{}' is not a string, only strings allowed in redaction mapping".format(key))
+                self._redact[re.compile(key)] = bool(value)
+        else:
+            raise ValueError("Expected redaction mapping to be of type dict, got '{}'".format(type(redact)))
 
     @decorators.hybridmethod
     def from_string(context, string):
diff --git a/Tools/Scripts/libraries/webkitscmpy/setup.py b/Tools/Scripts/libraries/webkitscmpy/setup.py
index d4d2c69..1c730cd 100644
--- a/Tools/Scripts/libraries/webkitscmpy/setup.py
+++ b/Tools/Scripts/libraries/webkitscmpy/setup.py
@@ -29,7 +29,7 @@
 
 setup(
     name='webkitscmpy',
-    version='5.1.0',
+    version='5.1.1',
     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 3a97b61..348e11d 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, 1, 0)
+version = Version(5, 1, 1)
 
 AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
 AutoInstall.register(Package('jinja2', Version(2, 11, 3)))
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/branch.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/branch.py
index 79e1023..819158d 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/branch.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/branch.py
@@ -107,11 +107,11 @@
 
         if string_utils.decode(args.issue).isnumeric() and Tracker.instance() and not redact:
             issue = Tracker.instance().issue(int(args.issue))
-            if issue and issue.title:
+            if issue and issue.title and not issue.redacted:
                 args.issue = cls.to_branch_name(issue.title)
         else:
             issue = Tracker.from_string(args.issue)
-            if issue and issue.title and not redact:
+            if issue and issue.title and not redact and not issue.redacted:
                 args.issue = cls.to_branch_name(issue.title)
             elif issue:
                 args.issue = str(issue.id)
@@ -128,7 +128,7 @@
                     sys.stderr.write('Failed to create new issue\n')
                     return 1
                 print("Created '{}'".format(issue))
-                if issue and issue.title and not redact:
+                if issue and issue.title and not redact and not issue.redacted:
                     args.issue = cls.to_branch_name(issue.title)
                 elif issue:
                     args.issue = str(issue.id)
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/commit.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/commit.py
index 65a0410..df053c0 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/commit.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/commit.py
@@ -96,7 +96,10 @@
             additional_args += ['--amend']
 
         env = os.environ
-        env['COMMIT_MESSAGE_TITLE'] = issue.title if issue else ''
+        if issue and issue.redacted:
+            env['COMMIT_MESSAGE_TITLE'] = '*' * 20
+        else:
+            env['COMMIT_MESSAGE_TITLE'] = issue.title if issue else ''
         env['COMMIT_MESSAGE_BUG'] = '\n'.join(cls.bug_urls(issue))
         return run(
             [repository.executable(), 'commit', '--date=now'] + additional_args + args.args,
diff --git a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py
index 943563c..e9cf42cb 100644
--- a/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py
+++ b/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py
@@ -629,19 +629,19 @@
 
     def test_github_branch_bugzilla(self):
         self.maxDiff = None
-        with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub(projects=bmocks.PROJECTS) as remote, bmocks.Bugzilla(
-            self.BUGZILLA.split('://')[-1],
-            projects=bmocks.PROJECTS, issues=bmocks.ISSUES,
-            environment=Environment(
-                BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com',
-                BUGS_EXAMPLE_COM_PASSWORD='password',
-            )), patch(
-                'webkitbugspy.Tracker._trackers', [bugzilla.Tracker(self.BUGZILLA)],
+        with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub(
+                projects=bmocks.PROJECTS) as remote, bmocks.Bugzilla(
+                self.BUGZILLA.split('://')[-1],
+                projects=bmocks.PROJECTS, issues=bmocks.ISSUES,
+                environment=Environment(
+                    BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com',
+                    BUGS_EXAMPLE_COM_PASSWORD='password',
+                )), patch(
+            'webkitbugspy.Tracker._trackers', [bugzilla.Tracker(self.BUGZILLA)],
         ), mocks.local.Git(
             self.path, remote='https://{}'.format(remote.remote),
             remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
         ) as repo, mocks.local.Svn():
-
             repo.staged['added.txt'] = 'added'
             self.assertEqual(0, program.main(
                 args=('pull-request', '-i', 'https://bugs.example.com/show_bug.cgi?id=1', '-v', '--no-history'),
@@ -686,6 +686,65 @@
             ],
         )
 
+    def test_github_branch_bugzilla_redacted(self):
+        self.maxDiff = None
+        with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub(projects=bmocks.PROJECTS) as remote, bmocks.Bugzilla(
+            self.BUGZILLA.split('://')[-1],
+            projects=bmocks.PROJECTS, issues=bmocks.ISSUES,
+            environment=Environment(
+                BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com',
+                BUGS_EXAMPLE_COM_PASSWORD='password',
+            )), patch(
+                'webkitbugspy.Tracker._trackers', [bugzilla.Tracker(self.BUGZILLA, redact={'.*': True})],
+        ), mocks.local.Git(
+            self.path, remote='https://{}'.format(remote.remote),
+            remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
+        ) as repo, mocks.local.Svn():
+
+            repo.staged['added.txt'] = 'added'
+            self.assertEqual(0, program.main(
+                args=('pull-request', '-i', 'https://bugs.example.com/show_bug.cgi?id=1', '-v', '--no-history'),
+                path=self.path,
+            ))
+
+            self.assertEqual(
+                Tracker.instance().issue(1).comments[-1].content,
+                'Pull request: https://github.example.com/WebKit/WebKit/pull/1',
+            )
+            gh_issue = github.Tracker('https://github.example.com/WebKit/WebKit').issue(1)
+            self.assertEqual(gh_issue.project, 'WebKit')
+            self.assertEqual(gh_issue.component, 'Text')
+            self.assertEqual(gh_issue.version, 'Other')
+
+        self.assertEqual(
+            captured.stdout.getvalue(),
+            "Created the local development branch 'eng/1'\n"
+            "Created 'PR 1 | Example issue 1'!\n"
+            "Posted pull request link to https://bugs.example.com/show_bug.cgi?id=1\n"
+            "https://github.example.com/WebKit/WebKit/pull/1\n",
+        )
+        self.assertEqual(captured.stderr.getvalue(), '')
+        log = captured.root.log.getvalue().splitlines()
+        self.assertEqual(
+            [line for line in log if 'Mock process' not in line], [
+                "Creating the local development branch 'eng/1'...",
+                '    Found 1 commit...',
+                'Creating commit...',
+                "Rebasing 'eng/1' on 'main'...",
+                "Rebased 'eng/1' on 'main!'",
+                "    Found 1 commit...",
+                'Running pre-PR checks...',
+                'No pre-PR checks to run',
+                "Pushing 'eng/1' to 'fork'...",
+                "Syncing 'main' to remote 'fork'",
+                "Creating pull-request for 'eng/1'...",
+                'Checking issue assignee...',
+                'Checking for pull request link in associated issue...',
+                'Syncing PR labels with issue component...',
+                'Synced PR labels with issue component!',
+            ],
+        )
+
     def test_github_reopen_bugzilla(self):
         with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub() as remote, bmocks.Bugzilla(
             self.BUGZILLA.split('://')[-1],
diff --git a/metadata/trackers.json b/metadata/trackers.json
index d758b3e..b628949 100644
--- a/metadata/trackers.json
+++ b/metadata/trackers.json
@@ -1,11 +1,20 @@
 [
-	{
-		"type" : "bugzilla",
-		"url" : "https://bugs.webkit.org",
-		"res" : [
-			"\\Awebkit.org/b/(?P<id>\\d+)\\Z",
-			"\\Ahttps?://webkit.org/b/(?P<id>\\d+)\\Z"
-		]
-	},
-	{ "type" : "radar", "projects" : [ "JavaScriptCore", "WebKit" ] }
+    {
+        "type" : "bugzilla",
+        "url" : "https://bugs.webkit.org",
+        "res" : [
+            "\\Awebkit.org/b/(?P<id>\\d+)\\Z",
+            "\\Ahttps?://webkit.org/b/(?P<id>\\d+)\\Z"
+        ],
+        "redact" : {
+            "product:Security": true
+        }
+    },
+    {
+        "type" : "radar",
+        "projects" : ["JavaScriptCore", "WebKit"],
+        "redact" : {
+            ".*": true
+        }
+    }
 ]