From 9d62c9abf970a05ccff5831a904cf7dbd6656dab Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Tue, 6 Feb 2024 09:45:46 -0800 Subject: [PATCH] Add a tool to delete (redact) gerrit comments This adds a CLI tool that allows a gerrit admin to list and delete change messages and comments. Change-Id: I3600a59520c1f13a24f99f04eaf9ceb17af67fff --- tools/gerrit-delete-comment.py | 172 +++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 tools/gerrit-delete-comment.py diff --git a/tools/gerrit-delete-comment.py b/tools/gerrit-delete-comment.py new file mode 100755 index 0000000000..11a586ab69 --- /dev/null +++ b/tools/gerrit-delete-comment.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 Acme Gating, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys +import json +import requests +import argparse + +BREAK = '------------------------------------------------------------' + + +class Gerrit: + def __init__(self, url, username, password): + if url.endswith('/'): + url = url[:-1] + self.url = url + self.session = requests.Session() + self.session.auth = requests.auth.HTTPBasicAuth(username, password) + + def get(self, path): + url = f'{self.url}{path}' + r = self.session.get(url, + headers={'Accept': 'application/json', + 'Accept-Encoding': 'gzip'}) + if r.status_code == 200: + ret = json.loads(r.text[4:]) + return ret + + def post(self, path, data): + url = f'{self.url}{path}' + r = self.session.post( + url, + data=json.dumps(data).encode('utf8'), + headers={'Content-Type': 'application/json;charset=UTF-8'}) + if r.status_code > 400: + raise Exception("POST to %s failed with http code %s (%s)", + path, r.status_code, r.text) + if r.text and len(r.text) > 4: + return json.loads(r.text[4:]) + + def list_messages(self, change): + data = self.get(f'/a/changes/{change}/messages') + for msg in data: + name = msg['real_author'].get('name') + username = msg['real_author'].get('username') + email = msg['real_author'].get('email') + date = msg['date'] + msgid = msg['id'] + print(BREAK) + print(f'Id : {msgid}') + print(f'Author: {name} ({username}) <{email}>') + print(f'Date : {date}') + print(msg['message']) + + def delete_message(self, change, message_id, reason): + self.post( + f'/a/changes/{change}/messages/{message_id}/delete', + {'reason': reason}) + print("Deleted") + + def list_comments(self, change, revision): + data = self.get(f'/a/changes/{change}/revisions/{revision}/comments') + for path, comments in data.items(): + for msg in comments: + name = msg['author'].get('name') + username = msg['author'].get('username') + email = msg['author'].get('email') + line = msg.get('line') + msgid = msg['id'] + print(BREAK) + print(f'Id : {msgid}') + print(f'Author: {name} ({username}) <{email}>') + print(f'File : {path} line {line}') + print(msg['message']) + + def delete_comment(self, change, revision, comment_id, reason): + self.post( + f'/a/changes/{change}/revisions/{revision}/' + f'comments/{comment_id}/delete', + {'reason': reason}) + print("Deleted") + + +class App: + def __init__(self): + self.parser = argparse.ArgumentParser() + p = self.parser + p.add_argument('url', + help='Gerrit HTTP url') + p.add_argument('username', + help='Username of Gerrit administrator') + p.add_argument('password', + help='Password of Gerrit administrator') + subparsers = p.add_subparsers(title='commands', + help='valid commands') + + cmd_list_messages = subparsers.add_parser( + 'list-messages', help='List change messages') + cmd_list_messages.add_argument('change', + help='Change number') + cmd_list_messages.set_defaults(func=self.list_messages) + + cmd_delete_message = subparsers.add_parser( + 'delete-message', help='Delete change message') + cmd_delete_message.add_argument('change', + help='Change number') + cmd_delete_message.add_argument('message_id', + help='Message ID') + cmd_delete_message.add_argument( + 'reason', + help='Reason for removal (will replace message)') + cmd_delete_message.set_defaults(func=self.delete_message) + + cmd_list_comments = subparsers.add_parser( + 'list-comments', help='List change comments') + cmd_list_comments.add_argument('change', + help='Change number') + cmd_list_comments.add_argument('revision', + help='Change revision') + cmd_list_comments.set_defaults(func=self.list_comments) + + cmd_delete_comment = subparsers.add_parser( + 'delete-comment', help='Delete change comment') + cmd_delete_comment.add_argument('change', + help='Change number') + cmd_delete_comment.add_argument('revision', + help='Change revision') + cmd_delete_comment.add_argument('comment_id', + help='Comment ID') + cmd_delete_comment.add_argument( + 'reason', + help='Reason for removal (will replace comment)') + cmd_delete_comment.set_defaults(func=self.delete_comment) + + args = p.parse_args() + self.args = args + self.gerrit = Gerrit(args.url, args.username, args.password) + if 'func' not in args: + p.print_help() + sys.exit(1) + args.func() + + def list_messages(self): + self.gerrit.list_messages(self.args.change) + + def delete_message(self): + self.gerrit.delete_message(self.args.change, + self.args.message_id, self.args.reason) + + def list_comments(self): + self.gerrit.list_comments(self.args.change, self.args.revision) + + def delete_comment(self): + self.gerrit.delete_comment(self.args.change, self.args.revision, + self.args.comment_id, self.args.reason) + + +if __name__ == '__main__': + App()