Created
January 25, 2017 03:39
-
-
Save jberry-suse/c0106957fac0b64453964ee96de786df to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/osc-staging.py b/osc-staging.py | |
index 980f236..4f7dbdd 100644 | |
--- a/osc-staging.py | |
+++ b/osc-staging.py | |
@@ -100,8 +100,8 @@ def _full_project_name(self, project): | |
@cmdln.option('--wipe-cache', dest='wipe_cache', action='store_true', default=False, | |
help='wipe GET request cache before executing') | |
@cmdln.option('-m', '--message', help='message used by ignore command') | |
-@cmdln.option('--filter-by', help='xpath by which to filter requests') | |
-@cmdln.option('--group-by', help='xpath by which to group requests') | |
+@cmdln.option('--filter-by', action='append', help='xpath by which to filter requests') | |
+@cmdln.option('--group-by', action='append', help='xpath by which to group requests') | |
@cmdln.option('-i', '--interactive', action='store_true', help='interactively modify selection proposal') | |
def do_staging(self, subcmd, opts, *args): | |
"""${cmd_name}: Commands to work with staging projects | |
@@ -133,6 +133,13 @@ def do_staging(self, subcmd, opts, *args): | |
"list" will pick the requests not in rings | |
"select" will add requests to the project | |
+ Stagings are expected to be either in short-hand or the full project | |
+ name. For example letter or named stagings can be specified simply as | |
+ A, B, Gcc6, etc, while adi stagings can be specified as adi:1, adi:2, | |
+ etc. Currently, adi stagings are not supported in proposal mode. | |
+ | |
+ Requests may either be the target package or the request ID. | |
+ | |
When using --filter-by or --group-by the xpath will be applied to the | |
request node as returned by OBS. Several values will supplement the | |
normal request node. | |
@@ -146,13 +153,16 @@ def do_staging(self, subcmd, opts, *args): | |
--filter-by './action/target[starts-with(@package, "yast-"]' | |
--filter-by './action/source/[@devel_project="YaST:Head"]' | |
--filter-by './action/target[@ring="1-MinimalX"]' | |
+ --filter-by '@id!="1234567"' | |
--group-by='./action/source/@devel_project' | |
--group-by='./action/target/@ring' | |
- Note that when using proposal mode multiple letter stagings to consider | |
- may be provided in addition to a list of request IDs by which to filter. | |
- A more complex example: | |
+ Multiple filter-by or group-by options may be used at the same time. | |
+ | |
+ Note that when using proposal mode, multiple stagings to consider may be | |
+ provided in addition to a list of requests by which to filter. A more | |
+ complex example: | |
select --group-by='./action/source/@devel_project' A B C 123 456 789 | |
@@ -182,8 +192,8 @@ def do_staging(self, subcmd, opts, *args): | |
osc staging ignore [-m MESSAGE] REQUEST... | |
osc staging unignore REQUEST...|all | |
osc staging list [--supersede] | |
- osc staging select [--no-freeze] [--move [--from PROJECT] LETTER REQUEST... | |
- osc staging select [--no-freeze] [[--interactive] [--filter-by] [--group-by]] [LETTER...] [REQUEST...] | |
+ osc staging select [--no-freeze] [--move [--from PROJECT] STAGING REQUEST... | |
+ osc staging select [--no-freeze] [[--interactive] [--filter-by...] [--group-by...]] [STAGING...] [REQUEST...] | |
osc staging unselect REQUEST... | |
osc staging repair REQUEST... | |
""" | |
@@ -280,30 +290,45 @@ def do_staging(self, subcmd, opts, *args): | |
elif cmd == 'unselect': | |
UnselectCommand(api).perform(args[1:]) | |
elif cmd == 'select': | |
+ # Include list of all stagings in short-hand and by full name. | |
+ existing_stagings = api.get_staging_projects_short(None) | |
+ existing_stagings += [p for p in api.get_staging_projects() if not p.endswith(':DVD')] | |
stagings = [] | |
- request_ids = [] | |
+ requests = [] | |
for arg in args[1:]: | |
- if not arg.isdigit(): | |
- stagings.append(arg) | |
- else: | |
- request_ids.append(arg) | |
- | |
- if len(stagings) != 1 or len(request_ids) == 0 or opts.filter_by or opts.group_by: | |
+ # Since requests may be given by either request ID or package | |
+ # name and stagings may include multi-letter special stagings | |
+ # there is no easy way to distinguish between stagings and | |
+ # requests in arguments. Therefore, check if argument is in the | |
+ # list of short-hand and full project name stagings, otherwise | |
+ # consider it a request. This also allows for special stagings | |
+ # with the same name as package, but the staging will be assumed | |
+ # first time around. The current practice seems to be to start a | |
+ # special staging with a capital letter which makes them unique. | |
+ # lastly adi stagings are consistently prefix with adi: which | |
+ # also makes it consistent to distinguish them from request IDs. | |
+ if arg in existing_stagings and arg not in stagings: | |
+ stagings.append(api.extract_staging_short(arg)) | |
+ elif arg not in requests: | |
+ requests.append(arg) | |
+ | |
+ if len(stagings) != 1 or len(requests) == 0 or opts.filter_by or opts.group_by: | |
if opts.move or opts.from_: | |
print('--move and --from must be used with explicit staging and request list') | |
return | |
- requests = api.get_open_requests() | |
- splitter = RequestSplitter(api, requests, in_ring=True) | |
- if len(request_ids) > 0: | |
- splitter.filter_add_ids(request_ids) | |
- else: | |
+ splitter = RequestSplitter(api, api.get_open_requests(), in_ring=True) | |
+ if len(requests) > 0: | |
+ splitter.filter_add_requests(requests) | |
+ if len(splitter.filters) == 0: | |
splitter.filter_add('./action[not(@type="add_role" or @type="change_devel")]') | |
splitter.filter_add('@ignored="false"') | |
if opts.filter_by: | |
- splitter.filter_add(opts.filter_by) | |
+ for filter_by in opts.filter_by: | |
+ splitter.filter_add(filter_by) | |
if opts.group_by: | |
- splitter.group_by(opts.group_by) | |
+ for group_by in opts.group_by: | |
+ splitter.group_by(group_by) | |
splitter.split() | |
result = splitter.propose_assignment(stagings) | |
@@ -317,14 +342,14 @@ def do_staging(self, subcmd, opts, *args): | |
if opts.interactive: | |
with tempfile.NamedTemporaryFile(suffix='.yml') as temp: | |
- temp.write('# staging proposal\n') | |
- temp.write('# make modifications or comment/remove lines\n\n') | |
- temp.write(yaml.dump(splitter.proposal, default_flow_style=False) + '\n\n') | |
- before = ', '.join(sorted(splitter._stagings_available.keys())) | |
- after = ', '.join(sorted(splitter.stagings_available.keys())) | |
- temp.write('# stagings available\n') | |
- temp.write('# - before: {}\n'.format(before)) | |
- temp.write('# - after: {}\n'.format(after)) | |
+ temp.write(yaml.safe_dump(splitter.proposal, default_flow_style=False) + '\n\n') | |
+ temp.write('# move requests between stagings or comment/remove them\n') | |
+ temp.write('# change the target staging for a group\n') | |
+ temp.write('# stagings\n') | |
+ temp.write('# - considered: {}\n' | |
+ .format(', '.join(sorted(splitter.stagings_considerable.keys())))) | |
+ temp.write('# - available: {}\n' | |
+ .format(', '.join(sorted(splitter.stagings_available.keys())))) | |
temp.flush() | |
editor = os.getenv('EDITOR') | |
@@ -334,10 +359,11 @@ def do_staging(self, subcmd, opts, *args): | |
proposal = yaml.safe_load(open(temp.name).read()) | |
- print(yaml.dump(proposal, default_flow_style=False)) | |
+ print(yaml.safe_dump(proposal, default_flow_style=False)) | |
- print('Accept proposal? [y/n]: ', end='') | |
- if raw_input().lower() != 'y': | |
+ print('Accept proposal? [y/n] (y): ', end='') | |
+ response = raw_input().lower() | |
+ if response != '' and response != 'y': | |
print('Quit') | |
return | |
@@ -351,17 +377,17 @@ def do_staging(self, subcmd, opts, *args): | |
# SelectCommand expects strings. | |
request_ids = map(str, g['requests'].keys()) | |
- target_project = api.prj_from_letter(g['staging']) | |
+ target_project = api.prj_from_short(g['staging']) | |
SelectCommand(api, target_project) \ | |
.perform(request_ids, opts.move, opts.from_, opts.no_freeze) | |
else: | |
- target_project = api.prj_from_letter(stagings[0]) | |
+ target_project = api.prj_from_short(stagings[0]) | |
if opts.add: | |
api.mark_additional_packages(target_project, [opts.add]) | |
else: | |
SelectCommand(api, target_project) \ | |
- .perform(args[2:], opts.move, opts.from_, opts.no_freeze) | |
+ .perform(requests, opts.move, opts.from_, opts.no_freeze) | |
elif cmd == 'cleanup_rings': | |
CleanupRings(api).perform() | |
elif cmd == 'ignore': | |
diff --git a/osclib/list_command.py b/osclib/list_command.py | |
index aabb4b0..78a5d7d 100644 | |
--- a/osclib/list_command.py | |
+++ b/osclib/list_command.py | |
@@ -29,7 +29,6 @@ class ListCommand: | |
# First dispatch all possible requests | |
self.api.dispatch_open_requests() | |
- # Print out the left overs | |
requests = self.api.get_open_requests() | |
requests_ignored = self.api.get_ignored_requests() | |
diff --git a/osclib/request_splitter.py b/osclib/request_splitter.py | |
index 14e4b9a..ec644ad 100644 | |
--- a/osclib/request_splitter.py | |
+++ b/osclib/request_splitter.py | |
@@ -23,9 +23,11 @@ class RequestSplitter(object): | |
def filter_add(self, xpath): | |
self.filters.append(ET.XPath(xpath)) | |
- def filter_add_ids(self, request_ids): | |
- request_ids = ' ' + ' '.join(request_ids) + ' ' | |
- self.filter_add('contains("{}", concat(" ", @id, " "))'.format(request_ids)) | |
+ def filter_add_requests(self, requests): | |
+ requests = ' ' + ' '.join(requests) + ' ' | |
+ self.filter_add('contains("{requests}", concat(" ", @id, " ")) or ' | |
+ 'contains("{requests}", concat(" ", ./action/target/@package, " "))' | |
+ .format(requests=requests)) | |
def group_by(self, xpath): | |
self.groups.append(ET.XPath(xpath)) | |
@@ -33,6 +35,8 @@ class RequestSplitter(object): | |
def filter_only(self): | |
ret = [] | |
for request in self.requests: | |
+ target_package = request.find('./action/target').get('package') | |
+ self.suppliment(request, target_package) | |
if self.filter_check(request): | |
ret.append(request) | |
return ret | |
@@ -111,45 +115,43 @@ class RequestSplitter(object): | |
key.append(element[0]) | |
return '__'.join(key) | |
- def propose_staging_load(self): | |
- self._stagings_available = {} | |
+ def propose_stagings_load(self, stagings): | |
+ self.stagings_considerable = {} | |
if self.api.rings: | |
xpath = 'link[@project="{}"]'.format(self.api.rings[0]) | |
- for staging in self.api.get_staging_projects(): | |
- if self.api.is_adi_project(staging) or staging.endswith(':DVD'): | |
- continue | |
- # TODO Allow stagings that have not finished building by threshold. | |
- if len(self.api.get_prj_pseudometa(staging)['requests']) > 0: | |
- continue | |
+ # Use specified list of stagings, otherwise only empty, letter stagings. | |
+ if len(stagings) == 0: | |
+ stagings = self.api.get_staging_projects_short() | |
+ filter_skip = False | |
+ else: | |
+ filter_skip = True | |
- letter = self.api.extract_staging_letter(staging) | |
- if len(letter) > 1: | |
- continue | |
+ for staging in stagings: | |
+ project = self.api.prj_from_short(staging) | |
+ | |
+ if not filter_skip: | |
+ if len(staging) > 1: | |
+ continue | |
+ | |
+ # TODO Allow stagings that have not finished building by threshold. | |
+ if len(self.api.get_prj_pseudometa(project)['requests']) > 0: | |
+ continue | |
if self.api.rings: | |
- meta = self.api.get_prj_meta(staging) | |
- self._stagings_available[letter] = True if meta.find(xpath) is not None else False | |
+ # Determine if staging is bootstrapped. | |
+ meta = self.api.get_prj_meta(project) | |
+ self.stagings_considerable[staging] = True if meta.find(xpath) is not None else False | |
else: | |
- self._stagings_available[letter] = False | |
- | |
- def propose_staging_list(self, stagings): | |
- if not hasattr(self, '_stagings_available'): | |
- self.propose_staging_load() | |
- | |
- if len(stagings) > 0: | |
- self.stagings_available = {} | |
- # Filter those not in the list of stagings to consider. | |
- for staging, bootstrapped in self._stagings_available.items(): | |
- if staging in stagings: | |
- self.stagings_available[staging] = bootstrapped | |
- else: | |
- self.stagings_available = self._stagings_available.copy() | |
+ self.stagings_considerable[staging] = False | |
+ | |
+ # Allow both considered and remaining to be accessible after proposal. | |
+ self.stagings_available = self.stagings_considerable.copy() | |
def propose_assignment(self, stagings): | |
- # Determine available stagings and make working copy | |
- self.propose_staging_list(stagings) | |
+ # Determine available stagings and make working copy. | |
+ self.propose_stagings_load(stagings) | |
if len(self.grouped) > len(self.stagings_available): | |
return 'more groups than available stagings' | |
diff --git a/osclib/stagingapi.py b/osclib/stagingapi.py | |
index 8086d56..d97648f 100644 | |
--- a/osclib/stagingapi.py | |
+++ b/osclib/stagingapi.py | |
@@ -314,13 +314,36 @@ class StagingAPI(object): | |
projects.append(val.get('name')) | |
return projects | |
- def is_adi_project(self, p): | |
- return ':adi:' in p | |
- | |
- def extract_staging_letter(self, p): | |
+ def extract_staging_short(self, p): | |
+ if not ':' in p: | |
+ return p | |
+ prefix = len(self.cstaging) + 1 | |
if p.endswith(':DVD'): | |
p = p[:-4] | |
- return p.split(':')[-1] | |
+ return p[prefix:] | |
+ | |
+ def prj_from_short(self, name): | |
+ if name.startswith(self.cstaging): | |
+ return name | |
+ return '{}:{}'.format(self.cstaging, name) | |
+ | |
+ def get_staging_projects_short(self, adi=False): | |
+ """ | |
+ Get list of staging project by short-hand names. | |
+ :param adi: True for only adi stagings, False for only non-adi stagings, | |
+ and None for both. | |
+ """ | |
+ prefix = len(self.cstaging) + 1 | |
+ projects = [] | |
+ for project in self.get_staging_projects(): | |
+ if project.endswith(':DVD') or \ | |
+ (adi is not None and self.is_adi_project(project) != adi): | |
+ continue | |
+ projects.append(self.extract_staging_short(project)) | |
+ return projects | |
+ | |
+ def is_adi_project(self, p): | |
+ return ':adi:' in p | |
# this function will crash if given a non-adi project name | |
def extract_adi_number(self, p): |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment