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 check_merged(self, patches, tree=None, quiet=False): """Return a subset of patches already merged.""" if not quiet: out.start('Checking for patches merged upstream') merged = [] if tree: self.temp_index.read_tree(tree) self.temp_index_tree = tree elif self.temp_index_tree != self.stack.head.data.tree: self.temp_index.read_tree(self.stack.head.data.tree) self.temp_index_tree = self.stack.head.data.tree for pn in reversed(patches): # check whether patch changes can be reversed in the current index cd = self.patches[pn].data if cd.is_nochange(): continue try: self.temp_index.apply_treediff( cd.tree, cd.parent.data.tree, quiet=True, ) merged.append(pn) # The self.temp_index was modified by apply_treediff() so # force read_tree() the next time merge() is used. self.temp_index_tree = None except MergeException: pass if not quiet: out.done('%d found' % len(merged)) return merged
def __send_message(msg_type, tmpl, options, *args): """Message sending dispatcher. """ domain = options.domain or config.get('stgit.domain') if domain: if sys.version_info < (3, 2): raise CmdException("Setting domain requires Python version 3.2+") msg_id = email.utils.make_msgid('stgit', domain=domain) else: msg_id = email.utils.make_msgid('stgit') if msg_type == 'cover': assert len(args) == 1, 'too many args for msg_type == "cover"' patches = args[0] msg = __build_cover(tmpl, msg_id, options, patches) outstr = 'the cover message' elif msg_type == 'patch': patch, patch_nr, total_nr, ref_id = args msg = __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id) outstr = 'patch "%s"' % patch else: raise AssertionError('invalid msg_type: %s' % msg_type) # pragma: no cover if hasattr(msg, 'as_bytes'): msg_bytes = msg.as_bytes(options.mbox) else: msg_bytes = msg.as_string(options.mbox) # Python 3.3 only has Message.as_string(). We encode it back to bytes # and hope for the best. if isinstance(msg_bytes, text): msg_bytes = msg_bytes.encode('utf-8') if options.mbox: out.stdout_bytes(msg_bytes + b'\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg_bytes, msg['From'], options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_bytes) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_bytes, options) # give recipients a chance of receiving related patches in correct order if msg_type == 'cover' or (msg_type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def tree_status(files=None, tree_id='HEAD', verbose=False): """Get the status of all changed files, or of a selected set of files. Returns a list of pairs - (status, filename). If 'not files', it will check all files. If 'files' is a list, it will only check the files in the list. """ if verbose: out.start('Checking for changes in the working directory') refresh_index() if files is None: files = [] cache_files = [] # conflicted files conflicts = get_conflicts() cache_files += [('C', filename) for filename in conflicts if not files or filename in files] reported_files = set(conflicts) files_left = [f for f in files if f not in reported_files] # files in the index. Only execute this code if no files were # specified when calling the function (i.e. report all files) or # files were specified but already found in the previous step if not files or files_left: args = [tree_id] if files_left: args += ['--'] + files_left diff_index_lines = GRun('diff-index', '-z', *args).output_lines('\0') for t, fn in parse_git_ls(diff_index_lines): # the condition is needed in case files is emtpy and # diff-index lists those already reported if fn not in reported_files: cache_files.append((t, fn)) reported_files.add(fn) files_left = [f for f in files if f not in reported_files] # files in the index but changed on (or removed from) disk. Only # execute this code if no files were specified when calling the # function (i.e. report all files) or files were specified but # already found in the previous step if not files or files_left: args = [] if files_left: args += ['--'] + files_left diff_files_lines = GRun('diff-files', '-z', *args).output_lines('\0') for t, fn in parse_git_ls(diff_files_lines): # the condition is needed in case files is empty and # diff-files lists those already reported if fn not in reported_files: cache_files.append((t, fn)) reported_files.add(fn) if verbose: out.done() return cache_files
def call_editor(filename): """Run the editor on the specified filename.""" cmd = '%s %s' % (get_editor(), filename) out.start('Invoking the editor: "%s"' % cmd) err = os.system(cmd) if err: raise EditorException('editor failed, exit code: %d' % err) out.done()
def __cleanup_branch(name, force = False): branch = stack.Series(name) if branch.get_protected(): raise CmdException('This branch is protected. Clean up is not permitted') out.start('Cleaning up branch "%s"' % name) branch.delete(force = force, cleanup = True) out.done()
def prepare_rebase(crt_series): # pop all patches applied = crt_series.get_applied() if len(applied) > 0: out.start('Popping all applied patches') crt_series.pop_patch(applied[0]) out.done() return applied
def __cleanup_branch(name, force=False): branch = stack.Series(name) if branch.get_protected(): raise CmdException( 'This branch is protected. Clean up is not permitted') out.start('Cleaning up branch "%s"' % name) branch.delete(force=force, cleanup=True) out.done()
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 check_and_append(c, n): next = n.data.parents try: [next] = next except ValueError: out.done() raise CmdException( 'Trying to uncommit %s, which does not have exactly one parent' % n.sha1) return c.append(n)
def check_local_changes(repository): out.start('Checking for changes in the working directory') iw = repository.default_iw iw.refresh_index() tree = repository.refs.get(repository.head_ref).data.tree local_changes = iw.changed_files(tree) out.done() if local_changes: raise CmdException( 'local changes in the tree. Use "refresh" or "reset --hard"')
def __cleanup_branch(name, force=False): stack = directory.repository.get_stack(name) if stack.protected: raise CmdException('This branch is protected. Clean up is not permitted') if not force and stack.patchorder.all: raise CmdException('Cannot clean up: the series still contains patches') out.start('Cleaning up branch "%s"' % name) stack.cleanup() out.done()
def __delete_branch(doomed_name, force = False): doomed = stack.Series(doomed_name) if __is_current_branch(doomed_name): raise CmdException('Cannot delete the current branch') if doomed.get_protected(): raise CmdException('This branch is protected. Delete is not permitted') out.start('Deleting branch "%s"' % doomed_name) doomed.delete(force) out.done()
def __delete_branch(doomed_name, force=False): doomed = stack.Series(doomed_name) if __is_current_branch(doomed_name): raise CmdException('Cannot delete the current branch') if doomed.get_protected(): raise CmdException('This branch is protected. Delete is not permitted') out.start('Deleting branch "%s"' % doomed_name) doomed.delete(force) out.done()
def check_local_changes(stack=None): out.start('Checking for changes in the working directory') if stack: local_changes = stack.repository.default_iw.changed_files( stack.head.data.tree) else: local_changes = git.local_changes() out.done() if local_changes: raise CmdException( 'local changes in the tree. Use "refresh" or "reset --hard"')
def rebase(crt_series, target): try: tree_id = git_id(crt_series, target) except: # it might be that we use a custom rebase command with its own # target type tree_id = target if target: out.start('Rebasing to "%s"' % target) else: out.start('Rebasing to the default target') git.rebase(tree_id = tree_id) out.done()
def rebase(crt_series, target): try: tree_id = git_id(crt_series, target) except StgException: # it might be that we use a custom rebase command with its own # target type tree_id = target if target: out.start('Rebasing to "%s"' % target) else: out.start('Rebasing to the default target') git.rebase(tree_id=tree_id) out.done()
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 pop_patches(crt_series, patches, keep=False): """Pop the patches in the list from the stack. It is assumed that the patches are listed in the stack reverse order. """ if len(patches) == 0: out.info('Nothing to push/pop') else: p = patches[-1] if len(patches) == 1: out.start('Popping patch "%s"' % p) else: out.start('Popping patches "%s" - "%s"' % (patches[0], p)) crt_series.pop_patch(p, keep) out.done()
def pop_patches(crt_series, patches, keep = False): """Pop the patches in the list from the stack. It is assumed that the patches are listed in the stack reverse order. """ if len(patches) == 0: out.info('Nothing to push/pop') else: p = patches[-1] if len(patches) == 1: out.start('Popping patch "%s"' % p) else: out.start('Popping patches "%s" - "%s"' % (patches[0], p)) crt_series.pop_patch(p, keep) out.done()
def __send_message(msg_type, tmpl, options, *args): """Message sending dispatcher. """ msg_id = email.utils.make_msgid('stgit', domain=options.domain or config.get('stgit.domain')) if msg_type == 'cover': assert len(args) == 1, 'too many args for msg_type == "cover"' patches = args[0] msg = __build_cover(tmpl, msg_id, options, patches) outstr = 'the cover message' elif msg_type == 'patch': patch, patch_nr, total_nr, ref_id = args msg = __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id) outstr = 'patch "%s"' % patch else: raise AssertionError('invalid msg_type: %s' % msg_type) # pragma: no cover msg_bytes = msg.as_bytes(options.mbox) if options.mbox: out.stdout_bytes(msg_bytes + b'\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg_bytes, msg['From'], options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_bytes) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_bytes, options) # give recipients a chance of receiving related patches in correct order if msg_type == 'cover' or (msg_type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def __send_message(type, tmpl, options, *args): """Message sending dispatcher. """ (build, outstr) = { 'cover': (__build_cover, 'the cover message'), 'patch': (__build_message, 'patch "%s"' % args[0]), }[type] if type == 'patch': (patch_nr, total_nr) = (args[1], args[2]) msg_id = email.utils.make_msgid('stgit') msg = build(tmpl, msg_id, options, *args) if hasattr(msg, 'as_bytes'): msg_bytes = msg.as_bytes(options.mbox) else: msg_bytes = msg.as_string(options.mbox) # Python 3.3 only has Message.as_string(). We encode it back to bytes # and hope for the best. if isinstance(msg_bytes, text): msg_bytes = msg_bytes.encode('utf-8') if options.mbox: out.stdout_bytes(msg_bytes + b'\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg_bytes, msg['From'], options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_bytes) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_bytes, options) # give recipients a chance of receiving related patches in correct order if type == 'cover' or (type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def func(parser, options, args): """Rename a patch in the series """ crt = crt_series.get_current() if len(args) == 2: old, new = args elif len(args) == 1: if not crt: raise CmdException("No applied top patch to rename exists.") old, [new] = crt, args else: parser.error('incorrect number of arguments') out.start('Renaming patch "%s" to "%s"' % (old, new)) crt_series.rename_patch(old, new) out.done()
def func(parser, options, args): """Rename a patch in the series""" stack = directory.repository.get_stack(options.branch) if len(args) == 2: old, new = args elif len(args) == 1: if not stack.patchorder.applied: raise CmdException("No applied top patch to rename exists.") old = stack.patchorder.applied[-1] new = args[0] else: parser.error('incorrect number of arguments') out.start('Renaming patch "%s" to "%s"' % (old, new)) stack.rename_patch(old, new) log_entry(stack, 'rename %s to %s' % (old, new)) out.done()
def rebase(stack, iw, target_commit=None): command = (config.get('branch.%s.stgit.rebasecmd' % stack.name) or config.get('stgit.rebasecmd')) if not command and not target_commit: raise CmdException('Default rebasing requires a commit') elif target_commit: out.start('Rebasing to "%s"' % target_commit.sha1) else: out.start('Rebasing to the default target') if command: command = command.split() if target_commit is not None: command.append(target_commit.sha1) iw.run(command).run() else: iw.checkout_hard(target_commit) stack.set_head(target_commit, 'rebase') out.done()
def prepare_rebase(stack, cmd_name): # pop all patches iw = stack.repository.default_iw trans = StackTransaction(stack, '%s (pop)' % cmd_name, check_clean_iw=iw) out.start('Popping all applied patches') try: trans.reorder_patches( applied=[], unapplied=trans.applied + trans.unapplied, iw=iw, allow_interactive=True, ) except TransactionException: pass retval = trans.run(iw, print_current_patch=False) if retval: out.done('Failed to pop applied patches') else: out.done() return retval
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 read_commit_dag(branch): out.start('Reading commit DAG') commits = {} patches = set() for line in Run('git', 'rev-list', '--parents', '--all').output_lines(): cs = line.split() for id in cs: if id not in commits: commits[id] = Commit(id) for id in cs[1:]: commits[cs[0]].parents.add(commits[id]) commits[id].children.add(commits[cs[0]]) for line in Run('git', 'show-ref').output_lines(): id, ref = line.split() m = re.match(r'^refs/patches/%s/(.+)$' % re.escape(branch), ref) if m and not m.group(1).endswith('.log'): c = commits[id] c.patch = m.group(1) patches.add(c) out.done() return commits, patches
def __delete_branch(doomed_name, force=False): if __is_current_branch(doomed_name): raise CmdException('Cannot delete the current branch') branch = Branch(directory.repository, doomed_name) try: stack = directory.repository.get_stack(doomed_name) except StackException: stack = None if stack: if stack.protected: raise CmdException('This branch is protected. Delete is not permitted') if not force and stack.patchorder.all: raise CmdException('Cannot delete: the series still contains patches') out.start('Deleting branch "%s"' % doomed_name) if stack: stack.cleanup() branch.delete() out.done()
def __send_message(type, tmpl, options, *args): """Message sending dispatcher. """ (build, outstr) = { 'cover': (__build_cover, 'the cover message'), 'patch': (__build_message, 'patch "%s"' % args[0]) }[type] if type == 'patch': (patch_nr, total_nr) = (args[1], args[2]) msg_id = email.utils.make_msgid('stgit') msg = build(tmpl, msg_id, options, *args) msg_str = msg.as_string(options.mbox) if options.mbox: out.stdout_raw(msg_str + '\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg, options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_str) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_str, options) # give recipients a chance of receiving related patches in correct order if type == 'cover' or (type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def push_patches(crt_series, patches, check_merged = False): """Push multiple patches onto the stack. This function is shared between the push and pull commands """ forwarded = crt_series.forward_patches(patches) if forwarded > 1: out.info('Fast-forwarded patches "%s" - "%s"' % (patches[0], patches[forwarded - 1])) elif forwarded == 1: out.info('Fast-forwarded patch "%s"' % patches[0]) names = patches[forwarded:] # check for patches merged upstream if names and check_merged: out.start('Checking for patches merged upstream') merged = crt_series.merged_patches(names) out.done('%d found' % len(merged)) else: merged = [] for p in names: out.start('Pushing patch "%s"' % p) if p in merged: crt_series.push_empty_patch(p) out.done('merged upstream') else: modified = crt_series.push_patch(p) if crt_series.empty_patch(p): out.done('empty patch') elif modified: out.done('modified') else: out.done()
def push_patches(crt_series, patches, check_merged=False): """Push multiple patches onto the stack. This function is shared between the push and pull commands """ forwarded = crt_series.forward_patches(patches) if forwarded > 1: out.info('Fast-forwarded patches "%s" - "%s"' % (patches[0], patches[forwarded - 1])) elif forwarded == 1: out.info('Fast-forwarded patch "%s"' % patches[0]) names = patches[forwarded:] # check for patches merged upstream if names and check_merged: out.start('Checking for patches merged upstream') merged = crt_series.merged_patches(names) out.done('%d found' % len(merged)) else: merged = [] for p in names: out.start('Pushing patch "%s"' % p) if p in merged: crt_series.push_empty_patch(p) out.done('merged upstream') else: modified = crt_series.push_patch(p) if crt_series.empty_patch(p): out.done('empty patch') elif modified: out.done('modified') else: out.done()
def __send_message(type, tmpl, options, *args): """Message sending dispatcher. """ (build, outstr) = {'cover': (__build_cover, 'the cover message'), 'patch': (__build_message, 'patch "%s"' % args[0])}[type] if type == 'patch': (patch_nr, total_nr) = (args[1], args[2]) msg_id = email.utils.make_msgid('stgit') msg = build(tmpl, msg_id, options, *args) msg_str = msg.as_string(options.mbox) if options.mbox: out.stdout_raw(msg_str + '\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg, options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_str) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_str, options) # give recipients a chance of receiving related patches in correct order if type == 'cover' or (type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id
def push_patches(crt_series, patches): """Push multiple patches onto the stack. This function is shared between the push and pull commands """ forwarded = crt_series.forward_patches(patches) if forwarded > 1: out.info('Fast-forwarded patches "%s" - "%s"' % (patches[0], patches[forwarded - 1])) elif forwarded == 1: out.info('Fast-forwarded patch "%s"' % patches[0]) names = patches[forwarded:] for p in names: out.start('Pushing patch "%s"' % p) modified = crt_series.push_patch(p) if crt_series.empty_patch(p): out.done('empty patch') elif modified: out.done('modified') else: out.done()
def func(parser, options, args): """Repair inconsistencies in StGit metadata.""" if args: parser.error('incorrect number of arguments') repository = directory.repository stack = repository.get_stack() if stack.protected: raise CmdException( 'This branch is protected. Modification is not permitted.') patchorder = stack.patchorder patches = [stack.patches.get(pn) for pn in patchorder.all] # Find commits that aren't patches, and applied patches. patchify = [] # commits to definitely patchify maybe_patchify = [] # commits to patchify if we find a patch below them applied = [] c = stack.head while len(c.data.parents) == 1: for p in patches: if p.commit == c: applied.append(p) patchify.extend(maybe_patchify) maybe_patchify = [] break else: maybe_patchify.append(c) c = c.data.parent applied.reverse() patchify.reverse() # Find patches unreachable behind a merge. merge = c todo = set([c]) seen = set() unreachable = set() while todo: c = todo.pop() seen.add(c) todo |= set(c.data.parents) - seen if any(p.commit == c for p in patches): unreachable.add(c) if unreachable: out.warn(('%d patch%s are hidden below the merge commit' % (len(unreachable), ['es', ''][len(unreachable) == 1])), '%s,' % merge.sha1, 'and will be considered unapplied.') # Make patches of any linear sequence of commits on top of a patch. if applied and patchify: out.start('Creating %d new patch%s' % (len(patchify), ['es', ''][len(patchify) == 1])) for c in patchify: pn = make_patch_name( c.data.message, unacceptable=lambda name: any(p.name == name for p in patches), ) out.info('Creating patch %s from commit %s' % (pn, c.sha1)) applied.append(stack.patches.new(pn, c, 'repair')) out.done() # Figure out hidden hidden = [p for p in patches if p.name in patchorder.hidden] # Write the applied/unapplied files. out.start('Checking patch appliedness') unapplied = [p for p in patches if p not in applied and p not in hidden] for pn in patchorder.all: if all(pn != p.name for p in patches): out.info('%s is gone' % pn) for p in applied: if p.name not in patchorder.applied: out.info('%s is now applied' % p.name) for p in unapplied: if p.name not in patchorder.unapplied: out.info('%s is now unapplied' % p.name) for p in hidden: if p.name not in patchorder.hidden: out.info('%s is now hidden' % p.name) out.done() orig_order = dict((pn, i) for i, pn in enumerate(patchorder.all)) def patchname_key(p): i = orig_order.get(p, len(orig_order)) return i, p trans = StackTransaction(stack, 'repair', check_clean_iw=False, allow_bad_head=True) try: trans.applied = [p.name for p in applied] trans.unapplied = sorted((p.name for p in unapplied), key=patchname_key) trans.hidden = sorted((p.name for p in hidden), key=patchname_key) except TransactionHalted: pass return trans.run()
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 __pick_commit(commit_id, patchname, options): """Pick a commit id. """ commit = git.Commit(commit_id) if options.name: patchname = options.name elif patchname and options.revert: patchname = 'revert-' + patchname if patchname: patchname = find_patch_name(patchname, crt_series.patch_exists) if options.parent: parent = git_id(crt_series, options.parent) else: parent = commit.get_parent() if not options.revert: bottom = parent top = commit_id else: bottom = commit_id top = parent if options.fold: out.start('Folding commit %s' % commit_id) # try a direct git apply first if not git.apply_diff(bottom, top, files = options.file): if options.file: raise CmdException('Patch folding failed') else: git.merge_recursive(bottom, git.get_head(), top) out.done() elif options.update: rev1 = git_id(crt_series, 'HEAD^') rev2 = git_id(crt_series, 'HEAD') files = git.barefiles(rev1, rev2).split('\n') out.start('Updating with commit %s' % commit_id) if not git.apply_diff(bottom, top, files = files): raise CmdException('Patch updating failed') out.done() else: message = commit.get_log() if options.revert: if message: lines = message.splitlines() subject = lines[0] body = '\n'.join(lines[2:]) else: subject = commit.get_id_hash() body = '' message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' \ % (subject, commit.get_id_hash(), body) elif options.expose: if not message.endswith('\n'): message += '\n' message += '(imported from commit %s)\n' % commit.get_id_hash() author_name, author_email, author_date = \ name_email_date(commit.get_author()) if options.revert: author_name = author_email = None out.start('Importing commit %s' % commit_id) newpatch = crt_series.new_patch(patchname, message = message, can_edit = False, unapplied = True, bottom = bottom, top = top, author_name = author_name, author_email = author_email, author_date = author_date) # in case the patch name was automatically generated patchname = newpatch.get_name() # find a patchlog to fork from refbranchname, refpatchname = parse_rev(patchname) if refpatchname: if refbranchname: # assume the refseries is OK, since we already resolved # commit_str to a git_id refseries = Series(refbranchname) else: refseries = crt_series patch = refseries.get_patch(refpatchname) if patch.get_log(): out.info("Log was %s" % newpatch.get_log()) out.info("Setting log to %s\n" % patch.get_log()) newpatch.set_log(patch.get_log()) out.info("Log is now %s" % newpatch.get_log()) else: out.info("No log for %s\n" % patchname) if not options.unapplied: modified = crt_series.push_patch(patchname) else: modified = False if crt_series.empty_patch(patchname): out.done('empty patch') elif modified: out.done('modified') else: out.done()
def func(parser, options, args): """Repair inconsistencies in StGit metadata.""" orig_applied = crt_series.get_applied() orig_unapplied = crt_series.get_unapplied() orig_hidden = crt_series.get_hidden() if crt_series.get_protected(): raise CmdException( 'This branch is protected. Modification is not permitted.') # Find commits that aren't patches, and applied patches. head = git.get_commit(git.get_head()).get_id_hash() commits, patches = read_commit_dag(crt_series.get_name()) c = commits[head] patchify = [] # commits to definitely patchify maybe_patchify = [] # commits to patchify if we find a patch below them applied = [] while len(c.parents) == 1: parent, = c.parents if c.patch: applied.append(c) patchify.extend(maybe_patchify) maybe_patchify = [] else: maybe_patchify.append(c) c = parent applied.reverse() patchify.reverse() # Find patches hidden behind a merge. merge = c todo = set([c]) seen = set() hidden = set() while todo: c = todo.pop() seen.add(c) todo |= c.parents - seen if c.patch: hidden.add(c) if hidden: out.warn(('%d patch%s are hidden below the merge commit' % (len(hidden), ['es', ''][len(hidden) == 1])), '%s,' % merge.id, 'and will be considered unapplied.') # Make patches of any linear sequence of commits on top of a patch. names = set(p.patch for p in patches) def name_taken(name): return name in names if applied and patchify: out.start('Creating %d new patch%s' % (len(patchify), ['es', ''][len(patchify) == 1])) for p in patchify: name = make_patch_name(p.commit.get_log(), name_taken) out.info('Creating patch %s from commit %s' % (name, p.id)) aname, amail, adate = name_email_date(p.commit.get_author()) cname, cmail, cdate = name_email_date(p.commit.get_committer()) parent, = p.parents crt_series.new_patch( name, can_edit = False, commit = False, top = p.id, bottom = parent.id, message = p.commit.get_log(), author_name = aname, author_email = amail, author_date = adate, committer_name = cname, committer_email = cmail) p.patch = name applied.append(p) names.add(name) out.done() # Figure out hidden orig_patches = orig_applied + orig_unapplied + orig_hidden orig_applied_name_set = set(orig_applied) orig_unapplied_name_set = set(orig_unapplied) orig_hidden_name_set = set(orig_hidden) orig_patches_name_set = set(orig_patches) hidden = [p for p in patches if p.patch in orig_hidden_name_set] # Write the applied/unapplied files. out.start('Checking patch appliedness') unapplied = patches - set(applied) - set(hidden) applied_name_set = set(p.patch for p in applied) unapplied_name_set = set(p.patch for p in unapplied) hidden_name_set = set(p.patch for p in hidden) patches_name_set = set(p.patch for p in patches) for name in orig_patches_name_set - patches_name_set: out.info('%s is gone' % name) for name in applied_name_set - orig_applied_name_set: out.info('%s is now applied' % name) for name in unapplied_name_set - orig_unapplied_name_set: out.info('%s is now unapplied' % name) for name in hidden_name_set - orig_hidden_name_set: out.info('%s is now hidden' % name) orig_order = dict(zip(orig_patches, range(len(orig_patches)))) def patchname_key(p): i = orig_order.get(p, len(orig_order)) return i, p crt_series.set_applied(p.patch for p in applied) crt_series.set_unapplied(sorted(unapplied_name_set, key=patchname_key)) crt_series.set_hidden(sorted(hidden_name_set, key=patchname_key)) out.done()
def func(parser, options, args): """Synchronise a range of patches """ if options.ref_branch: remote_series = stack.Series(options.ref_branch) if options.ref_branch == crt_series.get_name(): raise CmdException('Cannot synchronise with the current branch') remote_patches = remote_series.get_applied() # the merge function merge_patch(patch, pname) merge_patch = lambda patch, pname: \ __branch_merge_patch(remote_series, pname) elif options.series: patchdir = os.path.dirname(options.series) remote_patches = [] with open(options.series) as f: for line in f: p = re.sub('#.*$', '', line).strip() if not p: continue remote_patches.append(p) # the merge function merge_patch(patch, pname) merge_patch = lambda patch, pname: \ __series_merge_patch(patch.get_bottom(), patchdir, pname) else: raise CmdException('No remote branch or series specified') applied = crt_series.get_applied() unapplied = crt_series.get_unapplied() if options.all: patches = applied elif len(args) != 0: patches = parse_patches(args, applied + unapplied, len(applied), ordered = True) elif applied: patches = [crt_series.get_current()] else: parser.error('no patches applied') if not patches: raise CmdException('No patches to synchronise') __check_all() # 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') # 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: pop_patches(crt_series, to_pop[::-1]) pushed = [first_patch] else: to_pop = [] pushed = [] popped = to_pop + [p for p in patches if p in unapplied] for p in pushed + popped: if p in popped: # push this patch push_patches(crt_series, [p]) if p not in sync_patches: # nothing to synchronise continue # the actual sync out.start('Synchronising "%s"' % p) patch = crt_series.get_patch(p) top = patch.get_top() # reset the patch backup information. patch.set_top(top, backup = True) # the actual merging (either from a branch or an external file) merge_patch(patch, p) if git.local_changes(verbose = False): # index (cache) already updated by the git merge. The # backup information was already reset above crt_series.refresh_patch(cache_update = False, backup = False, log = 'sync') out.done('updated') else: out.done()
def func(parser, options, args): """Send the patches by e-mail using the patchmail.tmpl file as a template """ applied = crt_series.get_applied() if options.all: patches = applied elif len(args) >= 1: unapplied = crt_series.get_unapplied() patches = parse_patches(args, applied + unapplied, len(applied)) else: raise CmdException('Incorrect options. Unknown patches to send') # early test for sender identity __get_sender() out.start('Checking the validity of the patches') for p in patches: if crt_series.empty_patch(p): raise CmdException('Cannot send empty patch "%s"' % p) out.done() total_nr = len(patches) if total_nr == 0: raise CmdException('No patches to send') if options.in_reply_to: if options.no_thread or options.unrelated: raise CmdException('--in-reply-to option not allowed with ' '--no-thread or --unrelated') ref_id = options.in_reply_to else: ref_id = None # get username/password if sending by SMTP __set_smtp_credentials(options) # send the cover message (if any) if options.cover or options.edit_cover: if options.unrelated: raise CmdException('cover sending not allowed with --unrelated') # find the template file if options.cover: with open(options.cover) as f: tmpl = f.read() else: tmpl = templates.get_template('covermail.tmpl') if not tmpl: raise CmdException('No cover message template file found') msg_id = __send_message('cover', tmpl, options, patches) # subsequent e-mails are seen as replies to the first one if not options.no_thread: ref_id = msg_id # send the patches if options.template: with open(options.template) as f: tmpl = f.read() else: if options.attach: tmpl = templates.get_template('mailattch.tmpl') elif options.attach_inline: tmpl = templates.get_template('patchandattch.tmpl') else: tmpl = templates.get_template('patchmail.tmpl') if not tmpl: raise CmdException('No e-mail template file found') for (p, n) in zip(patches, range(1, total_nr + 1)): msg_id = __send_message('patch', tmpl, options, p, n, total_nr, ref_id) # subsequent e-mails are seen as replies to the first one if not options.no_thread and not options.unrelated and not ref_id: ref_id = msg_id
def func(parser, options, args): """Repair inconsistencies in StGit metadata.""" orig_applied = crt_series.get_applied() orig_unapplied = crt_series.get_unapplied() orig_hidden = crt_series.get_hidden() if crt_series.get_protected(): raise CmdException( 'This branch is protected. Modification is not permitted.') # Find commits that aren't patches, and applied patches. head = git.get_commit(git.get_head()).get_id_hash() commits, patches = read_commit_dag(crt_series.get_name()) c = commits[head] patchify = [] # commits to definitely patchify maybe_patchify = [] # commits to patchify if we find a patch below them applied = [] while len(c.parents) == 1: parent, = c.parents if c.patch: applied.append(c) patchify.extend(maybe_patchify) maybe_patchify = [] else: maybe_patchify.append(c) c = parent applied.reverse() patchify.reverse() # Find patches hidden behind a merge. merge = c todo = set([c]) seen = set() hidden = set() while todo: c = todo.pop() seen.add(c) todo |= c.parents - seen if c.patch: hidden.add(c) if hidden: out.warn(('%d patch%s are hidden below the merge commit' % (len(hidden), ['es', ''][len(hidden) == 1])), '%s,' % merge.id, 'and will be considered unapplied.') # Make patches of any linear sequence of commits on top of a patch. names = set(p.patch for p in patches) def name_taken(name): return name in names if applied and patchify: out.start('Creating %d new patch%s' % (len(patchify), ['es', ''][len(patchify) == 1])) for p in patchify: name = make_patch_name(p.commit.get_log(), name_taken) out.info('Creating patch %s from commit %s' % (name, p.id)) aname, amail, adate = name_email_date(p.commit.get_author()) cname, cmail, cdate = name_email_date(p.commit.get_committer()) parent, = p.parents crt_series.new_patch( name, can_edit=False, commit=False, top=p.id, bottom=parent.id, message=p.commit.get_log(), author_name=aname, author_email=amail, author_date=adate, committer_name=cname, committer_email=cmail, ) p.patch = name applied.append(p) names.add(name) out.done() # Figure out hidden orig_patches = orig_applied + orig_unapplied + orig_hidden orig_applied_name_set = set(orig_applied) orig_unapplied_name_set = set(orig_unapplied) orig_hidden_name_set = set(orig_hidden) orig_patches_name_set = set(orig_patches) hidden = [p for p in patches if p.patch in orig_hidden_name_set] # Write the applied/unapplied files. out.start('Checking patch appliedness') unapplied = patches - set(applied) - set(hidden) applied_name_set = set(p.patch for p in applied) unapplied_name_set = set(p.patch for p in unapplied) hidden_name_set = set(p.patch for p in hidden) patches_name_set = set(p.patch for p in patches) for name in orig_patches_name_set - patches_name_set: out.info('%s is gone' % name) for name in applied_name_set - orig_applied_name_set: out.info('%s is now applied' % name) for name in unapplied_name_set - orig_unapplied_name_set: out.info('%s is now unapplied' % name) for name in hidden_name_set - orig_hidden_name_set: out.info('%s is now hidden' % name) orig_order = dict(zip(orig_patches, range(len(orig_patches)))) def patchname_key(p): i = orig_order.get(p, len(orig_order)) return i, p crt_series.set_applied(p.patch for p in applied) crt_series.set_unapplied(sorted(unapplied_name_set, key=patchname_key)) crt_series.set_hidden(sorted(hidden_name_set, key=patchname_key)) out.done()
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 __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: patchname = find_patch_name(patchname, stack.patches.exists) else: patchname = make_patch_name(commit.data.message_str, stack.patches.exists) 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, )) trans = StackTransaction( stack, 'pick %s from %s' % (patchname, ref_stack.name)) trans.patches[patchname] = new_commit trans.unapplied.append(patchname) if not options.unapplied: try: trans.push_patch(patchname, iw, allow_interactive=True) except TransactionHalted: pass retval = trans.run(iw, print_current_patch=False) if retval == STGIT_CONFLICT: out.done('conflict(s)') elif stack.patches.get(patchname).is_empty(): out.done('empty patch') else: out.done() return retval
def func(parser, options, args): """Send the patches by e-mail using the patchmail.tmpl file as a template """ stack = directory.repository.current_stack applied = stack.patchorder.applied if options.all: patches = applied elif len(args) >= 1: unapplied = stack.patchorder.unapplied patches = parse_patches(args, applied + unapplied, len(applied)) else: raise CmdException('Incorrect options. Unknown patches to send') # early test for sender identity __get_sender() out.start('Checking the validity of the patches') for p in patches: if stack.patches.get(p).is_empty(): raise CmdException('Cannot send empty patch "%s"' % p) out.done() total_nr = len(patches) if total_nr == 0: raise CmdException('No patches to send') if options.in_reply_to: if options.no_thread or options.unrelated: raise CmdException('--in-reply-to option not allowed with ' '--no-thread or --unrelated') ref_id = options.in_reply_to else: ref_id = None # get username/password if sending by SMTP __set_smtp_credentials(options) # send the cover message (if any) if options.cover or options.edit_cover: if options.unrelated: raise CmdException('cover sending not allowed with --unrelated') # find the template file if options.cover: with io.open(options.cover, 'r') as f: tmpl = f.read() else: tmpl = templates.get_template('covermail.tmpl') if not tmpl: raise CmdException('No cover message template file found') msg_id = __send_message('cover', tmpl, options, patches) # subsequent e-mails are seen as replies to the first one if not options.no_thread: ref_id = msg_id # send the patches if options.template: with io.open(options.template, 'r') as f: tmpl = f.read() else: if options.attach: tmpl = templates.get_template('mailattch.tmpl') elif options.attach_inline: tmpl = templates.get_template('patchandattch.tmpl') else: tmpl = templates.get_template('patchmail.tmpl') if not tmpl: raise CmdException('No e-mail template file found') for (p, n) in zip(patches, range(1, total_nr + 1)): msg_id = __send_message('patch', tmpl, options, p, n, total_nr, ref_id) # subsequent e-mails are seen as replies to the first one if not options.no_thread and not options.unrelated and not ref_id: ref_id = msg_id