summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSlava Shishkin <slavashishkin@gmail.com>2022-04-15 17:48:45 +0300
committerSlava Shishkin <slavashishkin@gmail.com>2022-04-15 17:48:45 +0300
commitae00f4b6ef0b05bbd40166f57f9c9ffbfe9e4cdf (patch)
treef3fba859d14ccc75a6b16ffacc1a715846142479
parent8fd0053fe139282bc1887f6f6fdc959623e0441a (diff)
downloadgit-bz-ae00f4b6ef0b05bbd40166f57f9c9ffbfe9e4cdf.tar.gz
Apply automated Python 2 to 3 code translation
-rwxr-xr-xgit-bz529
1 files changed, 322 insertions, 207 deletions
diff --git a/git-bz b/git-bz
index de89eb7..44d2203 100755
--- a/git-bz
+++ b/git-bz
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
# -*- coding: utf_8 -*-
#
# git-bz - git subcommand to integrate with bugzilla
@@ -33,7 +33,7 @@
# (generated from git-bz.txt in this directory.)
#
DEFAULT_CONFIG = \
-"""
+ """
default-assigned-to =
default-op-sys = All
default-platform = All
@@ -43,26 +43,26 @@ default-version = unspecified
CONFIG = {}
CONFIG['bugs.freedesktop.org'] = \
-"""
+ """
https = true
default-priority = medium
"""
CONFIG['bugs.gentoo.org'] = \
-"""
+ """
https = true
default-priority = Normal
default-product = Gentoo Linux
"""
CONFIG['bugzilla.gnome.org'] = \
-"""
+ """
https = true
default-priority = Normal
"""
CONFIG['bugzilla.mozilla.org'] = \
-"""
+ """
https = true
default-priority = ---
"""
@@ -80,27 +80,28 @@ git_config = {
################################################################################
import base64
-import cPickle as pickle
-from ConfigParser import RawConfigParser, NoOptionError
-import httplib
-import urllib
+import pickle as pickle
+from configparser import RawConfigParser, NoOptionError
+import http.client
+import urllib.request, urllib.parse, urllib.error
import optparse
import os
+
try:
from sqlite3 import dbapi2 as sqlite
except ImportError:
from pysqlite2 import dbapi2 as sqlite
import re
-from StringIO import StringIO
+from io import StringIO
from subprocess import Popen, CalledProcessError, PIPE
import shutil
import sys
import tempfile
import time
import traceback
-import xmlrpclib
-import urllib
-import urlparse
+import xmlrpc.client
+import urllib.request, urllib.parse, urllib.error
+import urllib.parse
from xml.etree.cElementTree import ElementTree
import base64
import warnings
@@ -109,7 +110,6 @@ import smtplib
import random
import string
-
# Globals
# =======
@@ -117,6 +117,7 @@ import string
global_options = None
bugs_applied = []
+
# Utility functions for git
# =========================
@@ -139,7 +140,7 @@ def git_run(command, *args, **kwargs):
input = None
return_stderr = False
strip = True
- for (k,v) in kwargs.iteritems():
+ for (k, v) in kwargs.items():
if k == '_quiet':
quiet = True
elif k == '_interactive':
@@ -185,20 +186,25 @@ def git_run(command, *args, **kwargs):
else:
return output
+
# Wrapper to allow us to do git.<command>(...) instead of git_run()
class Git:
def __getattr__(self, command):
def f(*args, **kwargs):
return git_run(command, *args, **kwargs)
+
return f
+
git = Git()
+
class GitCommit:
def __init__(self, id, subject):
self.id = id
self.subject = subject
+
def rev_list_commits(*args, **kwargs):
kwargs_copy = dict(kwargs)
kwargs_copy['pretty'] = 'format:%s'
@@ -211,7 +217,7 @@ def rev_list_commits(*args, **kwargs):
raise RuntimeException("git rev-list didn't return an even number of lines")
result = []
- for i in xrange(0, len(lines), 2):
+ for i in range(0, len(lines), 2):
m = re.match("commit\s+([A-Fa-f0-9]+)", lines[i])
if not m:
raise RuntimeException("Can't parse commit it '%s'", lines[i])
@@ -221,6 +227,7 @@ def rev_list_commits(*args, **kwargs):
return result
+
def get_commits(commit_or_revision_range):
# We take specifying a single revision to mean everything since that
# revision, while git-rev-list lists that revision and all ancestors
@@ -238,18 +245,21 @@ def get_commits(commit_or_revision_range):
return commits
+
def get_patch(commit):
# We could pass through -M as an option, but I think you basically always
# want it; showing renames as renames rather than removes/adds greatly
# improves readability.
return git.format_patch(commit.id + "^.." + commit.id, stdout=True, M=True)
+
def get_body(commit):
body = git.log(commit.id + "^.." + commit.id, pretty="format:%b", _strip=False)
# Preserve leading space, which tends to be indents, but strip off
# the trailing newline and any other insignificant space at the end.
return body.rstrip()
+
def commit_is_merge(commit):
contents = git.cat_file("commit", commit.id)
parent_count = 0
@@ -261,6 +271,7 @@ def commit_is_merge(commit):
return parent_count > 1
+
# Global configuration variables
# ==============================
@@ -278,12 +289,14 @@ def init_git_config():
git_config[name] = value
+
def get_tracker():
if global_options.bugzilla != None:
return global_options.bugzilla
return git_config['default-tracker']
+
def get_default_product():
product = git_config['default-product']
if product is None:
@@ -292,6 +305,7 @@ def get_default_product():
return product
+
def get_default_component():
component = git_config['default-component']
if component is None:
@@ -300,6 +314,7 @@ def get_default_component():
return component
+
# Per-tracker configuration variables
# ===================================
@@ -309,6 +324,7 @@ def resolve_host_alias(alias):
except CalledProcessError:
return alias
+
def split_local_config(config_text):
result = {}
@@ -328,6 +344,7 @@ def split_local_config(config_text):
return result
+
def get_git_config(name):
try:
name = name.replace(".", r"\.")
@@ -349,10 +366,12 @@ def get_git_config(name):
return result
+
# We only ever should be the config for one tracker in the course of a single run
cached_config = None
cached_config_tracker = None
+
def get_config(tracker):
global cached_config
global cached_config_tracker
@@ -371,22 +390,26 @@ def get_config(tracker):
return cached_config
+
def tracker_uses_https(tracker):
config = get_config(tracker)
return 'https' in config and config['https'] == 'true'
+
def tracker_get_path(tracker):
config = get_config(tracker)
if 'path' in config:
return config['path']
return None
+
def tracker_get_auth_user(tracker):
config = get_config(tracker)
if 'auth-user' in config:
return config['auth-user']
return None
+
def tracker_get_auth_password(tracker):
config = get_config(tracker)
if 'auth-password' in config:
@@ -395,31 +418,35 @@ def tracker_get_auth_password(tracker):
def merge_default_fields_from_dict(default_fields, d):
- for key, value in d.iteritems():
+ for key, value in d.items():
if key.startswith("default-"):
param = key[8:].replace("-", "_")
if param in ['tracker', 'product', 'component']:
continue
default_fields[param] = value
+
def tracker_get_bz_user(tracker):
config = get_config(tracker)
if 'bz-user' in config:
return config['bz-user']
return None
+
def tracker_get_bz_password(tracker):
config = get_config(tracker)
if 'bz-password' in config:
return config['bz-password']
return None
+
def tracker_get_use_git_credential(tracker):
config = get_config(tracker)
if 'use-git-credential' in config:
return config['use-git-credential'] == 'true'
return False
+
def get_default_fields(tracker):
config = get_config(tracker)
@@ -435,17 +462,20 @@ def get_default_fields(tracker):
return default_fields
+
# Utility functions for bugzilla
# ==============================
class BugParseError(Exception):
pass
+
# A BugHandle is the parsed form of a bug reference string; it
# uniquely identifies a bug on a server, though until we try
# to load it (and create a Bug) we don't know if it actually exists.
class BugHandle:
- def __init__(self, host, path, https, id, auth_user=None, auth_password=None, bz_user=None, bz_password=None, use_git_credential=False):
+ def __init__(self, host, path, https, id, auth_user=None, auth_password=None, bz_user=None, bz_password=None,
+ use_git_credential=False):
self.host = host
self.path = path
self.https = https
@@ -475,7 +505,7 @@ class BugHandle:
@staticmethod
def parse(bug_reference):
- parseresult = urlparse.urlsplit (bug_reference)
+ parseresult = urllib.parse.urlsplit(bug_reference)
if parseresult.scheme in ('http', 'https'):
# Catch http://www.gnome.org and the oddball http:relative/path and http:/path
@@ -512,7 +542,7 @@ class BugHandle:
if bugid is not None:
return BugHandle(host=parseresult.hostname,
path=base_path,
- https=parseresult.scheme=="https",
+ https=parseresult.scheme == "https",
id=bugid,
auth_user=user,
auth_password=password,
@@ -543,13 +573,14 @@ class BugHandle:
if not re.match(r"^.*\.[a-zA-Z]{2,}$", host):
raise BugParseError("'%s' doesn't look like a valid bugzilla host or alias" % host)
- return BugHandle(host=host, path=path, https=https, id=id, auth_user=auth_user, auth_password=auth_password, bz_user=bz_user, bz_password=bz_password, use_git_credential=use_git_credential)
+ return BugHandle(host=host, path=path, https=https, id=id, auth_user=auth_user, auth_password=auth_password,
+ bz_user=bz_user, bz_password=bz_password, use_git_credential=use_git_credential)
@staticmethod
def parse_or_die(str):
try:
return BugHandle.parse(str)
- except BugParseError, e:
+ except BugParseError as e:
die(e.message)
def __hash__(self):
@@ -559,9 +590,11 @@ class BugHandle:
return ((self.host, self.https, self.id) ==
(other.host, other.https, other.id))
+
class CookieError(Exception):
pass
+
def do_get_cookies_from_sqlite(host, cookies_sqlite, browser, query, chromium_time):
result = {}
# We use a timeout of 0 since we expect to hit the browser holding
@@ -570,10 +603,10 @@ def do_get_cookies_from_sqlite(host, cookies_sqlite, browser, query, chromium_ti
try:
cursor = connection.cursor()
- cursor.execute(query, { 'host': host })
+ cursor.execute(query, {'host': host})
now = time.time()
- for name,value,path,expiry in cursor.fetchall():
+ for name, value, path, expiry in cursor.fetchall():
# Excessive caution: toss out values that need to be quoted in a cookie header
expiry = float(expiry)
if chromium_time:
@@ -590,6 +623,7 @@ def do_get_cookies_from_sqlite(host, cookies_sqlite, browser, query, chromium_ti
finally:
connection.close()
+
# Firefox 3.5 keeps the cookies database permamently locked; as a workaround
# hack, we make a copy, read from that, then delete the copy. Of course,
# we may hit an inconsistent state of the database
@@ -598,16 +632,17 @@ def get_cookies_from_sqlite_with_copy(host, cookies_sqlite, browser, *args, **kw
shutil.copyfile(cookies_sqlite, db_copy)
try:
return do_get_cookies_from_sqlite(host, db_copy, browser, *args, **kwargs)
- except sqlite.OperationalError, e:
+ except sqlite.OperationalError as e:
raise CookieError("Cookie database was locked; temporary copy didn't work")
finally:
os.remove(db_copy)
+
def get_cookies_from_sqlite(host, cookies_sqlite, browser, query, chromium_time=False):
try:
result = do_get_cookies_from_sqlite(host, cookies_sqlite, browser, query,
chromium_time=chromium_time)
- except sqlite.OperationalError, e:
+ except sqlite.OperationalError as e:
if "database is locked" in str(e):
# Try making a temporary copy
result = get_cookies_from_sqlite_with_copy(host, cookies_sqlite, browser, query,
@@ -621,10 +656,12 @@ def get_cookies_from_sqlite(host, cookies_sqlite, browser, query, chromium_time=
return result
+
def get_cookies_from_sqlite_xulrunner(host, cookies_sqlite, name):
return get_cookies_from_sqlite(host, cookies_sqlite, name,
"select name,value,path,expiry from moz_cookies where host = :host")
+
def get_bugzilla_cookies_ff3(host):
profiles_dir = os.path.expanduser('~/.mozilla/firefox')
profile_path = None
@@ -636,7 +673,7 @@ def get_bugzilla_cookies_ff3(host):
continue
if (not profile_path or
- (cp.has_option(section, "Default") and cp.get(section, "Default").strip() == "1")):
+ (cp.has_option(section, "Default") and cp.get(section, "Default").strip() == "1")):
profile_path = os.path.join(profiles_dir, cp.get(section, "Path").strip())
if not profile_path:
@@ -648,6 +685,7 @@ def get_bugzilla_cookies_ff3(host):
return get_cookies_from_sqlite_xulrunner(host, cookies_sqlite, "Firefox")
+
def get_bugzilla_cookies_galeon(host):
cookies_sqlite = os.path.expanduser('~/.galeon/mozilla/galeon/cookies.sqlite')
if not os.path.exists(cookies_sqlite):
@@ -655,6 +693,7 @@ def get_bugzilla_cookies_galeon(host):
return get_cookies_from_sqlite_xulrunner(host, cookies_sqlite, "Galeon")
+
def get_bugzilla_cookies_epy(host):
# epiphany-webkit migrated the cookie db to a different location, but the
# format is the same
@@ -673,6 +712,7 @@ def get_bugzilla_cookies_epy(host):
return get_cookies_from_sqlite_xulrunner(host, cookies_sqlite, "Epiphany")
+
# Shared for Chromium and Google Chrome
def get_bugzilla_cookies_chr(host, browser, config_dir):
config_dir = os.path.expanduser(config_dir)
@@ -683,25 +723,30 @@ def get_bugzilla_cookies_chr(host, browser, config_dir):
"select name,value,path,expires_utc from cookies where host_key = :host",
chromium_time=True)
+
def get_bugzilla_cookies_chromium(host):
return get_bugzilla_cookies_chr(host,
"Chromium",
'~/.config/chromium/Default')
+
def get_bugzilla_cookies_google_chrome(host):
return get_bugzilla_cookies_chr(host,
"Google Chrome",
'~/.config/google-chrome/Default')
-browsers = { 'firefox3' : get_bugzilla_cookies_ff3,
- 'epiphany' : get_bugzilla_cookies_epy,
- 'galeon' : get_bugzilla_cookies_galeon,
- 'chromium' : get_bugzilla_cookies_chromium,
- 'google-chrome': get_bugzilla_cookies_google_chrome }
+
+browsers = {'firefox3': get_bugzilla_cookies_ff3,
+ 'epiphany': get_bugzilla_cookies_epy,
+ 'galeon': get_bugzilla_cookies_galeon,
+ 'chromium': get_bugzilla_cookies_chromium,
+ 'google-chrome': get_bugzilla_cookies_google_chrome}
+
def browser_list():
return ", ".join(sorted(browsers.keys()))
+
def get_bugzilla_cookies(host):
browser = git_config['browser']
if browser in browsers:
@@ -711,7 +756,7 @@ def get_bugzilla_cookies(host):
try:
return do_get_cookies(host)
- except CookieError, e:
+ except CookieError as e:
die("""Error getting login cookie from browser:
%s
@@ -719,6 +764,7 @@ Configured browser: %s (change with 'git config --global bz.browser <value>')
Possible browsers: %s""" %
(str(e), browser, browser_list()))
+
# Based on http://code.activestate.com/recipes/146306/ - Wade Leftwich
def encode_multipart_formdata(fields, files=None):
"""
@@ -757,10 +803,12 @@ def encode_multipart_formdata(fields, files=None):
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
+
# Cache of constant-responses per bugzilla server
# ===============================================
-CACHE_EXPIRY_TIME = 3600 * 24 # one day
+CACHE_EXPIRY_TIME = 3600 * 24 # one day
+
class Cache(object):
def __init__(self):
@@ -793,8 +841,10 @@ class Cache(object):
self.cfp.write(f)
f.close()
+
cache = Cache()
+
# General Utility Functions
# =========================
@@ -805,6 +855,7 @@ def make_filename(description):
return filename
+
def edit_file(filename):
editor = git.var("GIT_EDITOR")
process = Popen(editor + " " + filename, shell=True)
@@ -812,6 +863,7 @@ def edit_file(filename):
if process.returncode != 0:
die("Editor exited with non-zero return code")
+
def edit_template(template):
# Prompts the user to edit the text 'template' and returns list of
# lines with comments stripped
@@ -824,11 +876,12 @@ def edit_template(template):
edit_file(filename)
f = open(filename, "r")
- lines = filter(lambda x: not x.startswith("#"), f.readlines())
+ lines = [x for x in f.readlines() if not x.startswith("#")]
f.close()
return lines
+
def split_subject_body(lines):
# Splits the first line (subject) from the subsequent lines (body)
@@ -842,22 +895,26 @@ def split_subject_body(lines):
return subject, "".join(lines[i + 1:]).strip()
+
def _shortest_unique_abbreviation(full, l):
- for i in xrange(1, len(full) + 1):
+ for i in range(1, len(full) + 1):
abbrev = full[0:i]
if not any((x != full and x.startswith(abbrev) for x in l)):
return abbrev
# Duplicate items or one item is a prefix of another
raise ValueError("%s has no unique abbreviation in %s" % (full, l))
+
def _abbreviation_item_help(full, l):
abbrev = _shortest_unique_abbreviation(full, l)
return '[%s]%s' % (abbrev, full[len(abbrev):])
+
# Return '[a]pple, [pe]ar, [po]tato'
def abbreviation_help_string(l):
return ", ".join((_abbreviation_item_help(full, l) for full in l))
+
# Find the unique element in l that starts with abbrev
def expand_abbreviation(abbrev, l):
for full in l:
@@ -865,6 +922,7 @@ def expand_abbreviation(abbrev, l):
return full
raise ValueError("No unique abbreviation expansion")
+
def prompt(message):
while True:
# Using print here could result in Python adding a stray space
@@ -876,6 +934,7 @@ def prompt(message):
elif line == 'n' or line == 'N':
return False
+
def prompt_multi(message, options):
while True:
# Using print here could result in Python adding a stray space
@@ -886,13 +945,16 @@ def prompt_multi(message, options):
if opt in options:
return opt
+
def die(message):
- print >>sys.stderr, message
+ print(message, file=sys.stderr)
sys.exit(1)
+
def http_auth_header(user, password):
return 'Basic ' + base64.encodestring("%s:%s" % (user, password)).strip()
+
# Classes for bug handling
# ========================
@@ -900,25 +962,30 @@ class BugPatch(object):
def __init__(self, attach_id):
self.attach_id = attach_id
+
class NoXmlRpcError(Exception):
pass
+
connections = {}
+
def get_connection(host, https):
identifier = (host, https)
if not identifier in connections:
if https:
- connection = httplib.HTTPSConnection(host, 443)
+ connection = http.client.HTTPSConnection(host, 443)
else:
- connection = httplib.HTTPConnection(host, 80)
+ connection = http.client.HTTPConnection(host, 80)
connections[identifier] = connection
return connections[identifier]
+
class BugServer(object):
- def __init__(self, host, path, https, auth_user=None, auth_password=None, bz_user=None, bz_password=None, use_git_credential=False):
+ def __init__(self, host, path, https, auth_user=None, auth_password=None, bz_user=None, bz_password=None,
+ use_git_credential=False):
self.host = host
self.path = path
self.https = https
@@ -950,14 +1017,17 @@ class BugServer(object):
connection.request("GET", self.path + "/index.cgi", '', headers)
res = connection.getresponse()
match = re.search(r'name="Bugzilla_login_token"[\s]+value="([^"]*)', res.read())
- login_token = match.group(1)
+ login_token = match.group(1)
headers = dict({})
headers['Cookie'] = login_request_cookie
headers['User-Agent'] = "git-bz"
if self.use_git_credential:
protocol = 'https' if self.https else 'http'
- git_credential_input = "protocol={protocol}\nhost={host}\npath={path}\n\n".format(protocol=protocol, host=self.host, path=self.path.lstrip('/'))
+ git_credential_input = "protocol={protocol}\nhost={host}\npath={path}\n\n".format(protocol=protocol,
+ host=self.host,
+ path=self.path.lstrip(
+ '/'))
process = Popen(["git", "credential", "fill"],
stdout=PIPE,
stdin=PIPE)
@@ -972,7 +1042,9 @@ class BugServer(object):
# now that we have both token and login request cookie
# authentication should now work
- connection.request("POST", self.path + "/index.cgi", urllib.urlencode({'Bugzilla_login':bz_user,'Bugzilla_password':bz_password,'Bugzilla_login_token':login_token}), headers)
+ connection.request("POST", self.path + "/index.cgi", urllib.parse.urlencode(
+ {'Bugzilla_login': bz_user, 'Bugzilla_password': bz_password, 'Bugzilla_login_token': login_token}),
+ headers)
res = connection.getresponse()
if self.use_git_credential:
@@ -980,10 +1052,10 @@ class BugServer(object):
# successful login
match = re.search('Invalid Login', res.read())
if match:
- process = Popen(["git", "credential", "reject"], stdin = PIPE)
+ process = Popen(["git", "credential", "reject"], stdin=PIPE)
process.communicate(git_credential_output)
else:
- process = Popen(["git", "credential", "approve"], stdin = PIPE)
+ process = Popen(["git", "credential", "approve"], stdin=PIPE)
process.communicate(git_credential_output)
self.cookiestring = res.getheader('set-cookie')
@@ -997,7 +1069,7 @@ class BugServer(object):
def send_request(self, method, url, data=None, headers={}):
headers = dict(headers)
cookies = self.get_cookie_string()
- if isinstance(cookies, unicode):
+ if isinstance(cookies, str):
cookies = cookies.encode('UTF-8')
headers['Cookie'] = cookies
headers['User-Agent'] = "git-bz"
@@ -1038,8 +1110,8 @@ class BugServer(object):
if new_url in seen_urls or len(seen_urls) >= 10:
die("Circular redirect or too many redirects")
- old_split = urlparse.urlsplit(url)
- new_split = urlparse.urlsplit(new_url)
+ old_split = urllib.parse.urlsplit(url)
+ new_split = urllib.parse.urlsplit(new_url)
new_https = new_split.scheme == 'https'
@@ -1054,9 +1126,8 @@ class BugServer(object):
# attachment.cgi, though we alternatively could just exclude
# attachment.cgi here.
if (response.status in (301, 302) and
- method == 'GET' and
- old_split.path == '/show_bug.cgi' and new_split.path == '/show_bug.cgi'):
-
+ method == 'GET' and
+ old_split.path == '/show_bug.cgi' and new_split.path == '/show_bug.cgi'):
self.host = new_split.hostname
self.https = new_https
@@ -1068,13 +1139,13 @@ class BugServer(object):
method = 'GET'
# Get the relative component of the new URL
- url = urlparse.urlunsplit((None, None, new_split.path, new_split.query, new_split.fragment))
+ url = urllib.parse.urlunsplit((None, None, new_split.path, new_split.query, new_split.fragment))
else:
return response
def send_post(self, url, fields, files=None):
content_type, body = encode_multipart_formdata(fields, files)
- return self.send_request("POST", url, data=body, headers={ 'Content-Type': content_type })
+ return self.send_request("POST", url, data=body, headers={'Content-Type': content_type})
def get_xmlrpc_proxy(self):
if self._xmlrpc_proxy is None:
@@ -1084,42 +1155,42 @@ class BugServer(object):
transport = SafeBugTransport(self)
else:
transport = BugTransport(self)
- self._xmlrpc_proxy = xmlrpclib.ServerProxy(uri, transport)
+ self._xmlrpc_proxy = xmlrpc.client.ServerProxy(uri, transport)
return self._xmlrpc_proxy
def _product_id(self, product_name):
# This way works with newer bugzilla; older Bugzilla doesn't support names:
try:
- response = self.get_xmlrpc_proxy().Product.get({ 'names': product_name, 'include_fields': ['id', 'name'] })
+ response = self.get_xmlrpc_proxy().Product.get({'names': product_name, 'include_fields': ['id', 'name']})
products = response['products']
if len(products) > 0:
return products[0]['id']
- except xmlrpclib.Fault, e:
+ except xmlrpc.client.Fault as e:
pass
- except xmlrpclib.ProtocolError, e:
+ except xmlrpc.client.ProtocolError as e:
pass
# This should work with any bugzilla that supports xmlrpc, but will be slow
- print >>sys.stderr, "Searching for product ID ...",
+ print("Searching for product ID ...", end=' ', file=sys.stderr)
try:
response = self.get_xmlrpc_proxy().Product.get_accessible_products({})
ids = response['ids']
- response = self.get_xmlrpc_proxy().Product.get_products({ 'ids': ids, 'include_fields': ['id', 'name']})
+ response = self.get_xmlrpc_proxy().Product.get_products({'ids': ids, 'include_fields': ['id', 'name']})
for p in response['products']:
if p['name'] == product_name:
- print >>sys.stderr, "found it"
+ print("found it", file=sys.stderr)
return p['id']
- except xmlrpclib.Fault, e:
+ except xmlrpc.client.Fault as e:
pass
- except xmlrpclib.ProtocolError, e:
+ except xmlrpc.client.ProtocolError as e:
pass
- print >>sys.stderr, "failed"
+ print("failed", file=sys.stderr)
return None
def product_id(self, product_name):
- key = 'product_id_' + urllib.quote(product_name)
+ key = 'product_id_' + urllib.parse.quote(product_name)
try:
return cache.get(self.host, key)
except IndexError:
@@ -1132,17 +1203,17 @@ class BugServer(object):
# array, or None if the query failed
def _legal_values(self, field):
try:
- response = self.get_xmlrpc_proxy().Bug.legal_values({ 'field': field })
+ response = self.get_xmlrpc_proxy().Bug.legal_values({'field': field})
cache.set(self.host, 'legal_' + field, response['values'])
return response['values']
- except xmlrpclib.Fault, e:
- if e.faultCode == -32000: # https://bugzilla.mozilla.org/show_bug.cgi?id=513511
+ except xmlrpc.client.Fault as e:
+ if e.faultCode == -32000: # https://bugzilla.mozilla.org/show_bug.cgi?id=513511
return None
raise
- except xmlrpclib.ProtocolError, e:
- if e.errcode == 500: # older bugzilla versions die this way
+ except xmlrpc.client.ProtocolError as e:
+ if e.errcode == 500: # older bugzilla versions die this way
return None
- elif e.errcode == 404: # really old bugzilla, no XML-RPC
+ elif e.errcode == 404: # really old bugzilla, no XML-RPC
return None
raise
@@ -1154,28 +1225,33 @@ class BugServer(object):
cache.set(self.host, 'legal_' + field, values)
return values
+
# mixin for xmlrpclib.Transport classes to add cookies
class CookieTransportMixin(object):
def send_request(self, connection, *args):
- xmlrpclib.Transport.send_request(self, connection, *args)
+ xmlrpc.client.Transport.send_request(self, connection, *args)
cookie = self.server.get_cookie_string()
- if isinstance(cookie, unicode):
+ if isinstance(cookie, str):
cookie = cookie.encode('UTF-8')
connection.putheader("Cookie", cookie)
connection.putheader("Authorization", http_auth_header(self.server.auth_user, self.server.auth_password))
-class BugTransport(CookieTransportMixin, xmlrpclib.Transport):
+
+class BugTransport(CookieTransportMixin, xmlrpc.client.Transport):
def __init__(self, server):
- xmlrpclib.Transport.__init__(self)
+ xmlrpc.client.Transport.__init__(self)
self.server = server
-class SafeBugTransport(CookieTransportMixin, xmlrpclib.SafeTransport):
+
+class SafeBugTransport(CookieTransportMixin, xmlrpc.client.SafeTransport):
def __init__(self, server):
- xmlrpclib.SafeTransport.__init__(self)
+ xmlrpc.client.SafeTransport.__init__(self)
self.server = server
+
servers = {}
+
# Note that if we detect that we are redirecting, we may rewrite the
# host/https of the server to avoid doing too many redirections, and
# so the host,https we connect to may be different than what we use
@@ -1183,7 +1259,8 @@ servers = {}
def get_bug_server(host, path, https, auth_user, auth_password, bz_user, bz_password, use_git_credential):
identifier = (host, path, https)
if not identifier in servers:
- servers[identifier] = BugServer(host, path, https, auth_user, auth_password, bz_user, bz_password, use_git_credential)
+ servers[identifier] = BugServer(host, path, https, auth_user, auth_password, bz_user, bz_password,
+ use_git_credential)
return servers[identifier]
@@ -1201,7 +1278,6 @@ def get_bug_server(host, path, https, auth_user, auth_password, bz_user, bz_pass
# that indicate success. Returns the matched regular expression
# on success, None otherwise
def check_for_success(response, response_data, *args):
-
if response.status != 200:
return False
@@ -1212,6 +1288,7 @@ def check_for_success(response, response_data, *args):
return None
+
class Bug(object):
def __init__(self, server):
self.server = server
@@ -1228,7 +1305,7 @@ class Bug(object):
response = self.server.send_request("GET", url)
if response.status != 200:
- die ("Failed to retrieve bug information: %d" % response.status)
+ die("Failed to retrieve bug information: %d" % response.status)
etree = ElementTree()
etree.parse(response)
@@ -1236,7 +1313,7 @@ class Bug(object):
bug = etree.find("bug")
error = bug.get("error")
if error != None:
- die ("Failed to retrieve bug information: %s" % error)
+ die("Failed to retrieve bug information: %s" % error)
self.id = int(bug.find("bug_id").text)
self.short_desc = bug.find("short_desc").text
@@ -1248,10 +1325,10 @@ class Bug(object):
patch_complexity = bug.find("cf_patch_complexity")
self.patch_complexity = None if patch_complexity is None else patch_complexity.text
depends = bug.findall("dependson")
- self.depends = None if depends is None else ' '.join(map(lambda e: e.text, depends))
+ self.depends = None if depends is None else ' '.join([e.text for e in depends])
for attachment in bug.findall("attachment"):
- if attachment.get("ispatch") == "1" and not attachment.get("isobsolete") == "1" :
+ if attachment.get("ispatch") == "1" and not attachment.get("isobsolete") == "1":
attach_id = int(attachment.find("attachid").text)
patch = BugPatch(attach_id)
# We have to save fields we might not otherwise care about
@@ -1283,13 +1360,13 @@ class Bug(object):
url = "/attachment.cgi?bugid=" + str(self.id) + "&action=enter"
response = self.server.send_request("GET", url)
if response.status != 200:
- return None
+ return None
match = re.search(r'name="token" value="([^"]+)',
- response.read())
+ response.read())
if not match:
- return None
- return match.group(1)
+ return None
+ return match.group(1)
def _create_via_xmlrpc(self, product, component, short_desc, comment, default_fields):
params = dict()
@@ -1297,20 +1374,20 @@ class Bug(object):
params['component'] = component
params['summary'] = short_desc
params['description'] = comment
- for (field, value) in default_fields.iteritems():
+ for (field, value) in default_fields.items():
params[field] = value
try:
response = self.server.get_xmlrpc_proxy().Bug.create(params)
self.id = response['id']
- except xmlrpclib.Fault, e:
+ except xmlrpc.client.Fault as e:
die(e.faultString)
- except xmlrpclib.ProtocolError, e:
+ except xmlrpc.client.ProtocolError as e:
if e.errcode == 404:
raise NoXmlRpcError(e.errmsg)
else:
- print >>sys.stderr, "Problem filing bug via XML-RPC: %s (%d)\n" % (e.errmsg, e.errcode)
- print >>sys.stderr, "falling back to form post\n"
+ print("Problem filing bug via XML-RPC: %s (%d)\n" % (e.errmsg, e.errcode), file=sys.stderr)
+ print("falling back to form post\n", file=sys.stderr)
raise NoXmlRpcError("Communication error")
def _create_with_form(self, product, component, short_desc, comment, default_fields):
@@ -1322,7 +1399,7 @@ class Bug(object):
# post_bug.cgi wants some names that are less congenial than the names
# expected in XML-RPC.
- for (field, value) in default_fields.iteritems():
+ for (field, value) in default_fields.items():
if field == 'severity':
field = 'bug_severity'
elif field == 'platform':
@@ -1348,7 +1425,7 @@ class Bug(object):
m = check_for_success(response, response_data,
r"<title>\s*Bug\s+([0-9]+)")
if not m:
- print response_data
+ print(response_data)
die("Failed to create bug, status=%d" % response.status)
self.id = int(m.group(1))
@@ -1359,16 +1436,16 @@ class Bug(object):
except NoXmlRpcError:
self._create_with_form(product, component, short_desc, comment, default_fields)
- print "Successfully created"
- print "Bug %d - %s" % (self.id, short_desc)
- print self.get_url()
+ print("Successfully created")
+ print("Bug %d - %s" % (self.id, short_desc))
+ print(self.get_url())
def create_patch(self, description, comment, filename, data, obsoletes=[], status='none'):
fields = {}
fields['bugid'] = str(self.id)
token = self.get_attachment_token()
if token:
- fields['token'] = token
+ fields['token'] = token
fields['action'] = 'insert'
fields['ispatch'] = '1'
fields['attachments.status'] = status
@@ -1378,9 +1455,9 @@ class Bug(object):
if obsoletes:
# this will produce multiple parts in the encoded data with the
# name 'obsolete' for each item in the list
- fields['obsolete'] = map(str, obsoletes)
+ fields['obsolete'] = list(map(str, obsoletes))
- files = { 'data': (filename, 'text/plain; charset=UTF-8', data) }
+ files = {'data': (filename, 'text/plain; charset=UTF-8', data)}
response = self.server.send_post("/attachment.cgi", fields, files)
response_data = response.read()
@@ -1389,23 +1466,23 @@ class Bug(object):
r"<title>\s*Changes\s+Submitted",
# Newer bugzilla's, use, instead:
r"<title>\s*Attachment\s+\d+\s+added"):
- print response_data
- die ("Failed to attach patch to bug %d, status=%d" % (self.id, response.status))
+ print(response_data)
+ die("Failed to attach patch to bug %d, status=%d" % (self.id, response.status))
- print "Attached %s" % filename
+ print("Attached %s" % filename)
if global_options.mail:
- N=6
+ N = 6
tempfile = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(N))
- f = open('/tmp/'+tempfile, 'w')
+ f = open('/tmp/' + tempfile, 'w')
f.write(data)
f.close()
mlist = "koha-patches@lists.koha-community.org"
- str1 = "git send-email --quiet --confirm never --to '" + mlist +"' /tmp/"+tempfile
+ str1 = "git send-email --quiet --confirm never --to '" + mlist + "' /tmp/" + tempfile
import os
retvalue = os.system(str1)
- print retvalue
+ print(retvalue)
# Update specified fields of a bug; keyword arguments are interpreted
# as field_name=value
@@ -1420,11 +1497,10 @@ class Bug(object):
response_data = response.read()
if not check_for_success(response, response_data,
r"<title>\s*[Bug]*[\S\s]*processed\s*</title>"):
-
# Mid-air collisions would be indicated by
# "<title>Mid-air collision!</title>"
- print response_data
- die ("Failed to update bug %d, status=%d" % (self.id, response.status))
+ print(response_data)
+ die("Failed to update bug %d, status=%d" % (self.id, response.status))
# Update specified fields of an attachment; keyword arguments are
# interpreted as field_name=value
@@ -1447,8 +1523,8 @@ class Bug(object):
if patch.status is not None:
fields['attachments.status'] = patch.status
- for (field, value) in changes.iteritems():
- if field == 'status': # encapsulate oddball form field name
+ for (field, value) in changes.items():
+ if field == 'status': # encapsulate oddball form field name
field = 'attachments.status'
fields[field] = value
@@ -1456,10 +1532,10 @@ class Bug(object):
response_data = response.read()
if not check_for_success(response, response_data,
r"<title>\s*Changes\s+Submitted"):
- print response_data
- die ("Failed to update attachment %d to bug %d, status=%d" % (patch.attach_id,
- self.id,
- response.status))
+ print(response_data)
+ die("Failed to update attachment %d to bug %d, status=%d" % (patch.attach_id,
+ self.id,
+ response.status))
def get_url(self):
return "%s://%s/show_bug.cgi?id=%d" % ("https" if self.server.https else "http",
@@ -1468,7 +1544,9 @@ class Bug(object):
@staticmethod
def load(bug_reference, attachmentdata=False):
- server = get_bug_server(bug_reference.host, bug_reference.path, bug_reference.https, bug_reference.auth_user, bug_reference.auth_password, bug_reference.bz_user, bug_reference.bz_password, bug_reference.use_git_credential)
+ server = get_bug_server(bug_reference.host, bug_reference.path, bug_reference.https, bug_reference.auth_user,
+ bug_reference.auth_password, bug_reference.bz_user, bug_reference.bz_password,
+ bug_reference.use_git_credential)
bug = Bug(server)
bug._load(bug_reference.id, attachmentdata)
@@ -1492,25 +1570,27 @@ class Bug(object):
return bug
+
# The Commands
# =============
def commit_needs_url(commit, bug_id):
- pat = re.compile(r"(?<!\d)%d(?!\d)" % bug_id)
- return (pat.search(commit.subject) is None and
- pat.search(get_body(commit)) is None)
+ pat = re.compile(r"(?<!\d)%d(?!\d)" % bug_id)
+ return (pat.search(commit.subject) is None and
+ pat.search(get_body(commit)) is None)
+
def check_add_url(commits, bug_id=None, is_add_url=False):
if bug_id != None:
# We only need to check the commits that we'll add the URL to
commits = [commit for commit in commits if commit_needs_url(commit, bug_id)]
- if len(commits) == 0: # Nothing to do
+ if len(commits) == 0: # Nothing to do
return
try:
git.diff(exit_code=True, ignore_submodules=True, _quiet=True)
- git.diff(exit_code=True, ignore_submodules=True, cached=True, _quiet=True)
+ git.diff(exit_code=True, ignore_submodules=True, cached=True, _quiet=True)
except CalledProcessError:
die("Cannot add bug reference to commit message(s); You must commit (or stash) all changes first")
@@ -1523,13 +1603,13 @@ def check_add_url(commits, bug_id=None, is_add_url=False):
# see if the commit is present in any remote branches
remote_branches = git.branch(contains=commit.id, r=True)
if remote_branches != "":
- print commit.id[0:7], commit.subject
- print "Commit is already in remote branch(es):", " ".join(remote_branches.split())
+ print(commit.id[0:7], commit.subject)
+ print("Commit is already in remote branch(es):", " ".join(remote_branches.split()))
if not prompt("Rewrite the commit add the bug URL anyways?"):
if is_add_url:
- print "Aborting."
+ print("Aborting.")
else:
- print "Aborting. You can use -n/--no-add-url to turn off adding the URL"
+ print("Aborting. You can use -n/--no-add-url to turn off adding the URL")
sys.exit(0)
# Check for merge commits
@@ -1537,16 +1617,18 @@ def check_add_url(commits, bug_id=None, is_add_url=False):
all_commits = rev_list_commits(commits[-1].id + "^..HEAD")
for commit in all_commits:
if commit_is_merge(commit):
- print "Found merge commit:"
- print commit.id[0:7], commit.subject
- print "Can't rewrite this commit or an ancestor commit to add bug URL"
+ print("Found merge commit:")
+ print(commit.id[0:7], commit.subject)
+ print("Can't rewrite this commit or an ancestor commit to add bug URL")
sys.exit(1)
+
def bad_url_method(add_url_method):
die("""add-url-method '%s' is invalid
Should be [subject-prepend|subject-append|body-prepend|body-append]:<format>""" %
add_url_method)
+
def add_url_to_subject_body(subject, body, bug):
add_url_method = git_config['add-url-method']
if not ':' in add_url_method:
@@ -1581,10 +1663,12 @@ def add_url_to_subject_body(subject, body, bug):
return subject, body
+
def validate_add_url_method(bug):
# Dry run
add_url_to_subject_body("", "", bug)
+
def add_url_to_head_commit(commit, bug):
subject = commit.subject
body = get_body(commit)
@@ -1594,6 +1678,7 @@ def add_url_to_head_commit(commit, bug):
input = subject + "\n\n" + body
git.commit(file="-", amend=True, _input=input)
+
def add_url(bug, commits):
commit_map = {}
oldest_commit = None
@@ -1617,7 +1702,7 @@ def add_url(bug, commits):
branch_name = None
try:
# Detach HEAD from the branch; this gives a cleaner reflog for the branch
- print "Moving to starting point"
+ print("Moving to starting point")
git.checkout(oldest_commit.id + "^", q=True)
for commit in reversed(all_commits):
@@ -1626,14 +1711,14 @@ def add_url(bug, commits):
commit = commit_map[commit.id]
if commit.id in commit_map and commit_needs_url(commit, bug.id):
- print "Adding bug reference ", commit.id[0:7], commit.subject
+ print("Adding bug reference ", commit.id[0:7], commit.subject)
git.cherry_pick(commit.id)
add_url_to_head_commit(commit, bug)
else:
if commit.id in commit_map:
- print "Recommitting", commit.id[0:7], commit.subject, "(already has bug #)"
+ print("Recommitting", commit.id[0:7], commit.subject, "(already has bug #)")
else:
- print "Recommitting", commit.id[0:7], commit.subject
+ print("Recommitting", commit.id[0:7], commit.subject)
git.cherry_pick(commit.id)
# Get the commit ID; we update the commit with the new ID, so we in the case
@@ -1645,12 +1730,13 @@ def add_url(bug, commits):
branch_name, new_head)
git.symbolic_ref("HEAD", branch_name)
except:
- print "Cleaning up back to original state on error"
+ print("Cleaning up back to original state on error")
git.reset(orig_head, hard=True)
if branch_name is not None:
git.symbolic_ref("HEAD", branch_name)
raise
+
def do_add_url(bug_reference, commit_or_revision_range):
commits = get_commits(commit_or_revision_range)
@@ -1658,34 +1744,36 @@ def do_add_url(bug_reference, commit_or_revision_range):
check_add_url(commits, bug.id, is_add_url=True)
- print "Bug %d - %s" % (bug.id, bug.short_desc)
- print bug.get_url()
- print
+ print("Bug %d - %s" % (bug.id, bug.short_desc))
+ print(bug.get_url())
+ print()
found = False
for commit in commits:
if commit_needs_url(commit, bug.id):
- print commit.id[0:7], commit.subject
+ print(commit.id[0:7], commit.subject)
found = True
else:
- print "SKIPPING", commit.id[0:7], commit.subject
+ print("SKIPPING", commit.id[0:7], commit.subject)
if not found:
sys.exit(0)
- print
+ print()
if not prompt("Add bug URL to above commits?"):
- print "Aborting"
+ print("Aborting")
sys.exit(0)
- print
+ print()
add_url(bug, commits)
+
resolvemsg = '''When you have resolved this problem run "git bz apply --continue".
If you would prefer to skip this patch, instead run "git bz apply --skip".
To restore the original branch and stop patching run "git bz apply --abort".'''
+
def do_apply(*args):
- git_dir = git.rev_parse(git_dir=True)
+ git_dir = git.rev_parse(git_dir=True).decode()
resuming = global_options.resolved or global_options.skip or global_options.abort
do_not_add_url = 0
@@ -1706,7 +1794,7 @@ def do_apply(*args):
bug_ref = lines[0]
orig_head = lines[1]
need_amend = lines[2] == "True"
- patch_ids = map(int, lines[3:])
+ patch_ids = list(map(int, lines[3:]))
f.close()
except:
die("Not inside a 'git bz apply' operation")
@@ -1723,7 +1811,7 @@ def do_apply(*args):
try:
git.commit(amend=True, _interactive=True)
except CalledProcessError:
- print >>sys.stderr, "Warning: left dummy commit message"
+ print("Warning: left dummy commit message", file=sys.stderr)
else:
if resuming:
@@ -1737,7 +1825,7 @@ def do_apply(*args):
bug = Bug.load(BugHandle.parse_or_die(bug_ref),
attachmentdata=True)
if len(bug.patches) == 0:
- print("No patches on bug %d" % bug.id)
+ print(("No patches on bug %d" % bug.id))
return
patches = []
@@ -1758,38 +1846,38 @@ def do_apply(*args):
continue
dep_bug = Bug.load(BugHandle.parse_or_die(d_id),
- attachmentdata=False)
+ attachmentdata=False)
s = dep_bug.bug_status
- if s == "Needs Signoff" \
- or s == 'Signed Off' \
- or s == 'Failed QA' \
- or s == 'Passed QA' \
- or s == 'BLOCKED':
- print "\nBug %s Depends on bug %s (%s)" % ( bug.id, dep_bug.id, s)
+ if s == "Needs Signoff" \
+ or s == 'Signed Off' \
+ or s == 'Failed QA' \
+ or s == 'Passed QA' \
+ or s == 'BLOCKED':
+ print("\nBug %s Depends on bug %s (%s)" % (bug.id, dep_bug.id, s))
opt = prompt_multi("Follow? [(y)es, (n)o]", ["y", "n"])
if opt == "n":
continue
else:
try:
- applied = do_apply(d_id, { 'do_not_add_url': 1} )
+ applied = do_apply(d_id, {'do_not_add_url': 1})
bugs_applied.extend(applied)
except BaseException:
die("\nCannot apply cleanly patches from bug %s. Everything will be left dirty.\ngit bz apply --continue will not continue the process if patches from other bug reports need to be applied." % d_id)
orig_head = git.rev_parse("HEAD")
- print "\nBug %d - %s" % (bug.id, bug.short_desc)
- print
+ print("\nBug %d - %s" % (bug.id, bug.short_desc))
+ print()
for patch in bug.patches:
if patch.status == 'committed' or patch.status == 'rejected':
- print "%d (skipping, %s) - %s" % (patch.attach_id, patch.status, patch.description)
+ print("%d (skipping, %s) - %s" % (patch.attach_id, patch.status, patch.description))
else:
patches.append(patch)
for patch in patches:
- print "%d - %s" % (patch.attach_id, patch.description)
- print
+ print("%d - %s" % (patch.attach_id, patch.description))
+ print()
opt = prompt_multi("Apply? [(y)es, (n)o, (i)nteractive]", ["y", "n", "i"])
if opt == "n":
@@ -1814,7 +1902,7 @@ def do_apply(*args):
match = re.match('^(\d+)', line)
if match:
pid = int(match.group(1))
- if not patches_by_id.has_key(pid):
+ if pid not in patches_by_id:
die("Unknown attachment id " + pid)
patches.append(patches_by_id[pid])
@@ -1824,7 +1912,7 @@ def do_apply(*args):
for patch in patches:
if re.search(r'(^|\n)From ', patch.data) is None:
# Plain diff... rewrite it into something git-am will accept
- users = bug.server.get_xmlrpc_proxy().User.get({ 'names': [patch.attacher] })['users']
+ users = bug.server.get_xmlrpc_proxy().User.get({'names': [patch.attacher]})['users']
name = users[0]['real_name']
email = users[0]['email']
headers = """From xxx
@@ -1852,10 +1940,10 @@ FIXME: need commit message.
try:
if global_options.signoff:
process = git.am("-3", "-s", filename, resolvemsg=resolvemsg,
- _interactive=True)
+ _interactive=True)
else:
process = git.am("-3", filename, resolvemsg=resolvemsg,
- _interactive=True)
+ _interactive=True)
except CalledProcessError:
if os.access(git_dir + "/rebase-apply", os.F_OK):
# git-am saved its state for an abort or continue,
@@ -1875,7 +1963,7 @@ FIXME: need commit message.
try:
git.commit(amend=True, _interactive=True)
except CalledProcessError:
- print >>sys.stderr, "Warning: left dummy commit message"
+ print("Warning: left dummy commit message", file=sys.stderr)
if global_options.add_url and not do_not_add_url:
# Slightly hacky. We could add the URLs as we go by using
@@ -1888,6 +1976,7 @@ FIXME: need commit message.
bugs_applied.append(bug_ref)
return bug_ref
+
def strip_bug_url(bug, commit_body):
# Strip off the trailing bug URLs we add with -u; we do this before
# using commit body in as a comment; doing it by stripping right before
@@ -1896,6 +1985,7 @@ def strip_bug_url(bug, commit_body):
pattern = "\s*" + re.escape(bug.get_url()) + "\s*$"
return re.sub(pattern, "", commit_body)
+
def edit_attachment_comment(bug, initial_description, initial_body):
template = StringIO()
template.write("# Attachment to Bug %d - %s\n\n" % (bug.id, bug.short_desc))
@@ -1939,10 +2029,11 @@ def edit_attachment_comment(bug, initial_description, initial_body):
lines = edit_template(template.getvalue())
- obsoletes= []
- statuses= []
+ obsoletes = []
+ statuses = []
patch_complexities = []
depends = []
+
def filter_line(line):
m = re.match("^\s*Obsoletes\s*:\s*([\d]+)", line)
if m:
@@ -1962,7 +2053,7 @@ def edit_attachment_comment(bug, initial_description, initial_body):
return False
return True
- lines = filter(filter_line, lines)
+ lines = list(filter(filter_line, lines))
description, comment = split_subject_body(lines)
@@ -1971,6 +2062,7 @@ def edit_attachment_comment(bug, initial_description, initial_body):
return description, comment, obsoletes, statuses, patch_complexities, depends
+
def attach_commits(bug, commits, include_comments=True, edit_comments=False, status='none'):
# We want to attach the patches in chronological order
commits = list(commits)
@@ -1985,7 +2077,9 @@ def attach_commits(bug, commits, include_comments=True, edit_comments=False, sta
else:
body = None
if edit_comments and is_first is True:
- description, body, obsoletes, statuses, patch_complexities, depends = edit_attachment_comment(bug, commit.subject, body)
+ description, body, obsoletes, statuses, patch_complexities, depends = edit_attachment_comment(bug,
+ commit.subject,
+ body)
else:
description = commit.subject
obsoletes = []
@@ -2011,16 +2105,17 @@ def attach_commits(bug, commits, include_comments=True, edit_comments=False, sta
if len(statuses) > 0 or len(patch_complexities) > 0 or len(depends) > 0:
bug.update(**bug_changes)
if len(depends) > 0:
- print "Updated depends to '%s'" % bug_changes['dependson']
+ print("Updated depends to '%s'" % bug_changes['dependson'])
if len(patch_complexities) > 0:
- print "Updated patch complexity to '%s'" % bug_changes['cf_patch_complexity']
+ print("Updated patch complexity to '%s'" % bug_changes['cf_patch_complexity'])
if len(statuses) > 0:
- print "Updated bug status to '%s'" % bug_changes['bug_status']
+ print("Updated bug status to '%s'" % bug_changes['bug_status'])
bug.create_patch(description, body, filename, patch, obsoletes=obsoletes, status=status)
+
def do_attach(*args):
if len(args) == 1:
commit_or_revision_range = args[0]
@@ -2055,15 +2150,15 @@ def do_attach(*args):
# For the common case of one attachment don't prompt if we are going
# to give them a chance to edit and abort anyways.
if len(commits) > 1 or not global_options.edit:
- print "Bug %d - %s" % (bug.id, bug.short_desc)
- print
+ print("Bug %d - %s" % (bug.id, bug.short_desc))
+ print()
for commit in reversed(commits):
- print commit.id[0:7], commit.subject
+ print(commit.id[0:7], commit.subject)
- print
+ print()
if not prompt("Attach?"):
- print "Aborting"
+ print("Aborting")
sys.exit(0)
if global_options.add_url:
@@ -2073,11 +2168,12 @@ def do_attach(*args):
# bug.update(addselfcc='1')
attach_commits(bug, commits, edit_comments=global_options.edit)
+
# Sort the patches in the bug into categories based on a set of Git
# git commits that we're considering to be newly applied. Matching
# is done on exact git subject <=> patch description matches.
def filter_patches(bug, applied_commits):
- newly_applied_patches = dict() # maps to the commit object where it was applied
+ newly_applied_patches = dict() # maps to the commit object where it was applied
obsoleted_patches = set()
unapplied_patches = set()
@@ -2101,6 +2197,7 @@ def filter_patches(bug, applied_commits):
return newly_applied_patches, obsoleted_patches, unapplied_patches
+
def edit_bug(bug, applied_commits=None, fix_commits=None):
if applied_commits is not None:
newly_applied_patches, obsoleted_patches, unapplied_patches = filter_patches(bug, applied_commits)
@@ -2222,7 +2319,7 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
patch_complexities = []
depends = []
- lines = filter(filter_line, lines)
+ lines = list(filter(filter_line, lines))
comment = "".join(lines).strip()
bug_status = statuses[0] if len(statuses) > 0 else None
@@ -2230,8 +2327,9 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
patch_complexity = patch_complexities[0] if len(patch_complexities) > 0 else None
depends = ' '.join(depends) if len(depends) > 0 else None
- if depends is None and patch_complexity is None and bug_status is None and resolution is None and len(changed_attachments) == 0 and comment == "":
- print "No changes, not editing Bug %d - %s" % (bug.id, bug.short_desc)
+ if depends is None and patch_complexity is None and bug_status is None and resolution is None and len(
+ changed_attachments) == 0 and comment == "":
+ print("No changes, not editing Bug %d - %s" % (bug.id, bug.short_desc))
return False
if fix_commits is not None:
@@ -2271,7 +2369,7 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
# We can add the comment when we submit the attachment change.
# Bugzilla will add a helpful notation ad we'll only send out
# one set of email
- pass # We'll put the comment with the attachment
+ pass # We'll put the comment with the attachment
else:
bug_changes['comment'] = comment
@@ -2282,7 +2380,7 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
bug_changes['addselfcc'] = '1'
bug.update(**bug_changes)
- for (attachment_id, status) in changed_attachments.iteritems():
+ for (attachment_id, status) in changed_attachments.items():
patch = None
if patches_have_status:
if legal_statuses:
@@ -2300,7 +2398,7 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
if not patch:
die("%d is not a valid attachment ID for Bug %d" % (attachment_id, bug.id))
attachment_changes = {}
- if comment != "" and not 'comment' in bug_changes: # See above
+ if comment != "" and not 'comment' in bug_changes: # See above
attachment_changes['comment'] = comment
if status == 'obsolete':
attachment_changes['isobsolete'] = "1"
@@ -2308,23 +2406,24 @@ def edit_bug(bug, applied_commits=None, fix_commits=None):
attachment_changes['status'] = status
bug.update_patch(patch, **attachment_changes)
if status == 'obsolete':
- print "Marked attachment as obsolete: %s - %s " % (patch.attach_id, patch.description)
+ print("Marked attachment as obsolete: %s - %s " % (patch.attach_id, patch.description))
else:
- print "Changed status of attachment to %s: %s - %s" % (status, patch.attach_id, patch.description)
+ print("Changed status of attachment to %s: %s - %s" % (status, patch.attach_id, patch.description))
if fix_commits is not None:
attach_commits(bug, fix_commits, status='committed')
if resolution is not None:
- print "Resolved as %s bug %d - %s" % (resolution, bug.id, bug.short_desc)
+ print("Resolved as %s bug %d - %s" % (resolution, bug.id, bug.short_desc))
elif len(changed_attachments) > 0:
- print "Updated bug %d - %s" % (bug.id, bug.short_desc)
+ print("Updated bug %d - %s" % (bug.id, bug.short_desc))
else:
- print "Added comment to bug %d - %s" % (bug.id, bug.short_desc)
- print bug.get_url()
+ print("Added comment to bug %d - %s" % (bug.id, bug.short_desc))
+ print(bug.get_url())
return True
+
LOG_BUG_REFERENCE = re.compile(r"""
(\b[Ss]ee\s+(?:[^\s:/]+\s+){0,2})?
(?:(https?://[^/]+/show_bug.cgi\?id=[^&\s]+)
@@ -2332,6 +2431,7 @@ LOG_BUG_REFERENCE = re.compile(r"""
[Bb]ug\s+\#?(\d+))
""", re.VERBOSE | re.DOTALL)
+
def extract_bugs_from_string(str):
refs = []
for m in LOG_BUG_REFERENCE.finditer(str):
@@ -2343,7 +2443,7 @@ def extract_bugs_from_string(str):
# about some peripherally related bug. So, if the word see
# occurs 0 to 2 words before the bug reference, we ignore it.
if m.group(1) is not None:
- print "Skipping cross-reference '%s'" % m.group(0)
+ print("Skipping cross-reference '%s'" % m.group(0))
continue
if m.group(2) is not None:
bug_reference = m.group(2)
@@ -2352,8 +2452,9 @@ def extract_bugs_from_string(str):
try:
yield BugHandle.parse(bug_reference)
- except BugParseError, e:
- print "WARNING: cannot resolve bug reference '%s'" % bug_reference
+ except BugParseError as e:
+ print("WARNING: cannot resolve bug reference '%s'" % bug_reference)
+
def extract_bugs_from_commit(commit):
for handle in extract_bugs_from_string(commit.subject):
@@ -2361,6 +2462,7 @@ def extract_bugs_from_commit(commit):
for handle in extract_bugs_from_string(get_body(commit)):
yield handle
+
# Yields bug, [<list of commits where it is referenced>] for each bug
# referenced in the list of commits. The order of bugs is the same as the
# order of their first reference in the list of commits
@@ -2378,6 +2480,7 @@ def extract_and_collate_bugs(commits):
for bug in bugs:
yield bug, bug_to_commits[bug]
+
def do_edit(bug_reference_or_revision_range):
try:
handle = BugHandle.parse(bug_reference_or_revision_range)
@@ -2387,7 +2490,7 @@ def do_edit(bug_reference_or_revision_range):
die("--fix requires commits to be specified")
bug = Bug.load(handle)
edit_bug(bug)
- except BugParseError, e:
+ except BugParseError as e:
try:
commits = get_commits(bug_reference_or_revision_range)
except CalledProcessError:
@@ -2407,6 +2510,7 @@ def do_edit(bug_reference_or_revision_range):
else:
edit_bug(bug)
+
PRODUCT_COMPONENT_HELP = """
Use:
@@ -2416,6 +2520,7 @@ Use:
to configure a default product and/or component for this module."""
+
def do_file(*args):
if len(args) == 1:
product_component, commit_or_revision_range = None, args[0]
@@ -2453,13 +2558,13 @@ def do_file(*args):
bug_references = [c for c in extract_and_collate_bugs(commits)]
if len(bug_references) > 0:
- print ("Found existing bug reference%s in commit message%s:" %
+ print(("Found existing bug reference%s in commit message%s:" %
("" if len(bug_references) == 1 else "s",
- "" if len(commits) == 1 else "s"))
+ "" if len(commits) == 1 else "s")))
for reference, _ in bug_references:
- print " ", reference.get_url()
+ print(" ", reference.get_url())
if not prompt("File anyway?"):
- print "Aborting"
+ print("Aborting")
sys.exit(0)
if global_options.add_url:
@@ -2476,7 +2581,7 @@ def do_file(*args):
# Product: %(product)s
# Component: %(component)s
# Patches to be attached:
-""" % { 'product': product, 'component': component })
+""" % {'product': product, 'component': component})
for commit in reversed(commits):
template.write("# " + commit.id[0:7] + " " + commit.subject + "\n")
@@ -2490,7 +2595,7 @@ def do_file(*args):
# If we have only one patch and no other description for the bug was
# specified, use the body of the commit as the the description for
# the bug rather than the descriptionfor the attachment
- include_comments=True
+ include_comments = True
if len(commits) == 1:
if description == "":
description = get_body(commits[0])
@@ -2503,6 +2608,7 @@ def do_file(*args):
attach_commits(bug, commits, include_comments=include_comments)
+
def run_push(*args, **kwargs):
# Predicting what 'git pushes' pushes based on the command line
# would be extraordinarily complex, but the interactive output goes
@@ -2515,13 +2621,13 @@ def run_push(*args, **kwargs):
if global_options.force:
options['force'] = True
try:
- options['_return_stderr']=True
+ options['_return_stderr'] = True
out, err = git.push(*args, **options)
except CalledProcessError:
return
if not dry:
# Echo the output so the user gets feedback about what happened
- print >>sys.stderr, err
+ print(err, file=sys.stderr)
commits = []
for line in err.strip().split("\n"):
@@ -2549,6 +2655,7 @@ def run_push(*args, **kwargs):
return unique_commits
+
def do_push(*args):
if global_options.fix:
handle = BugHandle.parse_or_die(global_options.fix)
@@ -2558,7 +2665,7 @@ def do_push(*args):
# We need to add the URLs to the commits before we push
# We need to push in order to find out what commits we are pushing
# So, we push --dry first
- options = { 'dry' : True }
+ options = {'dry': True}
commits = run_push(*args, **options)
if edit_bug(bug, fix_commits=commits):
run_push(*args)
@@ -2568,6 +2675,7 @@ def do_push(*args):
bug = Bug.load(handle)
edit_bug(bug, commits)
+
def do_components(*args):
tracker = get_tracker()
host = resolve_host_alias(tracker)
@@ -2596,12 +2704,13 @@ def do_components(*args):
response = server.get_xmlrpc_proxy().Bug.legal_values({'product_id': product_id, 'field': 'component'})
components = response['values']
for component in components:
- print component
- except xmlrpclib.Fault, e:
+ print(component)
+ except xmlrpc.client.Fault as e:
die(e.faultString)
- except xmlrpclib.ProtocolError, e:
+ except xmlrpc.client.ProtocolError as e:
die("Unable to retrieve components: %s" % e.errmsg)
+
################################################################################
init_git_config()
@@ -2617,28 +2726,34 @@ parser = optparse.OptionParser()
parser.add_option("-b", "--bugzilla", metavar="<host or alias>",
help="bug tracker to use")
+
def add_add_url_options():
parser.add_option("-u", "--add-url", action="store_true",
help="rewrite commits to add the bug URL [default]")
parser.add_option("-n", "--no-add-url", action="store_false", dest="add_url",
help="don't rewrite commits to add the bug URL")
+
def add_edit_option():
parser.add_option("-e", "--edit", action="store_true",
help="allow editing the bugzilla comment")
+
def add_mail_option():
parser.add_option("-m", "--mail", action="store_true",
help="send email")
+
def add_fix_option():
parser.add_option("", "--fix", metavar="<bug reference>",
help="attach commits and close bug")
+
def add_signoff_option():
parser.add_option("-s", "--signoff", action="store_true",
help="sign off when applying")
+
if command == 'add-url':
parser.set_usage("git bz add-url [options] <bug reference> (<commit> | <revision range>)");
min_args = max_args = 2
@@ -2689,9 +2804,9 @@ elif command == 'push':
parser.add_option("-f", "--force", action="store_true",
help="allow non-fast-forward commits")
min_args = 0
- max_args = 1000 # no max
+ max_args = 1000 # no max
else:
- print >>sys.stderr, "Usage: git bz [add-url|apply|attach|components|edit|file|push] [options]"
+ print("Usage: git bz [add-url|apply|attach|components|edit|file|push] [options]", file=sys.stderr)
sys.exit(1)
global_options, args = parser.parse_args()