def pop_patch(self, name, keep=False): """Pops the top patch from the stack """ applied = self.get_applied() applied.reverse() assert(name in applied) patch = self.get_patch(name) if git.get_head_file() == self.get_name(): if keep and not git.apply_diff(git.get_head(), patch.get_bottom(), check_index=False): raise StackException( 'Failed to pop patches while preserving the local changes') git.switch(patch.get_bottom(), keep) else: git.set_branch(self.get_name(), patch.get_bottom()) # save the new applied list idx = applied.index(name) + 1 popped = applied[:idx] popped.reverse() unapplied = popped + self.get_unapplied() write_strings(self.__unapplied_file, unapplied) del applied[:idx] applied.reverse() write_strings(self.__applied_file, applied)
def push_patch(self, name, empty=False): """Pushes a patch on the stack """ unapplied = self.get_unapplied() assert (name in unapplied) patch = Patch(name, self.__patch_dir, self.__refs_dir) head = git.get_head() bottom = patch.get_bottom() top = patch.get_top() ex = None modified = False # top != bottom always since we have a commit for each patch if empty: # just make an empty patch (top = bottom = HEAD). This # option is useful to allow undoing already merged # patches. The top is updated by refresh_patch since we # need an empty commit patch.set_bottom(head, backup=True) patch.set_top(head, backup=True) modified = True elif head == bottom: # reset the backup information. No need for logging patch.set_bottom(bottom, backup=True) patch.set_top(top, backup=True) git.switch(top) else: # new patch needs to be refreshed. # The current patch is empty after merge. patch.set_bottom(head, backup=True) patch.set_top(head, backup=True) # Try the fast applying first. If this fails, fall back to the # three-way merge if not git.apply_diff(bottom, top): # if git.apply_diff() fails, the patch requires a diff3 # merge and can be reported as modified modified = True # merge can fail but the patch needs to be pushed try: git.merge(bottom, head, top, recursive=True) except git.GitException, ex: out.error( 'The merge failed during "push".', 'Use "refresh" after fixing the conflicts or' ' revert the operation with "push --undo".')
def merged_patches(self, names): """Test which patches were merged upstream by reverse-applying them in reverse order. The function returns the list of patches detected to have been applied. The state of the tree is restored to the original one """ patches = [self.get_patch(name) for name in names] patches.reverse() merged = [] for p in patches: if git.apply_diff(p.get_top(), p.get_bottom()): merged.append(p.get_name()) merged.reverse() git.reset() return merged
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 push_patch(self, name): """Pushes a patch on the stack """ unapplied = self.get_unapplied() assert(name in unapplied) patch = self.get_patch(name) head = git.get_head() bottom = patch.get_bottom() top = patch.get_top() # top != bottom always since we have a commit for each patch if head == bottom: # A fast-forward push. Just reset the backup # information. No need for logging patch.set_top(top, backup=True) git.switch(top) append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) return False # Need to create a new commit an merge in the old patch ex = None modified = False # Try the fast applying first. If this fails, fall back to the # three-way merge if not git.apply_diff(bottom, top): # if git.apply_diff() fails, the patch requires a diff3 # merge and can be reported as modified modified = True # merge can fail but the patch needs to be pushed try: git.merge_recursive(bottom, head, top) except git.GitException: out.error('The merge failed during "push".', 'Revert the operation with "stg undo".') append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) if not ex: # if the merge was OK and no conflicts, just refresh the patch # The GIT cache was already updated by the merge operation if modified: log = 'push(m)' else: log = 'push' self.refresh_patch(bottom=head, cache_update=False, log=log) else: # we make the patch empty, with the merged state in the # working tree. self.refresh_patch( bottom=head, cache_update=False, empty=True, log='push(c)', ) raise StackException(str(ex)) return modified
def func(parser, options, args): """Import a commit object as a new patch """ if len(args) != 1: parser.error('incorrect number of arguments') if not options.unapplied: check_local_changes() check_conflicts() check_head_top_equal() commit_str = args[0] commit_id = git_id(commit_str) commit = git.Commit(commit_id) if options.fold or options.update: if not crt_series.get_current(): raise CmdException, 'No patches applied' else: patch_branch = commit_str.split('@') if options.name: patchname = options.name elif len(patch_branch) == 2: patchname = patch_branch[0] else: patchname = None if options.parent: parent = git_id(options.parent) else: parent = commit.get_parent() if not options.reverse: 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): git.merge(bottom, git.get_head(), top, recursive=True) out.done() elif options.update: rev1 = git_id('//bottom') rev2 = git_id('//top') 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.expose: message += '(imported from commit %s)\n' % commit.get_id_hash() author_name, author_email, author_date = \ name_email_date(commit.get_author()) 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 (refpatchname, refbranchname, refpatchid) = parse_rev(commit_str) if refpatchname and not refpatchid and \ (not refpatchid or refpatchid == 'top'): # FIXME: should also support picking //top.old 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() print_crt_patch()