def func(parser, options, args): """Integrate a GNU diff patch into the current patch""" if len(args) > 1: parser.error('incorrect number of arguments') repository = directory.repository stack = repository.get_stack() check_local_changes(repository) check_conflicts(repository.default_iw) check_head_top_equal(stack) if len(args) == 1: filename = args[0] else: filename = None applied = stack.patchorder.applied if not applied: raise CmdException('No patches applied') current = applied[-1] if filename: if os.path.exists(filename): out.start('Folding patch "%s"' % filename) with io.open(filename, 'rb') as f: diff = f.read() else: raise CmdException('No such file: %s' % filename) else: out.start('Folding patch from stdin') diff = sys.stdin.buffer.read() if options.threeway: top_patch = stack.patches.get(current) apply_patch( stack, diff, base=top_patch.commit.data.parent, strip=options.strip, reject=options.reject, ) elif options.base: apply_patch( stack, diff, base=git_commit(options.base, repository), reject=options.reject, strip=options.strip, ) else: apply_patch( stack, diff, strip=options.strip, reject=options.reject, ) out.done()
def func(parser, options, args): """Import a GNU diff file as a new patch """ if len(args) > 1: parser.error('incorrect number of arguments') if len(args) == 1: filename = args[0] else: filename = None if not options.url and filename: filename = os.path.abspath(filename) directory.cd_to_topdir() repository = directory.repository stack = repository.current_stack check_local_changes(repository) check_conflicts(repository.default_iw) check_head_top_equal(stack) if options.series: __import_series(filename, options) elif options.mail or options.mbox: __import_mail(filename, options) elif options.url: __import_url(filename, options) else: __import_file(filename, options)
def func(parser, options, args): """Import a GNU diff file as a new patch """ if len(args) > 1: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if len(args) == 1: filename = args[0] else: filename = None if not options.url and filename: filename = os.path.abspath(filename) directory.cd_to_topdir() if options.series: __import_series(filename, options) elif options.mbox: __import_mbox(filename, options) elif options.url: __import_url(filename, options) else: __import_file(filename, options) print_crt_patch(crt_series)
def __refresh_spill(annotate): stack = directory.repository.current_stack # Fetch the topmost patch. patchname = get_patch(stack, None) cd = stack.patches[patchname].data # Set the tree of the patch to the parent. cd = cd.set_tree(cd.parent.data.tree) log_msg = 'refresh (spill)' if annotate: log_msg += '\n\n' + annotate check_head_top_equal(stack) trans = StackTransaction(stack, allow_conflicts=True) trans.patches[patchname] = stack.repository.commit(cd) 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.execute(log_msg) except TransactionException: # Transaction aborted -- we couldn't check out files due to # dirty index/worktree. The edits were not carried out. out.error('Unable to spill the topmost patch') return utils.STGIT_COMMAND_ERROR
def func(parser, options, args): """Create a new patch.""" stack = directory.repository.current_stack if stack.repository.default_index.conflicts(): raise 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 not stack.patches.is_name_valid(name): raise CmdException('Invalid patch name: "%s"' % name) elif name in stack.patches: raise CmdException('%s: patch already exists' % name) else: parser.error('incorrect number of arguments') if options.verbose: verbose = options.verbose else: verbose = config.getbool('stgit.new.verbose') or False cd = CommitData( tree=stack.head.data.tree, parents=[stack.head], message='', author=Person.author(), committer=Person.committer(), ) cd = update_commit_data( stack.repository, cd, message=options.message, author=options.author(cd.author), trailers=options.trailers, edit=(not options.save_template and options.message is None), verbose=verbose, ) if options.save_template: options.save_template(cd.message) return utils.STGIT_SUCCESS if not options.no_verify: cd = run_commit_msg_hook(stack.repository, cd) if name is None: name = stack.patches.make_name(cd.message_str) # Write the new patch. check_head_top_equal(stack) trans = StackTransaction(stack) trans.patches[name] = stack.repository.commit(cd) trans.applied.append(name) return trans.execute('new: %s' % name)
def func(parser, options, args): """Import a commit object as a new patch """ if not args: parser.error('incorrect number of arguments') if options.file and not options.fold: parser.error('--file can only be specified with --fold') if not options.unapplied: check_local_changes() check_conflicts() check_head_top_equal(crt_series) if options.ref_branch: remote_series = Series(options.ref_branch) else: remote_series = crt_series applied = remote_series.get_applied() unapplied = remote_series.get_unapplied() try: patches = parse_patches(args, applied + unapplied, len(applied)) commit_id = None except CmdException: if len(args) > 1: raise # no patches found, try a commit id commit_id = git_id(remote_series, args[0]) if not commit_id and len(patches) > 1: if options.name: raise CmdException('--name can only be specified with one patch') if options.parent: raise CmdException('--parent can only be specified with one patch') if options.update and not crt_series.get_current(): raise CmdException('No patches applied') if commit_id: # Try to guess a patch name if the argument was <branch>:<patch> try: patchname = args[0].split(':')[1] except IndexError: patchname = None __pick_commit(commit_id, patchname, options) else: if options.unapplied: patches.reverse() for patch in patches: __pick_commit(git_id(remote_series, patch), patch, options) print_crt_patch(crt_series)
def func(parser, options, args): """Import a commit object as a new patch """ if not args: parser.error('incorrect number of arguments') if options.file and not options.fold: parser.error('--file can only be specified with --fold') if not options.unapplied: check_local_changes() check_conflicts() check_head_top_equal(crt_series) if options.ref_branch: remote_series = Series(options.ref_branch) else: remote_series = crt_series applied = remote_series.get_applied() unapplied = remote_series.get_unapplied() try: patches = parse_patches(args, applied + unapplied, len(applied)) commit_id = None except CmdException: if len(args) > 1: raise # no patches found, try a commit id commit_id = git_id(remote_series, args[0]) if not commit_id and len(patches) > 1: if options.name: raise CmdException('--name can only be specified with one patch') if options.parent: raise CmdException('--parent can only be specified with one patch') if options.update and not crt_series.get_current(): raise CmdException('No patches applied') if commit_id: # Try to guess a patch name if the argument was <branch>:<patch> try: patchname = args[0].split(':')[1] except IndexError: patchname = None __pick_commit(commit_id, patchname, options) else: if options.unapplied: patches.reverse() for patch in patches: __pick_commit(git_id(remote_series, patch), patch, options) print_crt_patch(crt_series)
def func(parser, options, args): """Integrate a GNU diff patch into the current patch """ if len(args) > 1: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if len(args) == 1: filename = args[0] else: filename = None current = crt_series.get_current() if not current: raise CmdException('No patches applied') if filename: if os.path.exists(filename): out.start('Folding patch "%s"' % filename) else: raise CmdException('No such file: %s' % filename) else: out.start('Folding patch from stdin') if options.threeway: crt_patch = crt_series.get_patch(current) bottom = crt_patch.get_bottom() git.apply_patch( filename=filename, base=bottom, strip=options.strip, reject=options.reject, ) elif options.base: git.apply_patch( filename=filename, reject=options.reject, strip=options.strip, base=git_id(crt_series, options.base), ) else: git.apply_patch( filename=filename, strip=options.strip, reject=options.reject, ) out.done()
def func(parser, options, args): if len(args) != 1: parser.error('incorrect number of arguments') name = args[0] stack = directory.repository.current_stack iw = stack.repository.default_iw check_head_top_equal(stack) if not options.keep: check_index_and_worktree_clean(stack) trans = transaction.StackTransaction(stack) if name not in trans.all_patches: candidates = [pn for pn in trans.all_patches if name in pn] if len(candidates) == 1: name = candidates[0] elif len(candidates) > 1: out.info('Possible patches:\n %s' % '\n '.join(candidates)) raise CmdException('Ambiguous patch name "%s"' % name) elif re.match('[0-9A-Fa-f]{4,40}$', name): sha1 = name name = stack.patches.name_from_sha1(sha1) if not name: raise CmdException('No patch associated with %s' % sha1) else: raise CmdException('Patch "%s" does not exist' % name) if name in trans.applied: to_pop = set(trans.applied[trans.applied.index(name) + 1:]) popped_extra = trans.pop_patches(lambda pn: pn in to_pop) assert not popped_extra elif name in trans.unapplied: try: to_push = trans.unapplied[:trans.unapplied.index(name) + 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 CmdException('Cannot goto a hidden patch') return trans.execute('goto', iw)
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) patches = parse_patches(patches, stack.patchorder.all) else: patches = parse_patches(args, stack.patchorder.all) if not patches: raise CmdException('No patches to float') iw = stack.repository.default_iw check_head_top_equal(stack) if not options.keep and (not options.noapply or any(pn in stack.patchorder.applied for pn in patches)): check_index_and_worktree_clean(stack) trans = transaction.StackTransaction(stack) if options.noapply: applied = [p for p in trans.applied if p not in patches] unapplied = patches + [p for p in trans.unapplied if p not in patches] else: applied = [p for p in trans.applied if p not in patches] + patches unapplied = [p for p in trans.unapplied if p not in patches] try: trans.reorder_patches(applied, unapplied, iw=iw) except transaction.TransactionHalted: pass return trans.execute('float', iw)
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 CmdException('Cannot sink below %s since it is not applied' % options.to) if len(args) > 0: patches = parse_patches(args, stack.patchorder.all) else: # current patch patches = list(stack.patchorder.applied[-1:]) if not patches: raise CmdException('No patches to sink') if options.to and options.to in patches: raise CmdException('Cannot have a sinked patch as target') unapplied_rem = [p for p in stack.patchorder.unapplied if p not in patches] applied_rem = [p for p in stack.patchorder.applied if p not in patches] insert_idx = applied_rem.index(options.to) if options.to else 0 stay_applied, re_applied = applied_rem[:insert_idx], applied_rem[ insert_idx:] if options.nopush: applied = stay_applied + patches unapplied = re_applied + unapplied_rem else: applied = stay_applied + patches + re_applied unapplied = unapplied_rem iw = stack.repository.default_iw check_head_top_equal(stack) if not options.keep: check_index_and_worktree_clean(stack) trans = transaction.StackTransaction(stack) try: trans.reorder_patches(applied, unapplied, iw=iw, allow_interactive=True) except transaction.TransactionHalted: pass return trans.execute('sink', iw)
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 check_head_top_equal(stack) if not options.keep and not options.spill: check_index_and_worktree_clean(stack) trans = transaction.StackTransaction(stack) if options.number == 0: # explicitly allow this without any warning/error message return if not trans.applied: raise 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 = parse_patches(args, trans.applied, ordered=True) if not patches: # FIXME: Why is this an error, and not just a noop ? raise 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 try: trans.reorder_patches( applied=[p for p in trans.applied if p not in patches], unapplied=patches + trans.unapplied, iw=iw, allow_interactive=True, ) except transaction.TransactionException: pass return trans.execute('pop', iw)
def _clean(stack, clean_applied, clean_unapplied): check_head_top_equal(stack) trans = transaction.StackTransaction(stack, allow_conflicts=True) def del_patch(pn): if pn in stack.patchorder.applied: if pn == stack.patchorder.applied[-1]: # We're about to clean away the topmost patch. Don't # do that if we have conflicts, since that means the # patch is only empty because the conflicts have made # us dump its contents into the index and worktree. if stack.repository.default_index.conflicts(): return False return clean_applied and trans.patches[pn].data.is_nochange() elif pn in stack.patchorder.unapplied: return clean_unapplied and trans.patches[pn].data.is_nochange() for pn in trans.delete_patches(del_patch): trans.push_patch(pn) trans.execute('clean')
def func(parser, options, args): """Unhide a range of patch in the series.""" stack = directory.repository.current_stack check_head_top_equal(stack) trans = transaction.StackTransaction(stack) if not args: parser.error('No patches specified') patches = parse_patches(args, trans.all_patches) for p in patches: if p not in trans.hidden: raise 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.execute('unhide')
def func(parser, options, args): """Integrate a GNU diff patch into the current patch """ if len(args) > 1: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if len(args) == 1: filename = args[0] else: filename = None current = crt_series.get_current() if not current: raise CmdException('No patches applied') if filename: if os.path.exists(filename): out.start('Folding patch "%s"' % filename) else: raise CmdException('No such file: %s' % filename) else: out.start('Folding patch from stdin') if options.threeway: crt_patch = crt_series.get_patch(current) bottom = crt_patch.get_bottom() git.apply_patch(filename = filename, base = bottom, strip = options.strip, reject = options.reject) elif options.base: git.apply_patch(filename = filename, reject = options.reject, strip = options.strip, base = git_id(crt_series, options.base)) else: git.apply_patch(filename = filename, strip = options.strip, reject = options.reject) out.done()
def func(parser, options, args): """Hide a range of patch in the series.""" stack = directory.repository.current_stack check_head_top_equal(stack) trans = transaction.StackTransaction(stack) if not args: parser.error('No patches specified') patches = parse_patches(args, trans.all_patches) for p in patches: if p in trans.hidden: out.warn('Patch "%s" already hidden' % p) patches = [p for p in patches if p not in set(trans.hidden)] applied = [p for p in trans.applied if p not in set(patches)] unapplied = [p for p in trans.unapplied if p not in set(patches)] hidden = patches + trans.hidden trans.reorder_patches(applied, unapplied, hidden) return trans.execute('hide')
def func(parser, options, args): """Rebase the current stack """ if len(args) != 1: parser.error('incorrect number of arguments') if crt_series.get_protected(): raise CmdException('This branch is protected. Rebase is not permitted') check_local_changes() check_conflicts() check_head_top_equal(crt_series) # ensure an exception is raised before popping on non-existent target if git_id(crt_series, args[0]) is None: raise GitException('Unknown revision: %s' % args[0]) applied = prepare_rebase(crt_series) rebase(crt_series, args[0]) post_rebase(crt_series, applied, options.nopush, options.merged) print_crt_patch(crt_series)
def func(parser, options, args): """Rebase the current stack """ if len(args) != 1: parser.error('incorrect number of arguments') if crt_series.get_protected(): raise CmdException('This branch is protected. Rebase is not permitted') check_local_changes() check_conflicts() check_head_top_equal(crt_series) # ensure an exception is raised before popping on non-existent target if git_id(crt_series, args[0]) is None: raise GitException('Unknown revision: %s' % args[0]) applied = prepare_rebase(crt_series) rebase(crt_series, args[0]) post_rebase(crt_series, applied, options.nopush, options.merged) print_crt_patch(crt_series)
def absorb(stack, patch_name, temp_name, edit_fun, annotate=None): """Absorb the temp patch into the target patch.""" log_msg = 'refresh' if annotate: log_msg += '\n\n' + annotate check_head_top_equal(stack) trans = StackTransaction(stack) iw = stack.repository.default_iw if patch_name in trans.applied: absorbed = absorb_applied(trans, iw, patch_name, temp_name, edit_fun) else: absorbed = absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun) r = trans.execute(log_msg, iw) if not absorbed: out.warn( 'The new changes did not apply cleanly to %s.' % patch_name, 'They were saved in %s.' % temp_name, ) return r
def __refresh( args, force=False, target_patch=None, message=None, author=None, trailers=None, annotate=None, use_temp_index=False, refresh_from_index=False, only_update_patchfiles=False, include_submodules=False, no_verify=False, invoke_editor=False, edit_diff=False, diff_flags=(), ): stack = directory.repository.current_stack patch_name = get_patch(stack, target_patch) if refresh_from_index: paths = set() else: paths = list_files( stack, patch_name, args, only_update_patchfiles, include_submodules, ) # Make sure there are no conflicts in the files we want to # refresh. if stack.repository.default_index.conflicts() & paths: raise CmdException('Cannot refresh -- resolve conflicts first') # Make sure the index is clean before performing a full refresh if not refresh_from_index and not force: if not ( stack.repository.default_index.is_clean(stack.head) or stack.repository.default_iw.worktree_clean() ): raise CmdException( 'The index is dirty. Did you mean --index? ' 'To force a full refresh use --force.' ) check_head_top_equal(stack) # Update index and write tree tree = write_tree(stack, paths, use_temp_index=use_temp_index) # Run pre-commit hook, if fails, abort refresh if not no_verify: pre_commit_hook = get_hook( stack.repository, 'pre-commit', extra_env={} if invoke_editor else {'GIT_EDITOR': ':'}, ) if pre_commit_hook: try: pre_commit_hook() except RunException: raise CmdException( 'pre-commit hook failed, review the changes using `stg diff`, ' 'run `stg add` to add them to index and run `stg refresh` again' ) else: # Update index and rewrite tree if hook updated files in index if not stack.repository.default_index.is_clean(tree): tree = write_tree(stack, paths, use_temp_index=use_temp_index) # Commit tree to temp patch, and absorb it into the target patch. retval, temp_name = make_temp_patch(stack, patch_name, tree) if retval != utils.STGIT_SUCCESS: return retval def edit_fun(cd): orig_msg = cd.message new_msg = None if message is not None: new_msg = message.encode(config.get('i18n.commitencoding')) cd = auto_edit_patch( stack.repository, cd, msg=new_msg, author=author, trailers=trailers, ) new_patch_name = None if invoke_editor: cd, new_patch_name, failed_diff = interactive_edit_patch( stack.repository, cd, patch_name, edit_diff, diff_flags ) assert not failed_diff if not no_verify and (invoke_editor or cd.message != orig_msg): cd = run_commit_msg_hook(stack.repository, cd, invoke_editor) # Refresh the committer information return cd.set_committer(None), new_patch_name return absorb(stack, patch_name, temp_name, edit_fun, annotate=annotate)
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 if options.number == 0: # explicitly allow this without any warning/error message return if not stack.patchorder.unapplied: raise CmdException('No patches to push') if options.all: if options.noapply: raise CmdException('Cannot use --noapply with --all') patches = list(stack.patchorder.unapplied) elif options.number is not None: if options.noapply: raise CmdException('Cannot use --noapply with --number') patches = list(stack.patchorder.unapplied[: options.number]) elif not args: if options.noapply: raise CmdException('Must supply patch names with --noapply') patches = [stack.patchorder.unapplied[0]] else: try: patches = parse_patches(args, stack.patchorder.unapplied) except CmdException as e: try: patches = parse_patches(args, stack.patchorder.applied) except CmdException: raise e else: raise CmdException( 'Patch%s already applied: %s' % ('es' if len(patches) > 1 else '', ', '.join(patches)) ) assert patches check_head_top_equal(stack) if not options.keep and not options.noapply: check_index_and_worktree_clean(stack) trans = transaction.StackTransaction(stack) if options.reverse: patches.reverse() if options.set_tree: if options.noapply: raise CmdException('Cannot use --noapply with --set-tree') for pn in patches: trans.push_tree(pn) elif options.noapply: if options.merged: raise CmdException('Cannot use --noapply with --merged') unapplied = patches + [pn for pn in trans.unapplied if pn not in patches] trans.reorder_patches(trans.applied, unapplied) 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.execute('push', iw)
def func(parser, options, args): if options.create: if len(args) == 0 or len(args) > 2: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) tree_id = None if len(args) >= 2: parentbranch = None try: branchpoint = git.rev_parse(args[1]) # parent branch? head_re = re.compile('refs/(heads|remotes)/') ref_re = re.compile(args[1] + '$') for ref in git.all_refs(): if head_re.match(ref) and ref_re.search(ref): # args[1] is a valid ref from the branchpoint # setting above parentbranch = args[1] break except git.GitException: # should use a more specific exception to catch only # non-git refs ? out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) # exception in branch = rev_parse() leaves branchpoint unbound branchpoint = None tree_id = git_id(crt_series, branchpoint or args[1]) if parentbranch: out.info('Recording "%s" as parent branch' % parentbranch) else: out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) else: # branch stack off current branch parentbranch = git.get_head_file() if parentbranch: parentremote = git.identify_remote(parentbranch) if parentremote: out.info('Using remote "%s" to pull parent from' % parentremote) else: out.info('Recording as a local branch') else: # no known parent branch, can't guess the remote parentremote = None stack.Series(args[0]).init(create_at=tree_id, parent_remote=parentremote, parent_branch=parentbranch) out.info('Branch "%s" created' % args[0]) log.compat_log_entry('branch --create') return elif options.clone: if len(args) == 0: clone = crt_series.get_name() + \ time.strftime('-%C%y%m%d-%H%M%S') elif len(args) == 1: clone = args[0] else: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Cloning current branch to "%s"' % clone) crt_series.clone(clone) out.done() log.copy_log(log.default_repo(), crt_series.get_name(), clone, 'branch --clone') return elif options.delete: if len(args) != 1: parser.error('incorrect number of arguments') __delete_branch(args[0], options.force) log.delete_log(log.default_repo(), args[0]) return elif options.cleanup: if not args: name = crt_series.get_name() elif len(args) == 1: name = args[0] else: parser.error('incorrect number of arguments') __cleanup_branch(name, options.force) log.delete_log(log.default_repo(), name) return elif options.list: if len(args) != 0: parser.error('incorrect number of arguments') branches = set(git.get_heads()) for br in set(branches): m = re.match(r'^(.*)\.stgit$', br) if m and m.group(1) in branches: branches.remove(br) if branches: out.info('Available branches:') max_len = max([len(i) for i in branches]) for i in sorted(branches): __print_branch(i, max_len) else: out.info('No branches') return elif options.protect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.start('Protecting branch "%s"' % branch_name) branch.protect() out.done() return elif options.rename: if len(args) != 2: parser.error('incorrect number of arguments') if __is_current_branch(args[0]): raise CmdException('Renaming the current branch is not supported') stack.Series(args[0]).rename(args[1]) out.info('Renamed branch "%s" to "%s"' % (args[0], args[1])) log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename') return elif options.unprotect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.info('Unprotecting branch "%s"' % branch_name) branch.unprotect() out.done() return elif options.description is not None: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) branch.set_description(options.description) return elif len(args) == 1: if __is_current_branch(args[0]): raise CmdException('Branch "%s" is already the current branch' % args[0]) if not options.merge: check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Switching to branch "%s"' % args[0]) git.switch_branch(args[0]) out.done() return # default action: print the current branch if len(args) != 0: parser.error('incorrect number of arguments') print(crt_series.get_name())
def __check_all(): check_local_changes() check_conflicts() check_head_top_equal(crt_series)
def func(parser, options, args): """Commit a number of patches.""" stack = directory.repository.current_stack args = parse_patches(args, list(stack.patchorder.all_visible)) exclusive = [args, options.number is not None, options.all] if sum(map(bool, exclusive)) > 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 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 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 CmdException('No patches to commit') if not options.allow_empty: empty_patches = [ pn for pn in patches if stack.patches[pn].data.tree == stack.patches[pn].data.parent.data.tree ] if empty_patches: raise CmdException( 'Empty patch%s (override with --allow-empty): %s' % ('' if len(empty_patches) == 1 else 'es', ', '.join(empty_patches))) 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 check_head_top_equal(stack) trans = transaction.StackTransaction(stack, 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.execute('commit', iw)
def __pick_commit(stack, ref_stack, iw, commit, patchname, options): """Pick a commit.""" repository = stack.repository if options.name: patchname = options.name elif patchname and options.revert: patchname = 'revert-' + patchname if patchname: if patchname in stack.patches: patchname = stack.patches.make_name(patchname, lower=False) else: patchname = stack.patches.make_name(commit.data.message_str) if options.parent: parent = git_commit(options.parent, repository, ref_stack.name) else: parent = commit.data.parent if not options.revert: bottom = parent top = commit else: bottom = commit top = parent if options.fold: out.start('Folding commit %s' % commit.sha1) diff = repository.diff_tree(bottom.data.tree, top.data.tree, pathlimits=options.file) if diff: try: # try a direct git apply first iw.apply(diff, quiet=True) except MergeException: if options.file: out.done('conflict(s)') out.error('%s does not apply cleanly' % patchname) return STGIT_CONFLICT else: try: iw.merge( bottom.data.tree, stack.head.data.tree, top.data.tree, ) except MergeConflictException as e: out.done('%s conflicts' % len(e.conflicts)) out.error('%s does not apply cleanly' % patchname, *e.conflicts) return STGIT_CONFLICT out.done() else: out.done('no changes') return STGIT_SUCCESS elif options.update: files = [ fn1 for _, _, _, _, _, fn1, fn2 in repository.diff_tree_files( stack.top.data.parent.data.tree, stack.top.data.tree) ] diff = repository.diff_tree(bottom.data.tree, top.data.tree, pathlimits=files) out.start('Updating with commit %s' % commit.sha1) try: iw.apply(diff, quiet=True) except MergeException: out.done('conflict(s)') out.error('%s does not apply cleanly' % patchname) return STGIT_CONFLICT else: out.done() return STGIT_SUCCESS else: author = commit.data.author message = commit.data.message_str if options.revert: author = Person.author() if message: lines = message.splitlines() subject = lines[0] body = '\n'.join(lines[2:]) else: subject = commit.sha1 body = '' message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' % ( subject, commit.sha1, body, ) elif options.expose: fmt = config.get('stgit.pick.expose-format') message = Run('git', 'show', '--no-patch', '--pretty=' + fmt, commit.sha1).raw_output() message = message.rstrip() + '\n' out.start('Importing commit %s' % commit.sha1) new_commit = repository.commit( CommitData( tree=top.data.tree, parents=[bottom], message=message, author=author, )) if not options.noapply: check_head_top_equal(stack) trans = StackTransaction(stack) trans.patches[patchname] = new_commit trans.unapplied.append(patchname) if not options.noapply: try: trans.push_patch(patchname, iw, allow_interactive=True) except TransactionHalted: pass retval = trans.execute( 'pick %s from %s' % (patchname, ref_stack.name), iw, print_current_patch=False, ) if retval == STGIT_CONFLICT: out.done('conflict(s)') elif stack.patches[patchname].data.is_nochange(): out.done('empty patch') else: out.done() return retval
def func(parser, options, args): repository = directory.repository if options.create: if len(args) == 0 or len(args) > 2: parser.error('incorrect number of arguments') branch_name = args[0] committish = None if len(args) < 2 else args[1] if committish: check_local_changes(repository) check_conflicts(repository.default_iw) try: stack = repository.get_stack() except (DetachedHeadException, StackException): pass else: check_head_top_equal(stack) stack = __create_branch(branch_name, committish) out.info('Branch "%s" created' % branch_name) log.log_entry(stack, 'branch --create %s' % stack.name) return elif options.clone: cur_branch = Branch(repository, repository.current_branch_name) if len(args) == 0: clone_name = cur_branch.name + time.strftime('-%C%y%m%d-%H%M%S') elif len(args) == 1: clone_name = args[0] else: parser.error('incorrect number of arguments') check_local_changes(repository) check_conflicts(repository.default_iw) try: stack = repository.current_stack except StackException: stack = None base = repository.refs.get(repository.head_ref) else: check_head_top_equal(stack) base = stack.base out.start('Cloning current branch to "%s"' % clone_name) clone = Stack.create( repository, name=clone_name, create_at=base, parent_remote=cur_branch.parent_remote, parent_branch=cur_branch.name, ) if stack: for pn in stack.patchorder.all_visible: patch = stack.patches.get(pn) clone.patches.new(pn, patch.commit, 'clone %s' % stack.name) clone.patchorder.set_order(applied=[], unapplied=stack.patchorder.all_visible, hidden=[]) trans = StackTransaction(clone, 'clone') try: for pn in stack.patchorder.applied: trans.push_patch(pn) except TransactionHalted: pass trans.run() prefix = 'branch.%s.' % cur_branch.name new_prefix = 'branch.%s.' % clone.name for n, v in list(config.getstartswith(prefix)): config.set(n.replace(prefix, new_prefix, 1), v) clone.set_description('clone of "%s"' % cur_branch.name) clone.switch_to() out.done() log.copy_log(log.default_repo(), cur_branch.name, clone.name, 'branch --clone') return elif options.delete: if len(args) != 1: parser.error('incorrect number of arguments') __delete_branch(args[0], options.force) log.delete_log(log.default_repo(), args[0]) return elif options.cleanup: if not args: name = repository.current_branch_name elif len(args) == 1: name = args[0] else: parser.error('incorrect number of arguments') __cleanup_branch(name, options.force) log.delete_log(log.default_repo(), name) return elif options.list: if len(args) != 0: parser.error('incorrect number of arguments') branch_names = sorted( ref.replace('refs/heads/', '', 1) for ref in repository.refs if ref.startswith('refs/heads/') and not ref.endswith('.stgit')) if branch_names: out.info('Available branches:') max_len = max(len(name) for name in branch_names) for branch_name in branch_names: __print_branch(branch_name, max_len) else: out.info('No branches') return elif options.protect: if len(args) == 0: branch_name = repository.current_branch_name elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') try: stack = repository.get_stack(branch_name) except StackException: raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.start('Protecting branch "%s"' % branch_name) stack.protected = True out.done() return elif options.rename: if len(args) == 1: stack = repository.current_stack new_name = args[0] elif len(args) == 2: stack = repository.get_stack(args[0]) new_name = args[1] else: parser.error('incorrect number of arguments') old_name = stack.name stack.rename(new_name) out.info('Renamed branch "%s" to "%s"' % (old_name, new_name)) log.rename_log(repository, old_name, new_name, 'branch --rename') return elif options.unprotect: if len(args) == 0: branch_name = repository.current_branch_name elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') try: stack = repository.get_stack(branch_name) except StackException: raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.info('Unprotecting branch "%s"' % branch_name) stack.protected = False out.done() return elif options.description is not None: if len(args) == 0: branch_name = repository.current_branch_name elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') Branch(repository, branch_name).set_description(options.description) return elif len(args) == 1: branch_name = args[0] if branch_name == repository.current_branch_name: raise CmdException('Branch "%s" is already the current branch' % branch_name) if not options.merge: check_local_changes(repository) check_conflicts(repository.default_iw) try: stack = repository.get_stack() except StackException: pass else: check_head_top_equal(stack) out.start('Switching to branch "%s"' % branch_name) Branch(repository, branch_name).switch_to() out.done() return # default action: print the current branch if len(args) != 0: parser.error('incorrect number of arguments') out.stdout(directory.repository.current_branch_name)
def func(parser, options, args): """Synchronise a range of patches""" repository = directory.repository stack = repository.get_stack() if options.ref_branch: remote_stack = repository.get_stack(options.ref_branch) if remote_stack.name == stack.name: raise CmdException('Cannot synchronise with the current branch') remote_patches = remote_stack.patchorder.applied def merge_patch(commit, pname): return __branch_merge_patch(remote_stack, stack, commit, pname) elif options.series: patchdir = os.path.dirname(options.series) remote_patches = [] with open(options.series) as f: for line in f: pn = re.sub('#.*$', '', line).strip() if not pn: continue remote_patches.append(pn) def merge_patch(commit, pname): return __series_merge_patch(patchdir, stack, commit, pname) else: raise CmdException('No remote branch or series specified') applied = list(stack.patchorder.applied) unapplied = list(stack.patchorder.unapplied) if options.all: patches = applied elif len(args) != 0: patches = parse_patches(args, applied + unapplied, len(applied), ordered=True) elif applied: patches = [applied[-1]] else: parser.error('no patches applied') assert patches # only keep the patches to be synchronised sync_patches = [p for p in patches if p in remote_patches] if not sync_patches: raise CmdException('No common patches to be synchronised') iw = repository.default_iw check_head_top_equal(stack) check_index_and_worktree_clean(stack) # pop to the one before the first patch to be synchronised first_patch = sync_patches[0] if first_patch in applied: to_pop = applied[applied.index(first_patch) + 1:] if to_pop: trans = StackTransaction(stack) popped_extra = trans.pop_patches(lambda pn: pn in to_pop) assert not popped_extra retval = trans.execute('sync (pop)', iw) assert not retval pushed = [first_patch] else: to_pop = [] pushed = [] popped = to_pop + [p for p in patches if p in unapplied] trans = StackTransaction(stack) try: for p in pushed + popped: if p in popped: trans.push_patch(p, iw=iw) if p not in sync_patches: # nothing to synchronise continue # the actual sync out.start('Synchronising "%s"' % p) commit = trans.patches[p] # the actual merging (either from a branch or an external file) tree = merge_patch(commit, p) if tree: trans.patches[p] = commit.data.set_tree(tree).commit( repository) out.done('updated') else: out.done() except TransactionHalted: pass return trans.execute('sync', iw)
def __check_all(): check_local_changes() check_conflicts() check_head_top_equal(crt_series)
def func(parser, options, args): """Import a commit object as a new patch """ if not args: parser.error('incorrect number of arguments') if options.file and not options.fold: parser.error('--file can only be specified with --fold') repository = directory.repository stack = repository.get_stack() iw = repository.default_iw if not options.unapplied: check_local_changes(repository) check_conflicts(iw) check_head_top_equal(stack) if options.ref_branch: ref_stack = repository.get_stack(options.ref_branch) else: ref_stack = stack try: patches = parse_patches( args, ref_stack.patchorder.all_visible, len(ref_stack.patchorder.applied), ) commit = None except CmdException: if len(args) > 1: raise branch, patch = parse_rev(args[0]) if not branch: commit = git_commit(patch, repository, options.ref_branch) patches = [] else: ref_stack = repository.get_stack(branch) patches = parse_patches( [patch], ref_stack.patchorder.all_visible, len(ref_stack.patchorder.applied), ) commit = None if not commit and len(patches) > 1: if options.name: raise CmdException('--name can only be specified with one patch') if options.parent: raise CmdException('--parent can only be specified with one patch') if options.update and not stack.patchorder.applied: raise CmdException('No patches applied') if commit: patchname = None retval = __pick_commit(stack, ref_stack, iw, commit, patchname, options) else: if options.unapplied: patches.reverse() for patchname in patches: commit = git_commit(patchname, repository, ref_stack.name) retval = __pick_commit(stack, ref_stack, iw, commit, patchname, options) if retval != STGIT_SUCCESS: break if retval == STGIT_SUCCESS: print_current_patch(stack) return retval
def func(parser, options, args): """Pull the changes from a remote repository """ policy = config.get('branch.%s.stgit.pull-policy' % crt_series.get_name()) or \ config.get('stgit.pull-policy') if policy == 'rebase': # parent is local if len(args) == 1: parser.error( 'specifying a repository is meaningless for policy="%s"' % policy) if len(args) > 0: parser.error('incorrect number of arguments') else: # parent is remote if len(args) > 1: parser.error('incorrect number of arguments') if len(args) >= 1: repository = args[0] else: repository = crt_series.get_parent_remote() if crt_series.get_protected(): raise CmdException('This branch is protected. Pulls are not permitted') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if policy not in ['pull', 'fetch-rebase', 'rebase']: raise GitConfigException('Unsupported pull-policy "%s"' % policy) applied = prepare_rebase(crt_series) # pull the remote changes if policy == 'pull': out.info('Pulling from "%s"' % repository) git.pull(repository) elif policy == 'fetch-rebase': out.info('Fetching from "%s"' % repository) git.fetch(repository) try: target = git.fetch_head() except git.GitException: out.error( 'Could not find the remote head to rebase onto - fix branch.%s.merge in .git/config' % crt_series.get_name()) out.error('Pushing any patches back...') post_rebase(crt_series, applied, False, False) raise rebase(crt_series, target) elif policy == 'rebase': rebase(crt_series, crt_series.get_parent_branch()) post_rebase(crt_series, applied, options.nopush, options.merged) # maybe tidy up if config.getbool('stgit.keepoptimized'): git.repack() print_crt_patch(crt_series)
def func(parser, options, args): if options.create: if len(args) == 0 or len(args) > 2: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) tree_id = None if len(args) >= 2: parentbranch = None try: branchpoint = git.rev_parse(args[1]) # parent branch? head_re = re.compile('refs/(heads|remotes)/') ref_re = re.compile(args[1] + '$') for ref in git.all_refs(): if head_re.match(ref) and ref_re.search(ref): # args[1] is a valid ref from the branchpoint # setting above parentbranch = args[1] break except git.GitException: # should use a more specific exception to catch only # non-git refs ? out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) # exception in branch = rev_parse() leaves branchpoint unbound branchpoint = None tree_id = git_id(crt_series, branchpoint or args[1]) if parentbranch: out.info('Recording "%s" as parent branch' % parentbranch) else: out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) else: # branch stack off current branch parentbranch = git.get_head_file() if parentbranch: parentremote = git.identify_remote(parentbranch) if parentremote: out.info('Using remote "%s" to pull parent from' % parentremote) else: out.info('Recording as a local branch') else: # no known parent branch, can't guess the remote parentremote = None stack.Series(args[0]).init(create_at = tree_id, parent_remote = parentremote, parent_branch = parentbranch) out.info('Branch "%s" created' % args[0]) log.compat_log_entry('branch --create') return elif options.clone: if len(args) == 0: clone = crt_series.get_name() + \ time.strftime('-%C%y%m%d-%H%M%S') elif len(args) == 1: clone = args[0] else: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Cloning current branch to "%s"' % clone) crt_series.clone(clone) out.done() log.copy_log(log.default_repo(), crt_series.get_name(), clone, 'branch --clone') return elif options.delete: if len(args) != 1: parser.error('incorrect number of arguments') __delete_branch(args[0], options.force) log.delete_log(log.default_repo(), args[0]) return elif options.cleanup: if not args: name = crt_series.get_name() elif len(args) == 1: name = args[0] else: parser.error('incorrect number of arguments') __cleanup_branch(name, options.force) log.delete_log(log.default_repo(), name) return elif options.list: if len(args) != 0: parser.error('incorrect number of arguments') branches = set(git.get_heads()) for br in set(branches): m = re.match(r'^(.*)\.stgit$', br) if m and m.group(1) in branches: branches.remove(br) if branches: out.info('Available branches:') max_len = max([len(i) for i in branches]) for i in sorted(branches): __print_branch(i, max_len) else: out.info('No branches') return elif options.protect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.start('Protecting branch "%s"' % branch_name) branch.protect() out.done() return elif options.rename: if len(args) != 2: parser.error('incorrect number of arguments') if __is_current_branch(args[0]): raise CmdException('Renaming the current branch is not supported') stack.Series(args[0]).rename(args[1]) out.info('Renamed branch "%s" to "%s"' % (args[0], args[1])) log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename') return elif options.unprotect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) out.info('Unprotecting branch "%s"' % branch_name) branch.unprotect() out.done() return elif options.description is not None: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException('Branch "%s" is not controlled by StGIT' % branch_name) branch.set_description(options.description) return elif len(args) == 1: if __is_current_branch(args[0]): raise CmdException('Branch "%s" is already the current branch' % args[0]) if not options.merge: check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Switching to branch "%s"' % args[0]) git.switch_branch(args[0]) out.done() return # default action: print the current branch if len(args) != 0: parser.error('incorrect number of arguments') print(crt_series.get_name())
def func(parser, options, args): """Pull the changes from a remote repository """ policy = config.get('branch.%s.stgit.pull-policy' % crt_series.get_name()) or \ config.get('stgit.pull-policy') if policy == 'rebase': # parent is local if len(args) == 1: parser.error('specifying a repository is meaningless for policy="%s"' % policy) if len(args) > 0: parser.error('incorrect number of arguments') else: # parent is remote if len(args) > 1: parser.error('incorrect number of arguments') if len(args) >= 1: repository = args[0] else: repository = crt_series.get_parent_remote() if crt_series.get_protected(): raise CmdException('This branch is protected. Pulls are not permitted') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if policy not in ['pull', 'fetch-rebase', 'rebase']: raise GitConfigException('Unsupported pull-policy "%s"' % policy) applied = prepare_rebase(crt_series) # pull the remote changes if policy == 'pull': out.info('Pulling from "%s"' % repository) git.pull(repository) elif policy == 'fetch-rebase': out.info('Fetching from "%s"' % repository) git.fetch(repository) try: target = git.fetch_head() except git.GitException: out.error('Could not find the remote head to rebase onto - fix branch.%s.merge in .git/config' % crt_series.get_name()) out.error('Pushing any patches back...') post_rebase(crt_series, applied, False, False) raise rebase(crt_series, target) elif policy == 'rebase': rebase(crt_series, crt_series.get_parent_branch()) post_rebase(crt_series, applied, options.nopush, options.merged) # maybe tidy up if config.get('stgit.keepoptimized') == 'yes': git.repack() print_crt_patch(crt_series)
def perform_edit( stack, cd, orig_patchname, new_patchname, edit_diff, diff_flags, set_tree=None, ): """Given instructions, performs required the edit. :returns: 2-tuple: - the result of the transaction - the new patch name, whether changed or not. """ # Refresh the committer information cd = cd.set_committer(None) # Rewrite the StGit patch with the given diff (and any patches on top of # it). iw = stack.repository.default_iw common.check_head_top_equal(stack) trans = transaction.StackTransaction(stack, allow_conflicts=True) if orig_patchname in trans.applied: popped = trans.applied[trans.applied.index(orig_patchname) + 1:] popped_extra = trans.pop_patches(lambda pn: pn in popped) assert not popped_extra else: popped = [] trans.patches[orig_patchname] = stack.repository.commit(cd) if new_patchname == "": new_patchname = stack.patches.make_name(cd.message_str, allow=orig_patchname) if new_patchname is not None and orig_patchname != new_patchname: out.start('Renaming patch "%s" to "%s"' % (orig_patchname, new_patchname)) trans.rename_patch(orig_patchname, new_patchname) out.done() log_stack_state(stack, 'rename %s to %s' % (orig_patchname, new_patchname)) else: new_patchname = orig_patchname try: for pn in popped: if 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.execute('edit', iw), new_patchname except transaction.TransactionException: # Transaction aborted -- we couldn't check out files due to # dirty index/worktree. The edits were not carried out. note_patch_application_failure( get_patch_description(stack.repository, cd, orig_patchname, edit_diff, diff_flags)) return utils.STGIT_COMMAND_ERROR, new_patchname