# for the URL to access. If that is unspecified, it will fallback to
# the hardcoded default value specified here.
DEFAULT_URL = "http://patchwork/xmlrpc/"
-CONFIG_FILES = [os.path.expanduser('~/.pwclientrc')]
+CONFIG_FILE = os.path.expanduser('~/.pwclientrc')
class Filter:
"""Filter for selecting patches."""
"""Return human-readable description of the filter."""
return str(self.d)
-class BasicHTTPAuthTransport(xmlrpclib.Transport):
+class BasicHTTPAuthTransport(xmlrpclib.SafeTransport):
- def __init__(self, username = None, password = None):
+ def __init__(self, username = None, password = None, use_https = False):
self.username = username
self.password = password
- xmlrpclib.Transport.__init__(self)
+ self.use_https = use_https
+ xmlrpclib.SafeTransport.__init__(self)
def authenticated(self):
return self.username != None and self.password != None
auth = 'Basic ' + base64.encodestring(credentials).strip()
connection.putheader('Authorization', auth)
+ def make_connection(self, host):
+ if self.use_https:
+ fn = xmlrpclib.SafeTransport.make_connection
+ else:
+ fn = xmlrpclib.Transport.make_connection
+ return fn(self, host)
+
def usage():
sys.stderr.write("Usage: %s <action> [options]\n\n" % \
(os.path.basename(sys.argv[0])))
sys.stderr.write("Where <action> is one of:\n")
sys.stderr.write(
""" apply <ID> : Apply a patch (in the current dir, using -p1)
+ git-am <ID> : Apply a patch to current git branch using "git am"
get <ID> : Download a patch and save it locally
+ info <ID> : Display patchwork info about a given patch ID
projects : List all projects
states : Show list of potential patch states
list [str] : List patches, using the optional filters specified
-p <project> : Filter by project name (see 'projects' for list)
-w <who> : Filter by submitter (name, e-mail substring search)
-d <who> : Filter by delegate (name, e-mail substring search)
- -n <max #> : Restrict number of results\n""")
+ -n <max #> : Restrict number of results
+ -m <messageid>: Filter by Message-Id\n""")
sys.stderr.write("""\nActions that take an ID argument can also be \
invoked with:
-h <hash> : Lookup by patch hash\n""")
def list_patches(patches):
"""Dump a list of patches to stdout."""
- print("%-5s %-12s %s" % ("ID", "State", "Name"))
- print("%-5s %-12s %s" % ("--", "-----", "----"))
+ print("%-7s %-12s %s" % ("ID", "State", "Name"))
+ print("%-7s %-12s %s" % ("--", "-----", "----"))
for patch in patches:
- print("%-5d %-12s %s" % (patch['id'], patch['state'], patch['name']))
+ print("%-7d %-12s %s" % (patch['id'], patch['state'], patch['name']))
def action_list(rpc, filter, submitter_str, delegate_str):
filter.resolve_ids(rpc)
if submitter_str != "":
ids = person_ids_by_name(rpc, submitter_str)
if len(ids) == 0:
- sys.stderr.write("Note: Nobody found matching *%s*\n", \
+ sys.stderr.write("Note: Nobody found matching *%s*\n" % \
submitter_str)
else:
for id in ids:
person = rpc.person_get(id)
print "Patches submitted by %s <%s>:" % \
- (person['name'], person['email'])
+ (unicode(person['name']).encode("utf-8"), \
+ unicode(person['email']).encode("utf-8"))
f = filter
f.add("submitter_id", id)
patches = rpc.patch_list(f.d)
if delegate_str != "":
ids = person_ids_by_name(rpc, delegate_str)
if len(ids) == 0:
- sys.stderr.write("Note: Nobody found matching *%s*\n", \
+ sys.stderr.write("Note: Nobody found matching *%s*\n" % \
delegate_str)
else:
for id in ids:
for state in states:
print("%-5d %s" % (state['id'], state['name']))
+def action_info(rpc, patch_id):
+ patch = rpc.patch_get(patch_id)
+ s = "Information for patch id %d" % (patch_id)
+ print(s)
+ print('-' * len(s))
+ for key, value in sorted(patch.iteritems()):
+ print("- %- 14s: %s" % (key, unicode(value).encode("utf-8")))
+
def action_get(rpc, patch_id):
patch = rpc.patch_get(patch_id)
s = rpc.patch_get_mbox(patch_id)
sys.exit(1)
try:
- f.write(s)
+ f.write(unicode(s).encode("utf-8"))
f.close()
print "Saved patch to %s" % fname
except:
sys.stderr.write("Failed to write to %s\n" % fname)
sys.exit(1)
-def action_apply(rpc, patch_id):
+def action_apply(rpc, patch_id, apply_cmd=None):
patch = rpc.patch_get(patch_id)
if patch == {}:
sys.stderr.write("Error getting information on patch ID %d\n" % \
patch_id)
sys.exit(1)
- print "Applying patch #%d to current directory" % patch_id
+
+ if apply_cmd is None:
+ print "Applying patch #%d to current directory" % patch_id
+ apply_cmd = ['patch', '-p1']
+ else:
+ print "Applying patch #%d using %s" % (
+ patch_id, repr(' '.join(apply_cmd)))
+
print "Description: %s" % patch['name']
s = rpc.patch_get_mbox(patch_id)
if len(s) > 0:
- proc = subprocess.Popen(['patch', '-p1'], stdin = subprocess.PIPE)
- proc.communicate(s)
+ proc = subprocess.Popen(apply_cmd, stdin = subprocess.PIPE)
+ proc.communicate(unicode(s).encode('utf-8'))
else:
sys.stderr.write("Error: No patch content found\n")
sys.exit(1)
if not success:
sys.stderr.write("Patch not updated\n")
-def patch_id_from_hash(rpc, hash):
- patch = rpc.patch_get_by_hash(hash)
+def patch_id_from_hash(rpc, project, hash):
+ try:
+ patch = rpc.patch_get_by_project_hash(project, hash)
+ except xmlrpclib.Fault:
+ # the server may not have the newer patch_get_by_project_hash function,
+ # so fall back to hash-only.
+ patch = rpc.patch_get_by_hash(hash)
+
if patch == {}:
return None
def main():
try:
- opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:')
+ opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:m:')
except getopt.GetoptError, err:
print str(err)
usage()
commit_str = ""
state_str = ""
hash_str = ""
+ msgid_str = ""
url = DEFAULT_URL
config = ConfigParser.ConfigParser()
- config.read(CONFIG_FILES)
+ config.read([CONFIG_FILE])
# grab settings from config files
if config.has_option('base', 'url'):
commit_str = value
elif name == '-h':
hash_str = value
+ elif name == '-m':
+ msgid_str = value
elif name == '-n':
try:
filt.add("max_count", int(value))
if config.has_option('auth', 'username') and \
config.has_option('auth', 'password'):
+ use_https = url.startswith('https')
+
transport = BasicHTTPAuthTransport( \
config.get('auth', 'username'),
- config.get('auth', 'password'))
+ config.get('auth', 'password'),
+ use_https)
else:
sys.stderr.write(("The %s action requires authentication, "
if state_str:
filt.add("state", state_str)
+ if msgid_str:
+ filt.add("msgid", msgid_str)
+
try:
rpc = xmlrpclib.Server(url, transport = transport)
except:
patch_id = None
if hash_str:
- patch_id = patch_id_from_hash(rpc, hash_str)
+ patch_id = patch_id_from_hash(rpc, project_str, hash_str)
if patch_id is None:
- sys.stderr.write("No patch has the hash provided")
+ sys.stderr.write("No patch has the hash provided\n")
sys.exit(1)
s = rpc.patch_get_mbox(patch_id)
if len(s) > 0:
- print s
+ print unicode(s).encode("utf-8")
- elif action == 'get' or action == 'save':
+ elif action in ('get', 'save', 'info'):
try:
patch_id = patch_id or int(args[0])
except:
sys.stderr.write("Invalid patch ID given\n")
sys.exit(1)
- action_get(rpc, patch_id)
+ if action == 'info':
+ action_info(rpc, patch_id)
+ else:
+ action_get(rpc, patch_id)
elif action == 'apply':
try:
action_apply(rpc, patch_id)
+ elif action == 'git-am':
+ try:
+ patch_id = patch_id or int(args[0])
+ except:
+ sys.stderr.write("Invalid patch ID given\n")
+ sys.exit(1)
+
+ action_apply(rpc, patch_id, ['git', 'am'])
+
elif action == 'update':
try:
patch_id = patch_id or int(args[0])