def __import_mail_path(mail_path, filename, options): with open(mail_path, 'rb') as f: mail = f.read() msg_path = mail_path + '-msg' patch_path = mail_path + '-patch' mailinfo_cmd = ['git', 'mailinfo'] if options.message_id or config.getbool('stgit.import.message-id'): mailinfo_cmd.append('--message-id') mailinfo_cmd.extend([msg_path, patch_path]) mailinfo_lines = (Run(*mailinfo_cmd).encoding(None).decoding( None).raw_input(mail).output_lines(b'\n')) mailinfo = dict(line.split(b': ', 1) for line in mailinfo_lines if line) with open(msg_path, 'rb') as f: msg_body = f.read() msg_bytes = mailinfo[b'Subject'] + b'\n\n' + msg_body with open(patch_path, 'rb') as f: diff = f.read() __create_patch( None if options.mbox else filename, decode_utf8_with_latin1(msg_bytes), None, mailinfo[b'Author'].decode('utf-8'), mailinfo[b'Email'].decode('utf-8'), mailinfo[b'Date'].decode('utf-8'), diff, options, )
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): """Generate a new commit for the current or given patch.""" if options.spill: if ( len(args) > 0 or options.index or options.edit or options.update or options.patch or options.force or options.no_verify or options.sign_str ): raise CmdException('--spill option does not take any arguments or options') return __refresh_spill(annotate=options.annotate) else: # Catch illegal argument combinations. is_path_limiting = bool(args or options.update) if options.index and is_path_limiting: raise CmdException('Only full refresh is available with the --index option') if options.index and options.force: raise CmdException( 'You cannot --force a full refresh when using --index mode' ) if options.update and options.submodules: raise CmdException( '--submodules is meaningless when only updating modified files' ) if options.index and options.submodules: raise CmdException( '--submodules is meaningless when keeping the current index' ) # If submodules was not specified on the command line, infer a default # from configuration. if options.submodules is None: options.submodules = config.getbool('stgit.refreshsubmodules') return __refresh( args, force=options.force, target_patch=options.patch, message=options.message, author=options.author, sign_str=options.sign_str, annotate=options.annotate, use_temp_index=is_path_limiting, refresh_from_index=options.index, only_update_patchfiles=options.update, include_submodules=options.submodules, no_verify=options.no_verify, invoke_editor=options.edit, )
def merge_recursive(base, head1, head2): """Perform a 3-way merge between base, head1 and head2 into the local tree """ refresh_index() p = GRun('merge-recursive', base, '--', head1, head2).env( { 'GITHEAD_%s' % base: 'ancestor', 'GITHEAD_%s' % head1: 'current', 'GITHEAD_%s' % head2: 'patched'}).returns([0, 1]) output = p.output_lines() if p.exitcode: # There were conflicts if config.getbool('stgit.autoimerge'): mergetool() else: conflicts = [l for l in output if l.startswith('CONFLICT')] out.info(*conflicts) raise GitException("%d conflict(s)" % len(conflicts))
def commit(self, repository): """Commit the commit. :returns: the committed commit :rtype: :class:`Commit` """ c = ['git', 'commit-tree', self.tree.sha1] for p in self.parents: c.append('-p') c.append(p.sha1) if config.getbool("commit.gpgsign"): c.append('-S') sha1 = ( repository.run(c, env=self.env) .encoding(None) .raw_input(self.message) .output_one_line() ) return repository.get_commit(sha1)
def func(parser, options, args): """Generate a new commit for the current or given patch.""" # Catch illegal argument combinations. path_limiting = bool(args or options.update) if options.index and path_limiting: raise common.CmdException( 'Only full refresh is available with the --index option') if options.index and options.force: raise common.CmdException( 'You cannot --force a full refresh when using --index mode') if options.update and options.submodules: raise common.CmdException( '--submodules is meaningless when only updating modified files') if options.index and options.submodules: raise common.CmdException( '--submodules is meaningless when keeping the current index') # If submodules was not specified on the command line, infer a default # from configuration. if options.submodules is None: options.submodules = (config.getbool('stgit.refreshsubmodules')) stack = directory.repository.current_stack patch_name = get_patch(stack, options.patch) paths = list_files( stack, patch_name, args, options.index, options.update, options.submodules, ) # Make sure there are no conflicts in the files we want to # refresh. if stack.repository.default_index.conflicts() & paths: raise common.CmdException('Cannot refresh -- resolve conflicts first') # Make sure the index is clean before performing a full refresh if not options.index and not options.force: if not (stack.repository.default_index.is_clean(stack.head) or stack.repository.default_iw.worktree_clean()): raise common.CmdException( 'The index is dirty. Did you mean --index? ' 'To force a full refresh use --force.') # Commit index to temp patch, and absorb it into the target patch. retval, temp_name = make_temp_patch(stack, patch_name, paths, temp_index=path_limiting) if retval != utils.STGIT_SUCCESS: return retval def edit_fun(cd): orig_msg = cd.message cd, failed_diff = edit.auto_edit_patch( stack.repository, cd, msg=(None if options.message is None else options.message.encode('utf-8')), contains_diff=False, author=options.author, committer=lambda p: p, sign_str=options.sign_str) assert not failed_diff if options.edit: cd, failed_diff = edit.interactive_edit_patch( stack.repository, cd, edit_diff=False, diff_flags=[], replacement_diff=None, ) assert not failed_diff if not options.no_verify and (options.edit or cd.message != orig_msg): cd = common.run_commit_msg_hook(stack.repository, cd, options.edit) return cd return absorb(stack, patch_name, temp_name, edit_fun, annotate=options.annotate)
def protected(self): return config.getbool('branch.%s.stgit.protect' % self.name)
def func(parser, options, args): """Pull the changes from a remote repository""" repository = directory.repository iw = repository.default_iw stack = repository.get_stack() policy = config.get('branch.%s.stgit.pull-policy' % stack.name) or config.get('stgit.pull-policy') if policy not in ['pull', 'fetch-rebase', 'rebase']: raise GitConfigException('Unsupported pull-policy "%s"' % policy) remote_name = None if policy == 'rebase': # parent is local if len(args) == 1: parser.error( 'specifying a repository is meaningless for policy="%s"' % (policy, )) elif 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: remote_name = args[0] else: remote_name = stack.parent_remote if policy in ['pull', 'fetch-rebase'] and remote_name is None: parser.error( 'There is no tracking information for the current branch.\n' 'Please specify the remote repository to pull from.') if stack.protected: raise CmdException('This branch is protected. Pulls are not permitted') applied = stack.patchorder.applied retval = prepare_rebase(stack, 'pull') if retval: return retval # pull the remote changes if policy == 'pull': out.info('Pulling from "%s"' % remote_name) pull(repository, remote_name) elif policy == 'fetch-rebase': out.info('Fetching from "%s"' % remote_name) fetch(repository, remote_name) try: target = repository.rev_parse('FETCH_HEAD') except RepositoryException: out.error('Could not find the remote head to rebase onto - ' 'fix branch.%s.merge in .git/config' % stack.name) out.error('Pushing any patches back...') post_rebase(stack, applied, 'pull', check_merged=False) raise rebase(stack, iw, target) elif policy == 'rebase': value = config.get('branch.%s.stgit.parentbranch' % stack.name) if value: parent_commit = git_commit(value, repository) else: try: parent_commit = repository.rev_parse('heads/origin') except RepositoryException: raise CmdException('Cannot find a parent branch for "%s"' % stack.name) else: out.warn( 'No parent branch declared for stack "%s", defaulting to' '"heads/origin".' % stack.name, 'Consider setting "branch.%s.stgit.parentbranch" with ' '"git config".' % stack.name, ) rebase(stack, iw, parent_commit) if not options.nopush: post_rebase(stack, applied, 'pull', check_merged=options.merged) # maybe tidy up if config.getbool('stgit.keepoptimized'): repository.repack()
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 push_patch(self, pn, iw=None, allow_interactive=False, already_merged=False): """Attempt to push the named patch. If this results in conflicts, halts the transaction. If index+worktree are given, spill any conflicts to them.""" out.start('Pushing patch "%s"' % pn) orig_cd = self.patches[pn].data cd = orig_cd.set_committer(None) oldparent = cd.parent cd = cd.set_parent(self.top) if already_merged: # the resulting patch is empty tree = cd.parent.data.tree else: base = oldparent.data.tree ours = cd.parent.data.tree theirs = cd.tree tree, self.temp_index_tree = self.temp_index.merge( base, ours, theirs, self.temp_index_tree) s = '' merge_conflict = False if not tree: if iw is None: self._halt('%s does not apply cleanly' % pn) try: self._checkout(ours, iw, allow_bad_head=False) except CheckoutException: self._halt('Index/worktree dirty') try: interactive = allow_interactive and config.getbool( 'stgit.autoimerge') iw.merge(base, ours, theirs, interactive=interactive) tree = iw.index.write_tree() self._current_tree = tree s = 'modified' except MergeConflictException as e: tree = ours merge_conflict = True self._conflicts = e.conflicts s = 'conflict' except MergeException as e: self._halt(str(e)) cd = cd.set_tree(tree) if any( getattr(cd, a) != getattr(orig_cd, a) for a in ['parent', 'tree', 'author', 'message']): comm = self.stack.repository.commit(cd) if merge_conflict: # When we produce a conflict, we'll run the update() # function defined below _after_ having done the # checkout in run(). To make sure that we check out # the real stack top (as it will look after update() # has been run), set it hard here. self.head = comm else: comm = None s = 'unmodified' if already_merged: s = 'merged' elif not merge_conflict and cd.is_nochange(): s = 'empty' out.done(s) if merge_conflict: # We've just caused conflicts, so we must allow them in # the final checkout. self._allow_conflicts = lambda trans: True # Update the stack state if comm: self.patches[pn] = comm if pn in self.hidden: x = self.hidden else: x = self.unapplied del x[x.index(pn)] self.applied.append(pn) if merge_conflict: self._halt("%d merge conflict(s)" % len(self._conflicts))
def func(parser, options, args): """Generate a new commit for the current or given patch.""" # Catch illegal argument combinations. path_limiting = bool(args or options.update) if options.index and path_limiting: raise CmdException( 'Only full refresh is available with the --index option') if options.index and options.force: raise CmdException( 'You cannot --force a full refresh when using --index mode') if options.update and options.submodules: raise CmdException( '--submodules is meaningless when only updating modified files') if options.index and options.submodules: raise CmdException( '--submodules is meaningless when keeping the current index') # If submodules was not specified on the command line, infer a default # from configuration. if options.submodules is None: options.submodules = config.getbool('stgit.refreshsubmodules') stack = directory.repository.current_stack patch_name = get_patch(stack, options.patch) paths = list_files( stack, patch_name, args, options.index, options.update, options.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 options.index and not options.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.') # Update index and write tree tree = write_tree(stack, paths, temp_index=path_limiting) # Run pre-commit hook, if fails, abort refresh if not options.no_verify: pre_commit_hook = get_hook( stack.repository, 'pre-commit', extra_env={} if options.edit 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, temp_index=path_limiting) # 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 cd = auto_edit_patch( stack.repository, cd, msg=(None if options.message is None else options.message.encode( config.get('i18n.commitencoding'))), author=options.author, sign_str=options.sign_str, ) if options.edit: cd, failed_diff = interactive_edit_patch(stack.repository, cd, edit_diff=False, diff_flags=[]) assert not failed_diff if not options.no_verify and (options.edit or cd.message != orig_msg): cd = run_commit_msg_hook(stack.repository, cd, options.edit) # Refresh the committer information return cd.set_committer(None) return absorb(stack, patch_name, temp_name, edit_fun, annotate=options.annotate)
def func(parser, options, args): """Generate a new commit for the current or given patch.""" if options.spill: if len(args) > 0: # TODO: would be nice if path limiting could be used with spill. raise CmdException('Cannot use path limiting with --spill') for opt_name, opt_value in [ ('--index', options.index), ('--edit', options.edit), ('--update', options.update), ('--patch', options.patch), ('--force', options.force), ('--no-verify', options.no_verify), ('--sign', options.trailers), ('--ack', options.trailers), ('--review', options.trailers), ]: if opt_value: raise CmdException('Cannot combine --spill with %s' % opt_name) return __refresh_spill(annotate=options.annotate) else: # Catch illegal argument combinations. is_path_limiting = bool(args or options.update) if options.index and is_path_limiting: raise CmdException('Only full refresh is available with the --index option') if options.index and options.force: raise CmdException( 'You cannot --force a full refresh when using --index mode' ) if options.update and options.submodules: raise CmdException( '--submodules is meaningless when only updating modified files' ) if options.index and options.submodules: raise CmdException( '--submodules is meaningless when keeping the current index' ) # If submodules was not specified on the command line, infer a default # from configuration. if options.submodules is None: options.submodules = config.getbool('stgit.refreshsubmodules') return __refresh( args, force=options.force, target_patch=options.patch, message=options.message, author=options.author, trailers=options.trailers, annotate=options.annotate, use_temp_index=is_path_limiting, refresh_from_index=options.index, only_update_patchfiles=options.update, include_submodules=options.submodules, no_verify=options.no_verify, invoke_editor=options.edit, edit_diff=options.diff, diff_flags=options.diff_flags, )
def get_protected(self): return config.getbool(self.__branch_protect())
def func(parser, options, args): """Show the patch series""" if options.all and options.short: raise CmdException('combining --all and --short is meaningless') stack = directory.repository.get_stack(options.branch) if options.missing: cmp_stack = stack stack = directory.repository.get_stack(options.missing) if options.description is None: options.description = config.getbool('stgit.series.description') # current series patches applied = unapplied = hidden = () if options.applied or options.unapplied or options.hidden: if options.all: raise CmdException( '--all cannot be used with --applied/unapplied/hidden') if options.applied: applied = stack.patchorder.applied if options.unapplied: unapplied = stack.patchorder.unapplied if options.hidden: hidden = stack.patchorder.hidden elif options.all: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied hidden = stack.patchorder.hidden else: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if options.missing: cmp_patches = cmp_stack.patchorder.all else: cmp_patches = () # the filtering range covers the whole series if args: show_patches = parse_patches(args, applied + unapplied + hidden, len(applied)) else: show_patches = applied + unapplied + hidden # missing filtering show_patches = [p for p in show_patches if p not in cmp_patches] # filter the patches applied = [p for p in applied if p in show_patches] unapplied = [p for p in unapplied if p in show_patches] hidden = [p for p in hidden if p in show_patches] if options.short: nr = int(config.get('stgit.shortnr')) if len(applied) > nr: applied = applied[-(nr + 1):] n = len(unapplied) if n > nr: unapplied = unapplied[:nr] elif n < nr: hidden = hidden[:nr - n] patches = applied + unapplied + hidden if options.count: out.stdout(len(patches)) return if not patches: return if options.showbranch: branch_str = stack.name + ':' else: branch_str = '' max_len = len(branch_str) + max(len(p) for p in patches) if applied: for p in applied[:-1]: __print_patch( stack, p, branch_str, '+ ', max_len, options, config.get("stgit.color.applied"), ) __print_patch( stack, applied[-1], branch_str, '> ', max_len, options, config.get("stgit.color.current"), ) for p in unapplied: __print_patch( stack, p, branch_str, '- ', max_len, options, config.get("stgit.color.unapplied"), ) for p in hidden: __print_patch( stack, p, branch_str, '! ', max_len, options, config.get("stgit.color.hidden"), )
'-n', '--nopush', action='store_true', short='Do not push the patches back after rebasing', ), opt( '-m', '--merged', action='store_true', short='Check for patches merged upstream', ), opt( '--autostash', action='store_true', short='Stash changes before the rebase and apply them after', default=config.getbool('stgit.autostash'), long=''' Automatically create a temporary stash before the operation begins, and apply it after the operation ends. This means that you can run rebase on a dirty work-tree. However, use with care: the final stash application after a successful rebase might result in non-trivial conflicts. ''', ), ] directory = DirectoryGotoTopLevel() INTERACTIVE_APPLY_LINE = '# --- APPLY_LINE ---' INTERACTIVE_HELP_INSTRUCTIONS = """ # Commands: