Em Thu, 20 Dec 2012 13:06:07 -0800 Doug Anderson <[email protected]> escreveu:
> Add a new filter option '-r' that attempts to list all patches in a > series. Since there's no built-in way in patman to do this, we use > some heuristics to try to find the series. Instead of adding it at the client level, IMHO, it would be a way better to add it at the patchwork server, allowing to show the patch group as such and letting tag the entire patch group with the same tag (as a v2 of a patch series superseeds a v1 of the same series). Regards, Mauro > > Signed-off-by: Doug Anderson <[email protected]> > > --- > Changes in v2: > - Handle more tag formats; use tags besides just version/num parts > (like RFC, REPOST, etc) to identify a series > > apps/patchwork/bin/pwclient | 151 +++++++++++++++++++++++++++++++++++++++++- > 1 files changed, 147 insertions(+), 4 deletions(-) > > diff --git a/apps/patchwork/bin/pwclient b/apps/patchwork/bin/pwclient > index 9588615..77d78c7 100755 > --- a/apps/patchwork/bin/pwclient > +++ b/apps/patchwork/bin/pwclient > @@ -23,11 +23,14 @@ import os > import sys > import xmlrpclib > import getopt > +import re > import string > +import time > import tempfile > import subprocess > import base64 > import ConfigParser > +import collections > > # Default Patchwork remote XML-RPC server URL > # This script will check the PW_XMLRPC_URL environment variable > @@ -79,6 +82,81 @@ class Filter: > """Return human-readable description of the filter.""" > return str(self.d) > > +class Patch(object): > + """Nicer representation of a patch from the server.""" > + > + def __init__(self, patch_dict): > + """Patch constructor. > + > + @patch_dict: The dictionary version of the patch. > + """ > + # Make it easy to compare times of patches by getting an int. > + self.time = time.mktime(time.strptime(patch_dict["date"], > + "%Y-%m-%d %H:%M:%S")) > + > + self.version, self.part_num, self.num_parts, self.series_tags = \ > + self._parse_patch_name(patch_dict["name"]) > + > + # Add a few things to make it easier... > + self.id = patch_dict["id"] > + self.project_id = patch_dict["project_id"] > + self.name = patch_dict["name"] > + self.submitter_id = patch_dict["submitter_id"] > + > + # Keep the dict in case we need anything else... > + self.dict = patch_dict > + > + @staticmethod > + def _parse_patch_name(name): > + """Parse tags out of a patch name. > + > + > + @name: The patch name. > + @return: version: integer version of the patch > + @return: part_num: integer part number of the patch > + @return: num_parts: integer number of parts in the patch > + @return: series_tags: A tuple of tags that should be shared by all > + patches in this series. Should be treated as opaque other > + than comparing equality with other patches. > + """ > + version = 1 > + part_num = 1 > + num_parts = 1 > + series_tags = [] > + > + # Pull out tags between []; bail if tags aren't found. > + mo = re.match(r"\[([^\]]*)\]", name) > + if mo: > + tags = mo.group(1).split(',') > + > + # Work on one tag at a time > + for tag in tags: > + mo = re.match(r"(\d*)/(\d*)", tag) > + if mo: > + part_num = int(mo.group(1)) > + num_parts = int(mo.group(2)) > + continue > + > + mo = re.match(r"[vV](\d*)", tag) > + if mo: > + version = int(mo.group(1)) > + > + series_tags.append(tag) > + > + # Add num_parts to the series tags > + series_tags.append("%d parts" % num_parts) > + > + # Turn series_tags into a tuple so it's hashable > + series_tags = tuple(series_tags) > + > + return (version, part_num, num_parts, series_tags) > + > + def __str__(self): > + return str(self.dict) > + > + def __repr__(self): > + return repr(self.dict) > + > class BasicHTTPAuthTransport(xmlrpclib.SafeTransport): > > def __init__(self, username = None, password = None, use_https = False): > @@ -128,7 +206,8 @@ def usage(): > -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 > - -m <messageid>: Filter by Message-Id\n""") > + -m <messageid>: Filter by Message-Id > + -r <ID> : Filter by patches in the same series as <ID>\n""") > sys.stderr.write("""\nActions that take an ID argument can also be \ > invoked with: > -h <hash> : Lookup by patch hash\n""") > @@ -162,6 +241,56 @@ def person_ids_by_name(rpc, name): > people = rpc.person_list(name, 0) > return map(lambda x: x['id'], people) > > +def patch_id_to_series(rpc, patch_id): > + """Take a patch ID and return a list of patches in the same series. > + > + This function uses the following heuristics to find patches in a series: > + - It searches for all patches with the same submitter that the same > version > + number and same number of parts. > + - It allows patches to span multiple projects (though they must all be on > + the same patchwork server), though it prefers patches that are part of > + the same project. This handles cases where some parts in a series > might > + have only been sent to a topic project (like "linux-mmc"). > + - For each part number it finds the matching patch that has a date value > + closest to the original patch. > + > + It would be nice to use "Message-ID" and "In-Reply-To", but that's not > + exported to the xmlrpc interface as far as I can tell. :( > + > + @patch_id: The patch ID that's part of the series. > + @return: A list of patches in the series. > + """ > + # Find this patch > + patch = Patch(rpc.patch_get(patch_id)) > + > + # Get the all patches by the submitter, ignoring project. > + filter = Filter() > + filter.add("submitter_id", patch.submitter_id) > + all_patches = [Patch(p) for p in rpc.patch_list(filter.d)] > + > + # Whittle down--only those with matching series_tags. > + all_patches = [p for p in all_patches if p.series_tags == > patch.series_tags] > + > + # Organize by part_num. > + by_part_num = collections.defaultdict(list) > + for p in all_patches: > + by_part_num[p.part_num].append(p) > + > + # Find the part that's closest in time to ours for each part num. > + final_list = [] > + for part_num, patch_list in sorted(by_part_num.iteritems()): > + # Create a list of tuples to make sorting easier. We want to find > + # the patch that has the closet time. If there's a tie then we want > + # the patch that has the same project ID... > + patch_list = [(abs(p.time - patch.time), > + abs(p.project_id - patch.project_id), > + p) for p in patch_list] > + > + best = sorted(patch_list)[0][-1] > + final_list.append(best) > + > + return final_list > + > def list_patches(patches): > """Dump a list of patches to stdout.""" > print("%-5s %-12s %s" % ("ID", "State", "Name")) > @@ -169,9 +298,20 @@ def list_patches(patches): > for patch in patches: > print("%-5d %-12s %s" % (patch['id'], patch['state'], patch['name'])) > > -def action_list(rpc, filter, submitter_str, delegate_str): > +def action_list(rpc, filter, submitter_str, delegate_str, series_str): > filter.resolve_ids(rpc) > > + if series_str != "": > + try: > + patch_id = int(series_str) > + except: > + sys.stderr.write("Invalid patch ID given\n") > + sys.exit(1) > + > + patches = patch_id_to_series(rpc, patch_id) > + list_patches([patch.dict for patch in patches]) > + return > + > if submitter_str != "": > ids = person_ids_by_name(rpc, submitter_str) > if len(ids) == 0: > @@ -320,7 +460,7 @@ auth_actions = ['update'] > > def main(): > try: > - opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:m:') > + opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:m:r:') > except getopt.GetoptError, err: > print str(err) > usage() > @@ -337,6 +477,7 @@ def main(): > project_str = "" > commit_str = "" > state_str = "" > + series_str = "" > hash_str = "" > msgid_str = "" > url = DEFAULT_URL > @@ -354,6 +495,8 @@ def main(): > for name, value in opts: > if name == '-s': > state_str = value > + elif name == '-r': > + series_str = value > elif name == '-p': > project_str = value > elif name == '-w': > @@ -424,7 +567,7 @@ def main(): > if action == 'list' or action == 'search': > if len(args) > 0: > filt.add("name__icontains", args[0]) > - action_list(rpc, filt, submitter_str, delegate_str) > + action_list(rpc, filt, submitter_str, delegate_str, series_str) > > elif action.startswith('project'): > action_projects(rpc) -- Cheers, Mauro _______________________________________________ Patchwork mailing list [email protected] https://lists.ozlabs.org/listinfo/patchwork
