def func(parser, options, args): """Commit a number of patches.""" stack = directory.repository.current_stack args = common.parse_patches(args, list(stack.patchorder.all_visible)) if len([x for x in [args, options.number is not None, options.all] if x]) > 1: parser.error('too many options') if args: patches = [pn for pn in stack.patchorder.all_visible if pn in args] bad = set(args) - set(patches) if bad: raise common.CmdException('Nonexistent or hidden patch names: %s' % ', '.join(sorted(bad))) elif options.number is not None: if options.number <= len(stack.patchorder.applied): patches = stack.patchorder.applied[:options.number] else: raise common.CmdException('There are not that many applied patches') elif options.all: patches = stack.patchorder.applied else: patches = stack.patchorder.applied[:1] if not patches: raise common.CmdException('No patches to commit') iw = stack.repository.default_iw def allow_conflicts(trans): # As long as the topmost patch stays where it is, it's OK to # run "stg commit" with conflicts in the index. return len(trans.applied) >= 1 trans = transaction.StackTransaction(stack, 'commit', allow_conflicts = allow_conflicts) try: common_prefix = 0 for i in range(min(len(stack.patchorder.applied), len(patches))): if stack.patchorder.applied[i] == patches[i]: common_prefix += 1 else: break if common_prefix < len(patches): to_push = [pn for pn in stack.patchorder.applied[common_prefix:] if pn not in patches[common_prefix:]] # this pops all the applied patches from common_prefix trans.pop_patches(lambda pn: pn in to_push) for pn in patches[common_prefix:]: trans.push_patch(pn, iw) else: to_push = [] new_base = trans.patches[patches[-1]] for pn in patches: trans.patches[pn] = None trans.applied = [pn for pn in trans.applied if pn not in patches] trans.base = new_base out.info('Committed %d patch%s' % (len(patches), ['es', ''][len(patches) == 1])) for pn in to_push: trans.push_patch(pn, iw) except transaction.TransactionHalted: pass return trans.run(iw)
def func(parser, options, args): """Pushes the given patches or the first unapplied onto the stack.""" stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'push', check_clean_iw=clean_iw) if options.number == 0: # explicitly allow this without any warning/error message return if not trans.unapplied: raise common.CmdException('No patches to push') if options.all: patches = list(trans.unapplied) elif options.number is not None: patches = trans.unapplied[:options.number] elif not args: patches = [trans.unapplied[0]] else: try: patches = common.parse_patches(args, trans.unapplied) except common.CmdException as e: try: patches = common.parse_patches(args, trans.applied) except common.CmdException: raise e else: raise common.CmdException( 'Patch%s already applied: %s' % ('es' if len(patches) > 1 else '', ', '.join(patches))) assert patches if options.reverse: patches.reverse() if options.set_tree: for pn in patches: trans.push_tree(pn) else: try: if options.merged: merged = set(trans.check_merged(patches)) else: merged = set() for pn in patches: trans.push_patch(pn, iw, allow_interactive=True, already_merged=pn in merged) except transaction.TransactionHalted: pass return trans.run(iw)
def get_patch(stack, given_patch): """Get the name of the patch we are to refresh.""" if given_patch: patch_name = given_patch if not stack.patches.exists(patch_name): raise common.CmdException('%s: no such patch' % patch_name) return patch_name else: if not stack.patchorder.applied: raise common.CmdException( 'Cannot refresh top patch because no patches are applied') return stack.patchorder.applied[-1]
def func(parser, options, args): """Generate a new commit for the current or given patch.""" # Catch illegal argument combinations. path_limiting = bool(args or options.update) if options.index and path_limiting: raise common.CmdException( 'Only full refresh is available with the --index option') if options.index and options.force: raise common.CmdException( 'You cannot --force a full refresh when using --index mode') stack = directory.repository.current_stack patch_name = get_patch(stack, options.patch) paths = list_files(stack, patch_name, args, options.index, options.update) # Make sure there are no conflicts in the files we want to # refresh. if stack.repository.default_index.conflicts() & paths: raise common.CmdException( 'Cannot refresh -- resolve conflicts first') # Make sure the index is clean before performing a full refresh if not options.index and not options.force: if not (stack.repository.default_index.is_clean(stack.head) or stack.repository.default_iw.worktree_clean()): raise common.CmdException( 'The index is dirty. Did you mean --index? To force a full refresh use --force.') # Commit index to temp patch, and absorb it into the target patch. retval, temp_name = make_temp_patch( stack, patch_name, paths, temp_index = path_limiting) if retval != utils.STGIT_SUCCESS: return retval def edit_fun(cd): orig_msg = cd.message cd, failed_diff = edit.auto_edit_patch( stack.repository, cd, msg = options.message, contains_diff = False, author = options.author, committer = lambda p: p, sign_str = options.sign_str) assert not failed_diff if options.edit: cd, failed_diff = edit.interactive_edit_patch( stack.repository, cd, edit_diff = False, diff_flags = [], replacement_diff = None) assert not failed_diff if not options.no_verify and (options.edit or cd.message != orig_msg): cd = common.run_commit_msg_hook(stack.repository, cd, options.edit) return cd return absorb(stack, patch_name, temp_name, edit_fun, annotate = options.annotate)
def func(parser, options, args): stack = directory.repository.current_stack iw = stack.repository.default_iw if len(args) >= 1: ref, patches = args[0], args[1:] state = log.get_log_entry(stack.repository, ref, stack.repository.rev_parse(ref)) elif options.hard: iw.checkout_hard(stack.head.data.tree) return utils.STGIT_SUCCESS else: raise common.CmdException('Wrong options or number of arguments') trans = transaction.StackTransaction( stack, 'reset', discard_changes=options.hard, allow_bad_head=True, ) try: if patches: log.reset_stack_partially(trans, iw, state, patches) else: log.reset_stack(trans, iw, state) except transaction.TransactionHalted: pass return trans.run(iw, allow_bad_head=not patches)
def func(parser, options, args): stack = directory.repository.current_stack patches = common.parse_patches(args, list(stack.patchorder.all)) if len(patches) < 2: raise common.CmdException('Need at least two patches') return _squash(stack, stack.repository.default_iw, options.name, options.message, options.save_template, patches, options.no_verify)
def func(parser, options, args): """Create a new patch.""" stack = directory.repository.current_stack if stack.repository.default_index.conflicts(): raise common.CmdException( 'Cannot create a new patch -- resolve conflicts first') # Choose a name for the new patch -- or None, which means make one # up later when we've gotten hold of the commit message. if len(args) == 0: name = None elif len(args) == 1: name = args[0] if stack.patches.exists(name): raise common.CmdException('%s: patch already exists' % name) elif not stack.patches.is_name_valid(name): raise common.CmdException('Invalid patch name: "%s"' % name) else: parser.error('incorrect number of arguments') cd = CommitData( tree=stack.head.data.tree, parents=[stack.head], message='', author=Person.author(), committer=Person.committer(), ) cd = common.update_commit_data(cd, options) if options.save_template: options.save_template(cd.message.encode('utf-8')) return utils.STGIT_SUCCESS if not options.no_verify: cd = common.run_commit_msg_hook(stack.repository, cd) if name is None: name = utils.make_patch_name(cd.message, stack.patches.exists) assert stack.patches.is_name_valid(name) # Write the new patch. stack.repository.default_iw trans = StackTransaction(stack, 'new: %s' % name) trans.patches[name] = stack.repository.commit(cd) trans.applied.append(name) return trans.run()
def func(parser, options, args): """Pop the given patches or the topmost one from the stack.""" stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and not options.spill and iw) or None trans = transaction.StackTransaction(stack, 'pop', check_clean_iw=clean_iw) if options.number == 0: # explicitly allow this without any warning/error message return if not trans.applied: raise common.CmdException('No patches applied') if options.all: patches = trans.applied elif options.number is not None: # reverse it twice to also work with negative or bigger than # the length numbers patches = trans.applied[::-1][:options.number][::-1] elif not args: patches = [trans.applied[-1]] else: patches = common.parse_patches(args, trans.applied, ordered=True) if not patches: #FIXME: Why is this an error, and not just a noop ? raise common.CmdException('No patches to pop') if options.spill: if set(stack.patchorder.applied[-len(patches):]) != set(patches): parser.error('Can only spill topmost applied patches') iw = None # don't touch index+worktree applied = [p for p in trans.applied if p not in set(patches)] unapplied = patches + trans.unapplied try: trans.reorder_patches(applied, unapplied, iw=iw, allow_interactive=True) except transaction.TransactionException: pass return trans.run(iw)
def check_and_append(c, n): next = n.data.parents try: [next] = next except ValueError: out.done() raise common.CmdException( 'Trying to uncommit %s, which does not have exactly one parent' % n.sha1) return c.append(n)
def func(parser, options, args): """Sink patches down the stack. """ stack = directory.repository.current_stack if options.to and options.to not in stack.patchorder.applied: raise common.CmdException( 'Cannot sink below %s since it is not applied' % options.to ) if len(args) > 0: patches = common.parse_patches(args, stack.patchorder.all) else: # current patch patches = list(stack.patchorder.applied[-1:]) if not patches: raise common.CmdException('No patches to sink') if options.to and options.to in patches: raise common.CmdException('Cannot have a sinked patch as target') applied = [p for p in stack.patchorder.applied if p not in patches] if options.to: insert_idx = applied.index(options.to) else: insert_idx = 0 applied = applied[:insert_idx] + patches + applied[insert_idx:] unapplied = [p for p in stack.patchorder.unapplied if p not in patches] iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction( stack, 'sink', check_clean_iw=clean_iw ) try: trans.reorder_patches( applied, unapplied, iw=iw, allow_interactive=True ) except transaction.TransactionHalted: pass return trans.run(iw)
def func(parser, options, args): """Show the name of the previous patch """ if len(args) != 0: parser.error('incorrect number of arguments') stack = directory.repository.get_stack(options.branch) applied = stack.patchorder.applied if applied and len(applied) >= 2: out.stdout(applied[-2]) else: raise common.CmdException('Not enough applied patches')
def func(parser, options, args): """Show the name of the next patch """ if len(args) != 0: parser.error('incorrect number of arguments') stack = directory.repository.get_stack(options.branch) unapplied = stack.patchorder.unapplied if unapplied: out.stdout(unapplied[0]) else: raise common.CmdException('No unapplied patches')
def func(parser, options, args): if len(args) != 1: parser.error('incorrect number of arguments') patch = args[0] stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'goto', check_clean_iw=clean_iw) if patch not in trans.all_patches: candidate = common.get_patch_from_list(patch, trans.all_patches) if candidate is None: raise common.CmdException('Patch "%s" does not exist' % patch) patch = candidate if patch in trans.applied: to_pop = set(trans.applied[trans.applied.index(patch) + 1:]) assert not trans.pop_patches(lambda pn: pn in to_pop) elif patch in trans.unapplied: try: to_push = trans.unapplied[:trans.unapplied.index(patch) + 1] if options.merged: merged = set(trans.check_merged(to_push)) else: merged = set() for pn in to_push: trans.push_patch(pn, iw, allow_interactive=True, already_merged=pn in merged) except transaction.TransactionHalted: pass else: raise common.CmdException('Cannot goto a hidden patch') return trans.run(iw)
def func(parser, options, args): stack = directory.repository.current_stack if options.number < 1: raise common.CmdException('Bad number of commands to undo') state = log.undo_state(stack, options.number) trans = transaction.StackTransaction(stack, 'undo %d' % options.number, discard_changes=options.hard, allow_bad_head=True) try: log.reset_stack(trans, stack.repository.default_iw, state) except transaction.TransactionHalted: pass return trans.run(stack.repository.default_iw, allow_bad_head=True)
def _squash(stack, iw, name, msg, save_template, patches, no_verify=False): # If a name was supplied on the command line, make sure it's OK. def bad_name(pn): return pn not in patches and stack.patches.exists(pn) def get_name(cd): return name or utils.make_patch_name(cd.message, bad_name) if name and bad_name(name): raise common.CmdException('Patch name "%s" already taken') def make_squashed_patch(trans, new_commit_data): name = get_name(new_commit_data) trans.patches[name] = stack.repository.commit(new_commit_data) trans.unapplied.insert(0, name) trans = transaction.StackTransaction(stack, 'squash', allow_conflicts=True) push_new_patch = bool(set(patches) & set(trans.applied)) try: new_commit_data = _squash_patches(trans, patches, msg, save_template, no_verify) if new_commit_data: # We were able to construct the squashed commit # automatically. So just delete its constituent patches. to_push = trans.delete_patches(lambda pn: pn in patches) else: # Automatic construction failed. So push the patches # consecutively, so that a second construction attempt is # guaranteed to work. to_push = trans.pop_patches(lambda pn: pn in patches) for pn in patches: trans.push_patch(pn, iw) new_commit_data = _squash_patches(trans, patches, msg, save_template, no_verify) assert not trans.delete_patches(lambda pn: pn in patches) make_squashed_patch(trans, new_commit_data) # Push the new patch if necessary, and any unrelated patches we've # had to pop out of the way. if push_new_patch: trans.push_patch(get_name(new_commit_data), iw) for pn in to_push: trans.push_patch(pn, iw) except SaveTemplateDone: trans.abort(iw) return except transaction.TransactionHalted: pass return trans.run(iw)
def func(parser, options, args): """Delete one or more patches.""" stack = directory.repository.get_stack(options.branch) if options.branch: iw = None # can't use index/workdir to manipulate another branch else: iw = stack.repository.default_iw if args and options.top: parser.error('Either --top or patches must be specified') elif args: patches = set( common.parse_patches(args, list(stack.patchorder.all), len(stack.patchorder.applied))) elif options.top: applied = stack.patchorder.applied if applied: patches = set([applied[-1]]) else: raise common.CmdException('No patches applied') else: parser.error('No patches specified') if options.spill: if set(stack.patchorder.applied[-len(patches):]) != patches: parser.error('Can only spill topmost applied patches') iw = None # don't touch index+worktree def allow_conflicts(trans): # Allow conflicts if the topmost patch stays the same. if stack.patchorder.applied: return (trans.applied and trans.applied[-1] == stack.patchorder.applied[-1]) else: return not trans.applied trans = transaction.StackTransaction(stack, 'delete', allow_conflicts=allow_conflicts) try: to_push = trans.delete_patches(lambda pn: pn in patches) for pn in to_push: trans.push_patch(pn, iw) except transaction.TransactionHalted: pass return trans.run(iw)
def func(parser, options, args): """Clone the <repository> into the local <dir> and initialises the stack """ if len(args) != 2: parser.error('incorrect number of arguments') repository = args[0] local_dir = args[1] if os.path.exists(local_dir): raise common.CmdException('"%s" exists. Remove it first' % local_dir) clone(repository, local_dir) os.chdir(local_dir) directory = common.DirectoryHasRepositoryLib() directory.setup() Stack.initialise(directory.repository)
def func(parser, options, args): """Unhide a range of patch in the series.""" stack = directory.repository.current_stack trans = transaction.StackTransaction(stack, 'unhide') if not args: parser.error('No patches specified') patches = common.parse_patches(args, trans.all_patches) for p in patches: if p not in trans.hidden: raise common.CmdException('Patch "%s" not hidden' % p) applied = list(trans.applied) unapplied = trans.unapplied + patches hidden = [p for p in trans.hidden if p not in set(patches)] trans.reorder_patches(applied, unapplied, hidden) return trans.run()
def func(parser, options, args): """Reorder patches to make the named patch the topmost one. """ if options.series and args: parser.error('<patches> cannot be used with --series') elif not options.series and not args: parser.error('incorrect number of arguments') stack = directory.repository.current_stack if options.series: if options.series == '-': f = io.open(sys.stdin.fileno()) else: f = io.open(options.series) patches = [] for line in f: patch = re.sub('#.*$', '', line).strip() if patch: patches.append(patch) else: patches = common.parse_patches(args, stack.patchorder.all) if not patches: raise common.CmdException('No patches to float') applied = [p for p in stack.patchorder.applied if p not in patches] + \ patches unapplied = [p for p in stack.patchorder.unapplied if p not in patches] iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'float', check_clean_iw=clean_iw) try: trans.reorder_patches(applied, unapplied, iw=iw) except transaction.TransactionHalted: pass return trans.run(iw)
def func(parser, options, args): """Export a range of patches. """ stack = directory.repository.get_stack(options.branch) if options.dir: dirname = options.dir else: dirname = 'patches-%s' % stack.name directory.cd_to_topdir() if not options.branch and git.local_changes(): out.warn('Local changes in the tree;' ' you might want to commit them first') applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if len(args) != 0: patches = common.parse_patches(args, applied + unapplied, len(applied)) else: patches = applied num = len(patches) if num == 0: raise common.CmdException('No patches applied') zpadding = len(str(num)) if zpadding < 2: zpadding = 2 # get the template if options.template: with io.open(options.template, 'r') as f: tmpl = f.read() else: tmpl = templates.get_template('patchexport.tmpl') if not tmpl: tmpl = '' if not options.stdout: if not os.path.isdir(dirname): os.makedirs(dirname) series = io.open(os.path.join(dirname, 'series'), 'w') # note the base commit for this series base_commit = stack.base.sha1 print('# This series applies on GIT commit %s' % base_commit, file=series) for patch_no, p in enumerate(patches, 1): pname = p if options.patch: pname = '%s.patch' % pname elif options.extension: pname = '%s.%s' % (pname, options.extension) if options.numbered: pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname) pfile = os.path.join(dirname, pname) if not options.stdout: print(pname, file=series) # get the patch description patch = stack.patches.get(p) cd = patch.commit.data descr = cd.message.strip() descr_lines = descr.split('\n') short_descr = descr_lines[0].rstrip() long_descr = '\n'.join(descr_lines[1:]).strip() diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree, options.diff_flags) tmpl_dict = { 'description': descr, 'shortdescr': short_descr, 'longdescr': long_descr, 'diffstat': gitlib.diffstat(diff).rstrip(), 'authname': cd.author.name, 'authemail': cd.author.email, 'authdate': cd.author.date.isoformat(), 'commname': cd.committer.name, 'commemail': cd.committer.email } try: descr = templates.specialize_template(tmpl, tmpl_dict) except KeyError as err: raise common.CmdException('Unknown patch template variable: %s' % err) except TypeError: raise common.CmdException('Only "%(name)s" variables are ' 'supported in the patch template') if options.stdout: if hasattr(sys.stdout, 'buffer'): f = sys.stdout.buffer else: f = sys.stdout else: f = io.open(pfile, 'wb') if options.stdout and num > 1: f.write('\n'.join(['-' * 79, patch.name, '-' * 79, '']).encode('utf-8')) f.write(descr) f.write(diff) if not options.stdout: f.close() if not options.stdout: series.close()
def func(parser, options, args): """Show the patch series """ if options.all and options.short: raise common.CmdException('combining --all and --short is meaningless') stack = directory.repository.get_stack(options.branch) if options.missing: cmp_stack = stack stack = directory.repository.get_stack(options.missing) # current series patches applied = unapplied = hidden = () if options.applied or options.unapplied or options.hidden: if options.all: raise common.CmdException('--all cannot be used with' ' --applied/unapplied/hidden') if options.applied: applied = stack.patchorder.applied if options.unapplied: unapplied = stack.patchorder.unapplied if options.hidden: hidden = stack.patchorder.hidden elif options.all: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied hidden = stack.patchorder.hidden else: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if options.missing: cmp_patches = cmp_stack.patchorder.all else: cmp_patches = () # the filtering range covers the whole series if args: show_patches = parse_patches(args, applied + unapplied + hidden, len(applied)) else: show_patches = applied + unapplied + hidden # missing filtering show_patches = [p for p in show_patches if p not in cmp_patches] # filter the patches applied = [p for p in applied if p in show_patches] unapplied = [p for p in unapplied if p in show_patches] hidden = [p for p in hidden if p in show_patches] if options.short: nr = int(config.get('stgit.shortnr')) if len(applied) > nr: applied = applied[-(nr + 1):] n = len(unapplied) if n > nr: unapplied = unapplied[:nr] elif n < nr: hidden = hidden[:nr - n] patches = applied + unapplied + hidden if options.count: out.stdout(len(patches)) return if not patches: return if options.showbranch: branch_str = stack.name + ':' else: branch_str = '' max_len = 0 if len(patches) > 0: max_len = max([len(i + branch_str) for i in patches]) if applied: for p in applied[:-1]: __print_patch( stack, p, branch_str, '+ ', max_len, options, config.get("stgit.color.applied"), ) __print_patch( stack, applied[-1], branch_str, '> ', max_len, options, config.get("stgit.color.current"), ) for p in unapplied: __print_patch( stack, p, branch_str, '- ', max_len, options, config.get("stgit.color.unapplied"), ) for p in hidden: __print_patch( stack, p, branch_str, '! ', max_len, options, config.get("stgit.color.hidden"), )
def func(parser, options, args): """Publish the stack changes.""" repository = directory.repository stack = repository.get_stack(options.branch) if not args: public_ref = common.get_public_ref(stack.name) elif len(args) == 1: public_ref = args[0] else: parser.error('incorrect number of arguments') if not public_ref.startswith('refs/heads/'): public_ref = 'refs/heads/' + public_ref # just clone the stack if the public ref does not exist if not repository.refs.exists(public_ref): if options.unpublished or options.last: raise common.CmdException('"%s" does not exist' % public_ref) repository.refs.set(public_ref, stack.head, 'publish') out.info('Created "%s"' % public_ref) return public_head = repository.refs.get(public_ref) public_tree = public_head.data.tree # find the last published patch if options.last: last = __get_last(stack, public_tree) if not last: raise common.CmdException( 'Unable to find the last published patch ' '(possibly rebased stack)' ) out.info('%s' % last) return # check for same tree (already up to date) if public_tree.sha1 == stack.head.data.tree.sha1: out.info('"%s" already up to date' % public_ref) return # check for unpublished patches if options.unpublished: published = set(__get_published(stack, public_tree)) for p in stack.patchorder.applied: if p not in published: out.stdout(p) return if options.overwrite: repository.refs.set(public_ref, stack.head, 'publish') out.info('Overwrote "%s"' % public_ref) return # check for rebased stack. In this case we emulate a merge with the stack # base by setting two parents. merge_bases = set(repository.get_merge_bases(public_head, stack.base)) if public_head in merge_bases: # fast-forward the public ref repository.refs.set(public_ref, stack.head, 'publish') out.info('Fast-forwarded "%s"' % public_ref) return if stack.base not in merge_bases: message = 'Merge %s into %s' % ( repository.describe(stack.base).strip(), utils.strip_prefix('refs/heads/', public_ref), ) public_head = __create_commit( repository, stack.head.data.tree, [public_head, stack.base], options, message, ) repository.refs.set(public_ref, public_head, 'publish') out.info('Merged the stack base into "%s"' % public_ref) return # check for new patches from the last publishing. This is done by checking # whether the public tree is the same as the bottom of the checked patch. # If older patches were modified, new patches cannot be detected. The new # patches and their metadata are pushed directly to the published head. for p in stack.patchorder.applied: pc = stack.patches.get(p).commit if public_tree.sha1 == pc.data.parent.data.tree.sha1: if pc.data.is_nochange(): out.info('Ignored new empty patch "%s"' % p) continue cd = pc.data.set_parent(public_head) public_head = repository.commit(cd) public_tree = public_head.data.tree out.info('Published new patch "%s"' % p) # create a new commit (only happens if no new patches are detected) if public_tree.sha1 != stack.head.data.tree.sha1: public_head = __create_commit(repository, stack.head.data.tree, [public_head], options) # update the public head repository.refs.set(public_ref, public_head, 'publish') out.info('Updated "%s"' % public_ref)
def func(parser, options, args): """Uncommit a number of patches. """ stack = directory.repository.current_stack if options.to: if options.number: parser.error('cannot give both --to and --number') if len(args) != 0: parser.error('cannot specify patch name with --to') patch_nr = patchnames = None to_commit = stack.repository.rev_parse(options.to) # check whether the --to commit is on a different branch merge_bases = directory.repository.get_merge_bases( to_commit, stack.base) if to_commit not in merge_bases: to_commit = merge_bases[0] options.exclusive = True elif options.number: if options.number <= 0: parser.error('invalid value passed to --number') patch_nr = options.number if len(args) == 0: patchnames = None elif len(args) == 1: # prefix specified patchnames = [ '%s%d' % (args[0], i) for i in range(patch_nr, 0, -1) ] else: parser.error('when using --number, specify at most one patch name') elif len(args) == 0: patchnames = None patch_nr = 1 else: patchnames = args patch_nr = len(patchnames) def check_and_append(c, n): next = n.data.parents try: [next] = next except ValueError: out.done() raise common.CmdException( 'Trying to uncommit %s, which does not have exactly one parent' % n.sha1) return c.append(n) commits = [] next_commit = stack.base if patch_nr: out.start('Uncommitting %d patches' % patch_nr) for i in range(patch_nr): check_and_append(commits, next_commit) next_commit = next_commit.data.parent else: if options.exclusive: out.start('Uncommitting to %s (exclusive)' % to_commit.sha1) else: out.start('Uncommitting to %s' % to_commit.sha1) while True: if next_commit == to_commit: if not options.exclusive: check_and_append(commits, next_commit) break check_and_append(commits, next_commit) next_commit = next_commit.data.parent patch_nr = len(commits) taken_names = set(stack.patchorder.all) if patchnames: for pn in patchnames: if pn in taken_names: raise common.CmdException('Patch name "%s" already taken' % pn) taken_names.add(pn) else: patchnames = [] for c in reversed(commits): pn = utils.make_patch_name(c.data.message, lambda pn: pn in taken_names) patchnames.append(pn) taken_names.add(pn) patchnames.reverse() trans = transaction.StackTransaction(stack, 'uncommit', allow_conflicts=True, allow_bad_head=True) for commit, pn in zip(commits, patchnames): trans.patches[pn] = commit trans.applied = list(reversed(patchnames)) + trans.applied trans.run(set_head=False) out.done()
def func(parser, options, args): """Edit the given patch or the current one. """ stack = directory.repository.current_stack if len(args) == 0: if not stack.patchorder.applied: raise common.CmdException( 'Cannot edit top patch, because no patches are applied') patchname = stack.patchorder.applied[-1] elif len(args) == 1: [patchname] = args if not stack.patches.exists(patchname): raise common.CmdException('%s: no such patch' % patchname) else: parser.error('Cannot edit more than one patch') cd = orig_cd = stack.patches.get(patchname).commit.data if options.set_tree: cd = cd.set_tree( stack.repository.rev_parse( options.set_tree, discard_stderr=True, object_type='tree' ) ) cd, failed_diff = edit.auto_edit_patch( stack.repository, cd, msg=(None if options.message is None else options.message.encode('utf-8')), contains_diff=True, author=options.author, committer=lambda p: p, sign_str=options.sign_str) if options.save_template: options.save_template( edit.patch_desc(stack.repository, cd, options.diff, options.diff_flags, failed_diff)) return utils.STGIT_SUCCESS use_editor = cd == orig_cd or options.edit if use_editor: cd, failed_diff = edit.interactive_edit_patch( stack.repository, cd, options.diff, options.diff_flags, failed_diff, ) def failed(reason='Edited patch did not apply.'): fn = '.stgit-failed.patch' with io.open(fn, 'wb') as f: f.write(edit.patch_desc(stack.repository, cd, options.diff, options.diff_flags, failed_diff)) out.error(reason, 'The patch has been saved to "%s".' % fn) return utils.STGIT_COMMAND_ERROR # If we couldn't apply the patch, fail without even trying to # effect any of the changes. if failed_diff: return failed() if not options.no_verify and (use_editor or cd.message != orig_cd.message): try: cd = common.run_commit_msg_hook(stack.repository, cd, use_editor) except Exception: if options.diff: failed('The commit-msg hook failed.') raise # The patch applied, so now we have to rewrite the StGit patch # (and any patches on top of it). iw = stack.repository.default_iw trans = transaction.StackTransaction(stack, 'edit', allow_conflicts=True) if patchname in trans.applied: popped = trans.applied[trans.applied.index(patchname) + 1:] assert not trans.pop_patches(lambda pn: pn in popped) else: popped = [] trans.patches[patchname] = stack.repository.commit(cd) try: for pn in popped: if options.set_tree: trans.push_tree(pn) else: trans.push_patch(pn, iw, allow_interactive=True) except transaction.TransactionHalted: pass try: # Either a complete success, or a conflict during push. But in # either case, we've successfully effected the edits the user # asked us for. return trans.run(iw) except transaction.TransactionException: # Transaction aborted -- we couldn't check out files due to # dirty index/worktree. The edits were not carried out. return failed()