def compat_log_entry(msg): """Write a new log entry. (Convenience function intended for use by code not yet converted to the new infrastructure.)""" repo = default_repo() try: stack = repo.get_stack(repo.current_branch_name) except libstack.StackException, e: out.warn(str(e), 'Could not write to stack log')
def delete(self, force=False, cleanup=False): """Deletes an stgit series """ if self.is_initialised(): patches = ( self.get_unapplied() + self.get_applied() + self.get_hidden() ) if not force and patches: raise StackException( 'Cannot %s: the series still contains patches' % ('delete', 'clean up')[cleanup]) for p in patches: self.get_patch(p).delete() # remove the trash directory if any if os.path.exists(self.__trash_dir): for fname in os.listdir(self.__trash_dir): os.remove(os.path.join(self.__trash_dir, fname)) os.rmdir(self.__trash_dir) # FIXME: find a way to get rid of those manual removals # (move functionality to StgitObject ?) if os.path.exists(self.__applied_file): os.remove(self.__applied_file) if os.path.exists(self.__unapplied_file): os.remove(self.__unapplied_file) if os.path.exists(self.__hidden_file): os.remove(self.__hidden_file) if os.path.exists(self._dir() + '/orig-base'): os.remove(self._dir() + '/orig-base') if not os.listdir(self.__patch_dir): os.rmdir(self.__patch_dir) else: out.warn('Patch directory %s is not empty' % self.__patch_dir) try: os.removedirs(self._dir()) except OSError: raise StackException('Series directory %s is not empty' % self._dir()) if not cleanup: try: git.delete_branch(self.get_name()) except git.GitException: out.warn('Could not delete branch "%s"' % self.get_name()) config.remove_section('branch.%s' % self.get_name()) config.remove_section('branch.%s.stgit' % self.get_name())
def get_log_mode(spec): if ':' not in spec: spec += ':' (log_mode, outfile) = spec.split(':', 1) all_log_modes = ['debug', 'profile'] if log_mode and log_mode not in all_log_modes: out.warn(('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.' % log_mode), 'Valid values are: %s' % ', '.join(all_log_modes)) if outfile: f = MessagePrinter(io.open(outfile, 'a', encoding='utf-8')) else: f = out return (log_mode, f)
def delete(self, keep_log=False): if os.path.isdir(self._dir()): for f in os.listdir(self._dir()): os.remove(os.path.join(self._dir(), f)) os.rmdir(self._dir()) else: out.warn('Patch directory "%s" does not exist' % self._dir()) try: # the reference might not exist if the repository was corrupted git.delete_ref(self.__top_ref) except git.GitException as e: out.warn(str(e)) if not keep_log and git.ref_exists(self.__log_ref): git.delete_ref(self.__log_ref)
def delete(self, keep_log = False): if os.path.isdir(self._dir()): for f in os.listdir(self._dir()): os.remove(os.path.join(self._dir(), f)) os.rmdir(self._dir()) else: out.warn('Patch directory "%s" does not exist' % self._dir()) try: # the reference might not exist if the repository was corrupted git.delete_ref(self.__top_ref) except git.GitException as e: out.warn(str(e)) if not keep_log and git.ref_exists(self.__log_ref): git.delete_ref(self.__log_ref)
def get_log_mode(spec): if ':' not in spec: spec += ':' (log_mode, outfile) = spec.split(':', 1) all_log_modes = ['debug', 'profile'] if log_mode and log_mode not in all_log_modes: out.warn(('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.' % log_mode), 'Valid values are: %s' % ', '.join(all_log_modes)) if outfile: f = MessagePrinter(open(outfile, 'a')) else: f = out return (log_mode, f)
def compat_log_entry(msg): """Write a new log entry. (Convenience function intended for use by code not yet converted to the new infrastructure.)""" try: repo = default_repo() stack = repo.get_stack(repo.current_branch_name) except (StackException, git.RepositoryException) as e: out.warn(str(e), 'Could not write to stack log') else: if repo.default_index.conflicts() and stack.patchorder.applied: log_entry(Fakestack(stack), msg) log_entry(stack, msg + ' (CONFLICT)') else: log_entry(stack, msg)
def compat_log_entry(msg): """Write a new log entry. (Convenience function intended for use by code not yet converted to the new infrastructure.)""" try: repo = default_repo() stack = repo.get_stack(repo.current_branch_name) except (StackException, RepositoryException) as e: out.warn(str(e), 'Could not write to stack log') else: if repo.default_index.conflicts() and stack.patchorder.applied: log_entry(Fakestack(stack), msg) log_entry(stack, msg + ' (CONFLICT)') else: log_entry(stack, msg)
def delete(self, force = False, cleanup = False): """Deletes an stgit series """ if self.is_initialised(): patches = self.get_unapplied() + self.get_applied() + \ self.get_hidden() if not force and patches: raise StackException( 'Cannot %s: the series still contains patches' % ('delete', 'clean up')[cleanup]) for p in patches: self.get_patch(p).delete() # remove the trash directory if any if os.path.exists(self.__trash_dir): for fname in os.listdir(self.__trash_dir): os.remove(os.path.join(self.__trash_dir, fname)) os.rmdir(self.__trash_dir) # FIXME: find a way to get rid of those manual removals # (move functionality to StgitObject ?) if os.path.exists(self.__applied_file): os.remove(self.__applied_file) if os.path.exists(self.__unapplied_file): os.remove(self.__unapplied_file) if os.path.exists(self.__hidden_file): os.remove(self.__hidden_file) if os.path.exists(self._dir()+'/orig-base'): os.remove(self._dir()+'/orig-base') if not os.listdir(self.__patch_dir): os.rmdir(self.__patch_dir) else: out.warn('Patch directory %s is not empty' % self.__patch_dir) try: os.removedirs(self._dir()) except OSError: raise StackException('Series directory %s is not empty' % self._dir()) if not cleanup: try: git.delete_branch(self.get_name()) except git.GitException: out.warn('Could not delete branch "%s"' % self.get_name()) config.remove_section('branch.%s' % self.get_name()) config.remove_section('branch.%s.stgit' % self.get_name())
def log_stack_state(stack, msg): """Write a new metadata entry for the stack.""" try: prev_state = get_stack_state(stack.repository, stack.state_ref) except KeyError: prev_state = None try: new_state = StackState.from_stack(prev_state.commit, stack) except LogException as e: out.warn(str(e), 'No log entry written.') else: if not prev_state or not new_state.same_state(prev_state): state_commit = new_state.commit_state(stack.repository, msg) stack.repository.refs.set(stack.state_ref, state_commit, msg)
def log_entry(stack, msg): """Write a new log entry for the stack.""" ref = log_ref(stack.name) try: last_log_commit = stack.repository.refs.get(ref) except KeyError: last_log_commit = None try: if last_log_commit: last_log = get_log_entry(stack.repository, ref, last_log_commit) else: last_log = None new_log = LogEntry.from_stack(last_log, stack, msg) except LogException, e: out.warn(str(e), 'No log entry written.') return
def log_entry(stack, msg): """Write a new log entry for the stack.""" ref = log_ref(stack.name) try: last_log_commit = stack.repository.refs.get(ref) except KeyError: last_log_commit = None try: if last_log_commit: last_log = get_log_entry(stack.repository, ref, last_log_commit) else: last_log = None new_log = LogEntry.from_stack(last_log, stack, msg) except LogException as e: out.warn(str(e), 'No log entry written.') return if last_log and same_state(last_log, new_log): return new_log.write_commit() stack.repository.refs.set(ref, new_log.commit, msg)
def func(parser, options, args): """Hide a range of patch in the series.""" stack = directory.repository.current_stack trans = transaction.StackTransaction(stack, 'hide') if not args: parser.error('No patches specified') patches = common.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.run()
def fetch_head(): """Return the git id for the tip of the parent branch as left by 'git fetch'. """ fetch_head = None stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r") for line in stream: # Only consider lines not tagged not-for-merge m = re.match('^([^\t]*)\t\t', line) if m: if fetch_head: raise GitException( 'StGit does not support multiple FETCH_HEAD') else: fetch_head = m.group(1) stream.close() if not fetch_head: out.warn('No for-merge remote head found in FETCH_HEAD') # here we are sure to have a single fetch_head return fetch_head
def fetch_head(): """Return the git id for the tip of the parent branch as left by 'git fetch'. """ fetch_head=None stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r") for line in stream: # Only consider lines not tagged not-for-merge m = re.match('^([^\t]*)\t\t', line) if m: if fetch_head: raise GitException('StGit does not support multiple FETCH_HEAD') else: fetch_head=m.group(1) stream.close() if not fetch_head: out.warn('No for-merge remote head found in FETCH_HEAD') # here we are sure to have a single fetch_head return fetch_head
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 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 info_msg(): out.warn( 'The new changes did not apply cleanly to %s.' % patch_name, 'They were saved in %s.' % temp_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 info_msg(): out.warn("The new changes did not apply cleanly to %s." % patch_name, "They were saved in %s." % temp_name)
def func(parser, options, args): """Publish the stack changes.""" out.warn( 'DEPRECATED: stg publish will be removed in a future version of StGit.' ) repository = directory.repository stack = repository.get_stack(options.branch) if not args: public_ref = get_public_ref(stack.name) elif len(args) == 1: public_ref = args[0] else: parser.error('incorrect number of arguments') if not public_ref.startswith('refs/heads/'): public_ref = 'refs/heads/' + public_ref # just clone the stack if the public ref does not exist if not repository.refs.exists(public_ref): if options.unpublished or options.last: raise CmdException('"%s" does not exist' % public_ref) repository.refs.set(public_ref, stack.head, 'publish') out.info('Created "%s"' % public_ref) return public_head = repository.refs.get(public_ref) public_tree = public_head.data.tree # find the last published patch if options.last: last = __get_last(stack, public_tree) if not last: raise CmdException( 'Unable to find the last published patch ' '(possibly rebased stack)' ) out.info('%s' % last) return # check for same tree (already up to date) if public_tree.sha1 == stack.head.data.tree.sha1: out.info('"%s" already up to date' % public_ref) return # check for unpublished patches if options.unpublished: published = set(__get_published(stack, public_tree)) for p in stack.patchorder.applied: if p not in published: out.stdout(p) return if options.overwrite: repository.refs.set(public_ref, stack.head, 'publish') out.info('Overwrote "%s"' % public_ref) return # check for rebased stack. In this case we emulate a merge with the stack # base by setting two parents. merge_bases = set(repository.get_merge_bases(public_head, stack.base)) if public_head in merge_bases: # fast-forward the public ref repository.refs.set(public_ref, stack.head, 'publish') out.info('Fast-forwarded "%s"' % public_ref) return if stack.base not in merge_bases: message = 'Merge %s into %s' % ( repository.describe(stack.base).strip(), utils.strip_prefix('refs/heads/', public_ref), ) public_head = __create_commit( repository, stack.head.data.tree, [public_head, stack.base], options, message, ) repository.refs.set(public_ref, public_head, 'publish') out.info('Merged the stack base into "%s"' % public_ref) return # check for new patches from the last publishing. This is done by checking # whether the public tree is the same as the bottom of the checked patch. # If older patches were modified, new patches cannot be detected. The new # patches and their metadata are pushed directly to the published head. for p in stack.patchorder.applied: pc = stack.patches.get(p).commit if public_tree.sha1 == pc.data.parent.data.tree.sha1: if pc.data.is_nochange(): out.info('Ignored new empty patch "%s"' % p) continue cd = pc.data.set_parent(public_head) public_head = repository.commit(cd) public_tree = public_head.data.tree out.info('Published new patch "%s"' % p) # create a new commit (only happens if no new patches are detected) if public_tree.sha1 != stack.head.data.tree.sha1: public_head = __create_commit(repository, stack.head.data.tree, [public_head], options) # update the public head repository.refs.set(public_ref, public_head, 'publish') out.info('Updated "%s"' % public_ref)
def func(parser, options, args): """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 = list(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 pn in patchorder.all: if stack.patches[pn] == c: applied.append(pn) patchify.extend(maybe_patchify) maybe_patchify = [] break else: maybe_patchify.append(c) c = c.data.parent if stack.base == c: # Reaching the original stack base can happen if, for example, the first # applied patch is amended. In this case, any commits descending from the # stack base should be patchified. patchify.extend(maybe_patchify) maybe_patchify = [] # Once the base commit has been found, we know that no existing patches # can be found be searching further. break applied.reverse() patchify.reverse() # Find patches unreachable behind a merge. if c != stack.base: 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(stack.patches[pn] == c for pn 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 patchify: out.start('Creating %d new patch%s' % (len(patchify), ['es', ''][len(patchify) == 1])) for c in patchify: pn = stack.patches.make_name(c.data.message_str) out.info('Creating patch %s from commit %s' % (pn, c.sha1)) stack.patches.new(pn, c, 'repair') applied.append(pn) out.done() # Figure out hidden hidden = [pn for pn in patches if pn in patchorder.hidden] # Write the applied/unapplied files. out.start('Checking patch appliedness') unapplied = [ pn for pn in patches if pn not in applied and pn not in hidden ] for pn in patchorder.all: if pn not in patches: out.info('%s is gone' % pn) for pn in applied: if pn not in patchorder.applied: out.info('%s is now applied' % pn) for pn in unapplied: if pn not in patchorder.unapplied: out.info('%s is now unapplied' % pn) for pn in hidden: if pn not in patchorder.hidden: out.info('%s is now hidden' % pn) out.done() orig_order = {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) try: trans.applied = applied trans.unapplied = sorted(unapplied, key=patchname_key) trans.hidden = sorted(hidden, key=patchname_key) except TransactionHalted: pass return trans.execute('repair')
def func(parser, options, args): """Export a range of patches. """ stack = directory.repository.get_stack(options.branch) if options.dir: dirname = options.dir else: dirname = 'patches-%s' % stack.name directory.cd_to_topdir() if not options.branch and git.local_changes(): out.warn('Local changes in the tree;' ' you might want to commit them first') if not options.stdout: if not os.path.isdir(dirname): os.makedirs(dirname) series = file(os.path.join(dirname, 'series'), 'w+') applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if len(args) != 0: patches = common.parse_patches(args, applied + unapplied, len(applied)) else: patches = applied num = len(patches) if num == 0: raise common.CmdException, 'No patches applied' zpadding = len(str(num)) if zpadding < 2: zpadding = 2 # get the template if options.template: tmpl = file(options.template).read() else: tmpl = templates.get_template('patchexport.tmpl') if not tmpl: tmpl = '' # note the base commit for this series if not options.stdout: base_commit = stack.patches.get(patches[0]).commit.sha1 print >> series, '# This series applies on GIT commit %s' % base_commit patch_no = 1; for p in patches: pname = p if options.patch: pname = '%s.patch' % pname elif options.extension: pname = '%s.%s' % (pname, options.extension) if options.numbered: pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname) pfile = os.path.join(dirname, pname) if not options.stdout: print >> series, pname # get the patch description patch = stack.patches.get(p) cd = patch.commit.data descr = cd.message.strip() descr_lines = descr.split('\n') short_descr = descr_lines[0].rstrip() long_descr = reduce(lambda x, y: x + '\n' + y, descr_lines[1:], '').strip() diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree, options.diff_flags) tmpl_dict = {'description': descr, 'shortdescr': short_descr, 'longdescr': long_descr, 'diffstat': gitlib.diffstat(diff).rstrip(), 'authname': cd.author.name, 'authemail': cd.author.email, 'authdate': cd.author.date.isoformat(), 'commname': cd.committer.name, 'commemail': cd.committer.email} for key in tmpl_dict: if not tmpl_dict[key]: tmpl_dict[key] = '' try: descr = tmpl % tmpl_dict except KeyError, err: raise common.CmdException, 'Unknown patch template variable: %s' \ % err except TypeError: raise common.CmdException, 'Only "%(name)s" variables are ' \ 'supported in the patch template'
def __create_patch(filename, message, author_name, author_email, author_date, diff, options): """Create a new patch on the stack """ if options.name: patch = options.name elif filename: patch = os.path.basename(filename) else: patch = '' if options.stripname: patch = __strip_patch_name(patch) if not patch: if options.ignore or options.replace: def unacceptable_name(name): return False else: unacceptable_name = crt_series.patch_exists patch = make_patch_name(message, unacceptable_name) else: # fix possible invalid characters in the patch name patch = re.sub(r'[^\w.]+', '-', patch).strip('-') if options.ignore and patch in crt_series.get_applied(): out.info('Ignoring already applied patch "%s"' % patch) return if options.replace and patch in crt_series.get_unapplied(): crt_series.delete_patch(patch, keep_log=True) # override the automatically parsed settings author = options.author(Person()) if author.name: author_name = author.name if author.email: author_email = author.email if author.date: author_date = text(author.date) sign_str = options.sign_str if not options.sign_str: sign_str = config.get('stgit.autosign') crt_series.new_patch( patch, message=message, can_edit=False, author_name=author_name, author_email=author_email, author_date=author_date, sign_str=sign_str, ) if not diff: out.warn('No diff found, creating empty patch') else: out.start('Importing patch "%s"' % patch) if options.base: base = git_id(crt_series, options.base) else: base = None try: git.apply_patch( diff=diff, base=base, reject=options.reject, strip=options.strip, ) except git.GitException: if not options.reject: crt_series.delete_patch(patch) raise crt_series.refresh_patch( edit=options.edit, show_patch=options.showdiff, author_date=author_date, backup=False, ) 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): """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 __create_patch(filename, message, author_name, author_email, author_date, diff, options): """Create a new patch on the stack """ stack = directory.repository.current_stack if options.name: name = options.name if not stack.patches.is_name_valid(name): raise CmdException('Invalid patch name: %s' % name) elif filename: name = os.path.basename(filename) else: name = '' if options.stripname: name = __strip_patch_name(name) if not name: if options.ignore or options.replace: def unacceptable_name(name): return False else: unacceptable_name = stack.patches.exists name = make_patch_name(message, unacceptable_name) else: # fix possible invalid characters in the patch name name = re.sub(r'[^\w.]+', '-', name).strip('-') assert stack.patches.is_name_valid(name) if options.ignore and name in stack.patchorder.applied: out.info('Ignoring already applied patch "%s"' % name) return out.start('Importing patch "%s"' % name) author = Person( author_name, author_email, Date.maybe(author_date), ) author = options.author(author) try: if not diff: out.warn('No diff found, creating empty patch') tree = stack.head.data.tree else: iw = stack.repository.default_iw iw.apply( diff, quiet=False, reject=options.reject, strip=options.strip ) tree = iw.index.write_tree() cd = CommitData( tree=tree, parents=[stack.head], author=author, message=message, ) cd = update_commit_data( cd, message=None, author=None, sign_str=options.sign_str, edit=options.edit, ) commit = stack.repository.commit(cd) trans = StackTransaction(stack, 'import: %s' % name) try: if options.replace and name in stack.patchorder.unapplied: trans.delete_patches(lambda pn: pn == name, quiet=True) trans.patches[name] = commit trans.applied.append(name) except TransactionHalted: pass trans.run() finally: out.done()
def func(parser, options, args): """Export a range of patches. """ stack = directory.repository.get_stack(options.branch) if options.dir: dirname = options.dir else: dirname = 'patches-%s' % stack.name directory.cd_to_topdir() if not options.branch and git.local_changes(): out.warn('Local changes in the tree;' ' you might want to commit them first') applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if len(args) != 0: patches = common.parse_patches(args, applied + unapplied, len(applied)) else: patches = applied num = len(patches) if num == 0: raise common.CmdException('No patches applied') zpadding = len(str(num)) if zpadding < 2: zpadding = 2 # get the template if options.template: with io.open(options.template, 'r') as f: tmpl = f.read() else: tmpl = templates.get_template('patchexport.tmpl') if not tmpl: tmpl = '' if not options.stdout: if not os.path.isdir(dirname): os.makedirs(dirname) series = io.open(os.path.join(dirname, 'series'), 'w') # note the base commit for this series base_commit = stack.base.sha1 print('# This series applies on GIT commit %s' % base_commit, file=series) for patch_no, p in enumerate(patches, 1): pname = p if options.patch: pname = '%s.patch' % pname elif options.extension: pname = '%s.%s' % (pname, options.extension) if options.numbered: pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname) pfile = os.path.join(dirname, pname) if not options.stdout: print(pname, file=series) # get the patch description patch = stack.patches.get(p) cd = patch.commit.data descr = cd.message.strip() descr_lines = descr.split('\n') short_descr = descr_lines[0].rstrip() long_descr = '\n'.join(descr_lines[1:]).strip() diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree, options.diff_flags) tmpl_dict = { 'description': descr, 'shortdescr': short_descr, 'longdescr': long_descr, 'diffstat': gitlib.diffstat(diff).rstrip(), 'authname': cd.author.name, 'authemail': cd.author.email, 'authdate': cd.author.date.isoformat(), 'commname': cd.committer.name, 'commemail': cd.committer.email } try: descr = templates.specialize_template(tmpl, tmpl_dict) except KeyError as err: raise common.CmdException('Unknown patch template variable: %s' % err) except TypeError: raise common.CmdException('Only "%(name)s" variables are ' 'supported in the patch template') if options.stdout: if hasattr(sys.stdout, 'buffer'): f = sys.stdout.buffer else: f = sys.stdout else: f = io.open(pfile, 'wb') if options.stdout and num > 1: f.write('\n'.join(['-' * 79, patch.name, '-' * 79, '']).encode('utf-8')) f.write(descr) f.write(diff) if not options.stdout: f.close() if not options.stdout: series.close()
def __create_patch(filename, message, patch_name, author_name, author_email, author_date, diff, options): """Create a new patch on the stack""" stack = directory.repository.current_stack if patch_name: name = patch_name elif options.name: name = options.name elif filename: name = os.path.basename(filename) else: name = '' if options.stripname: # Removing leading numbers and trailing extension name = re.sub( r'''^ (?:[0-9]+-)? # Optional leading patch number (.*?) # Patch name group (non-greedy) (?:\.(?:diff|patch))? # Optional .diff or .patch extension $ ''', r'\g<1>', name, flags=re.VERBOSE, ) need_unique = not (options.ignore or options.replace) if name: name = stack.patches.make_name(name, unique=need_unique, lower=False) else: name = stack.patches.make_name(message, unique=need_unique, lower=True) if options.ignore and name in stack.patchorder.applied: out.info('Ignoring already applied patch "%s"' % name) return out.start('Importing patch "%s"' % name) author = options.author( Person( author_name, author_email, Date.maybe(author_date), )) try: if not diff: out.warn('No diff found, creating empty patch') tree = stack.head.data.tree else: iw = stack.repository.default_iw iw.apply( diff, quiet=False, reject=options.reject, strip=options.strip, context_lines=options.context_lines, ) tree = iw.index.write_tree() cd = CommitData( tree=tree, parents=[stack.head], author=author, message=message, ) cd = update_commit_data( stack.repository, cd, message=None, author=None, trailers=options.trailers, edit=options.edit, ) commit = stack.repository.commit(cd) trans = StackTransaction(stack) try: if options.replace and name in stack.patchorder.unapplied: trans.delete_patches(lambda pn: pn == name, quiet=True) trans.patches[name] = commit trans.applied.append(name) except TransactionHalted: pass trans.execute('import: %s' % name) finally: out.done()