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 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 func(parser, options, args): """Merge the applied patches into the base of the current stack and remove them from the series while advancing the base """ if len(args) != 0: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal() applied = crt_series.get_applied() if not applied: raise CmdException, 'No patches applied' if crt_series.get_protected(): raise CmdException, 'This branch is protected. Commit is not permitted' crt_head = git.get_head() out.start('Committing %d patches' % len(applied)) crt_series.pop_patch(applied[0]) git.switch(crt_head) for patch in applied: crt_series.delete_patch(patch) out.done()
def rebase(target): if target == git.get_head(): out.info('Already at "%s", no need for rebasing.' % target) return out.start('Rebasing to "%s"' % target) git.reset(tree_id = git_id(target)) out.done()
def get_base(self): # Return the parent of the bottommost patch, if there is one. if os.path.isfile(self.__applied_file): bottommost = file(self.__applied_file).readline().strip() if bottommost: return self.get_patch(bottommost).get_bottom() # No bottommost patch, so just return HEAD return git.get_head()
def head_top_equal(self): """Return true if the head and the top are the same """ crt = self.get_current_patch() if not crt: # we don't care, no patches applied return True return git.get_head() == crt.get_top()
def clone(self, target_series): """Clones a series """ try: # allow cloning of branches not under StGIT control base = self.get_base() except BaseException: base = git.get_head() Series(target_series).init(create_at=base) new_series = Series(target_series) # generate an artificial description file new_series.set_description('clone of "%s"' % self.get_name()) # clone self's entire series as unapplied patches try: # allow cloning of branches not under StGIT control applied = self.get_applied() unapplied = self.get_unapplied() patches = applied + unapplied patches.reverse() except BaseException: patches = applied = unapplied = [] for p in patches: patch = self.get_patch(p) newpatch = new_series.new_patch( p, message=patch.get_description(), can_edit=False, unapplied=True, bottom=patch.get_bottom(), top=patch.get_top(), author_name=patch.get_authname(), author_email=patch.get_authemail(), author_date=patch.get_authdate(), ) if patch.get_log(): out.info('Setting log to %s' % patch.get_log()) newpatch.set_log(patch.get_log()) else: out.info('No log for %s' % p) # fast forward the cloned series to self's top new_series.forward_patches(applied) # Clone parent informations value = config.get('branch.%s.remote' % self.get_name()) if value: config.set('branch.%s.remote' % target_series, value) value = config.get('branch.%s.merge' % self.get_name()) if value: config.set('branch.%s.merge' % target_series, value) value = config.get('branch.%s.stgit.parentbranch' % self.get_name()) if value: config.set('branch.%s.stgit.parentbranch' % target_series, value)
def func(parser, options, args): """Assimilate a number of patches. """ def nothing_to_do(): out.info('No commits to assimilate') top_patch = crt_series.get_current_patch() if not top_patch: return nothing_to_do() victims = [] victim = git.get_commit(git.get_head()) while victim.get_id_hash() != top_patch.get_top(): victims.append(victim) parents = victim.get_parents() if not parents: raise CmdException, 'Commit %s has no parents, aborting' % victim elif len(parents) > 1: raise CmdException, 'Commit %s is a merge, aborting' % victim victim = git.get_commit(parents[0]) if not victims: return nothing_to_do() if crt_series.get_protected(): raise CmdException( 'This branch is protected. Modification is not permitted') patch2name = {} name2patch = {} def name_taken(name): return name in name2patch or crt_series.patch_exists(name) for victim in victims: patchname = make_patch_name(victim.get_log(), name_taken) patch2name[victim] = patchname name2patch[patchname] = victim victims.reverse() for victim in victims: out.info('Creating patch "%s" from commit %s' % (patch2name[victim], victim)) aname, amail, adate = name_email_date(victim.get_author()) cname, cmail, cdate = name_email_date(victim.get_committer()) crt_series.new_patch(patch2name[victim], can_edit=False, before_existing=False, commit=False, top=victim.get_id_hash(), bottom=victim.get_parent(), message=victim.get_log(), author_name=aname, author_email=amail, author_date=adate, committer_name=cname, committer_email=cmail)
def clone(self, target_series): """Clones a series """ try: # allow cloning of branches not under StGIT control base = self.get_base() except: base = git.get_head() Series(target_series).init(create_at=base) new_series = Series(target_series) # generate an artificial description file new_series.set_description('clone of "%s"' % self.get_name()) # clone self's entire series as unapplied patches try: # allow cloning of branches not under StGIT control applied = self.get_applied() unapplied = self.get_unapplied() patches = applied + unapplied patches.reverse() except: patches = applied = unapplied = [] for p in patches: patch = self.get_patch(p) newpatch = new_series.new_patch( p, message=patch.get_description(), can_edit=False, unapplied=True, bottom=patch.get_bottom(), top=patch.get_top(), author_name=patch.get_authname(), author_email=patch.get_authemail(), author_date=patch.get_authdate(), ) if patch.get_log(): out.info("Setting log to %s" % patch.get_log()) newpatch.set_log(patch.get_log()) else: out.info("No log for %s" % p) # fast forward the cloned series to self's top new_series.forward_patches(applied) # Clone parent informations value = config.get("branch.%s.remote" % self.get_name()) if value: config.set("branch.%s.remote" % target_series, value) value = config.get("branch.%s.merge" % self.get_name()) if value: config.set("branch.%s.merge" % target_series, value) value = config.get("branch.%s.stgit.parentbranch" % self.get_name()) if value: config.set("branch.%s.stgit.parentbranch" % target_series, value)
def func(parser, options, args): """Assimilate a number of patches. """ def nothing_to_do(): out.info('No commits to assimilate') top_patch = crt_series.get_current_patch() if not top_patch: return nothing_to_do() victims = [] victim = git.get_commit(git.get_head()) while victim.get_id_hash() != top_patch.get_top(): victims.append(victim) parents = victim.get_parents() if not parents: raise CmdException, 'Commit %s has no parents, aborting' % victim elif len(parents) > 1: raise CmdException, 'Commit %s is a merge, aborting' % victim victim = git.get_commit(parents[0]) if not victims: return nothing_to_do() if crt_series.get_protected(): raise CmdException( 'This branch is protected. Modification is not permitted') patch2name = {} name2patch = {} def name_taken(name): return name in name2patch or crt_series.patch_exists(name) for victim in victims: patchname = make_patch_name(victim.get_log(), name_taken) patch2name[victim] = patchname name2patch[patchname] = victim victims.reverse() for victim in victims: out.info('Creating patch "%s" from commit %s' % (patch2name[victim], victim)) aname, amail, adate = name_email_date(victim.get_author()) cname, cmail, cdate = name_email_date(victim.get_committer()) crt_series.new_patch( patch2name[victim], can_edit = False, before_existing = False, commit = False, top = victim.get_id_hash(), bottom = victim.get_parent(), message = victim.get_log(), author_name = aname, author_email = amail, author_date = adate, committer_name = cname, committer_email = cmail)
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 push_empty_patch(self, name): """Pushes an empty patch on the stack """ unapplied = self.get_unapplied() assert(name in unapplied) # patch = self.get_patch(name) head = git.get_head() append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) self.refresh_patch(bottom=head, cache_update=False, log='push(m)')
def push_empty_patch(self, name): """Pushes an empty patch on the stack """ unapplied = self.get_unapplied() assert(name in unapplied) # patch = self.get_patch(name) head = git.get_head() append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) self.refresh_patch(bottom = head, cache_update = False, log = 'push(m)')
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, ex: out.error('The merge failed during "push".', 'Revert the operation with "stg undo".')
def init(self, create_at=False, parent_remote=None, parent_branch=None): """Initialises the stgit series """ if self.is_initialised(): raise StackException, '%s already initialized' % self.get_name() for d in [self._dir(), self.__refs_dir]: if os.path.exists(d): raise StackException, '%s already exists' % d if (create_at != False): git.create_branch(self.get_name(), create_at) os.makedirs(self.__patch_dir) self.set_parent(parent_remote, parent_branch) self.create_empty_field('applied') self.create_empty_field('unapplied') os.makedirs(self.__refs_dir) self._set_field('orig-base', git.get_head()) config.set(self.format_version_key(), str(FORMAT_VERSION))
def forward_patches(self, names): """Try to fast-forward an array of patches. On return, patches in names[0:returned_value] have been pushed on the stack. Apply the rest with push_patch """ unapplied = self.get_unapplied() forwarded = 0 top = git.get_head() for name in names: assert(name in unapplied) patch = self.get_patch(name) head = top bottom = patch.get_bottom() top = patch.get_top() # top != bottom always since we have a commit for each patch if head == bottom: # reset the backup information. No logging since the # patch hasn't changed patch.set_top(top, backup=True) else: head_tree = git.get_commit(head).get_tree() bottom_tree = git.get_commit(bottom).get_tree() if head_tree == bottom_tree: # We must just reparent this patch and create a new commit # for it descr = patch.get_description() author_name = patch.get_authname() author_email = patch.get_authemail() author_date = patch.get_authdate() committer_name = patch.get_commname() committer_email = patch.get_commemail() top_tree = git.get_commit(top).get_tree() top = git.commit( message=descr, parents=[head], cache_update=False, tree_id=top_tree, allowempty=True, author_name=author_name, author_email=author_email, author_date=author_date, committer_name=committer_name, committer_email=committer_email, ) patch.set_top(top, backup=True) self.log_patch(patch, 'push(f)') else: top = head # stop the fast-forwarding, must do a real merge break forwarded += 1 unapplied.remove(name) if forwarded == 0: return 0 git.switch(top) append_strings(self.__applied_file, names[0:forwarded]) write_strings(self.__unapplied_file, unapplied) return forwarded
def forward_patches(self, names): """Try to fast-forward an array of patches. On return, patches in names[0:returned_value] have been pushed on the stack. Apply the rest with push_patch """ unapplied = self.get_unapplied() forwarded = 0 top = git.get_head() for name in names: assert(name in unapplied) patch = self.get_patch(name) head = top bottom = patch.get_bottom() top = patch.get_top() # top != bottom always since we have a commit for each patch if head == bottom: # reset the backup information. No logging since the # patch hasn't changed patch.set_top(top, backup = True) else: head_tree = git.get_commit(head).get_tree() bottom_tree = git.get_commit(bottom).get_tree() if head_tree == bottom_tree: # We must just reparent this patch and create a new commit # for it descr = patch.get_description() author_name = patch.get_authname() author_email = patch.get_authemail() author_date = patch.get_authdate() committer_name = patch.get_commname() committer_email = patch.get_commemail() top_tree = git.get_commit(top).get_tree() top = git.commit(message = descr, parents = [head], cache_update = False, tree_id = top_tree, allowempty = True, author_name = author_name, author_email = author_email, author_date = author_date, committer_name = committer_name, committer_email = committer_email) patch.set_top(top, backup = True) self.log_patch(patch, 'push(f)') else: top = head # stop the fast-forwarding, must do a real merge break forwarded+=1 unapplied.remove(name) if forwarded == 0: return 0 git.switch(top) append_strings(self.__applied_file, names[0:forwarded]) write_strings(self.__unapplied_file, unapplied) return forwarded
def new_patch(self, name, message = None, can_edit = True, unapplied = False, show_patch = False, top = None, bottom = None, commit = True, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None, before_existing = False, sign_str = None): """Creates a new patch, either pointing to an existing commit object, or by creating a new commit object. """ assert commit or (top and bottom) assert not before_existing or (top and bottom) assert not (commit and before_existing) assert (top and bottom) or (not top and not bottom) assert commit or (not top or (bottom == git.get_commit(top).get_parent())) if name is not None: self.__patch_name_valid(name) if self.patch_exists(name): raise StackException('Patch "%s" already exists' % name) # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction def sign(msg): return add_sign_line(msg, sign_str, committer_name or git.committer().name, committer_email or git.committer().email) if not message and can_edit: descr = edit_file( self, sign(''), 'Please enter the description for the patch above.', show_patch) else: descr = sign(message) head = git.get_head() if name is None: name = make_patch_name(descr, self.patch_exists) patch = self.get_patch(name) patch.create() patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) patch.set_authdate(author_date) patch.set_commname(committer_name) patch.set_commemail(committer_email) if before_existing: insert_string(self.__applied_file, patch.get_name()) elif unapplied: patches = [patch.get_name()] + self.get_unapplied() write_strings(self.__unapplied_file, patches) set_head = False else: append_string(self.__applied_file, patch.get_name()) set_head = True if commit: if top: top_commit = git.get_commit(top) else: bottom = head top_commit = git.get_commit(head) # create a commit for the patch (may be empty if top == bottom); # only commit on top of the current branch assert(unapplied or bottom == head) commit_id = git.commit(message = descr, parents = [bottom], cache_update = False, tree_id = top_commit.get_tree(), allowempty = True, set_head = set_head, author_name = author_name, author_email = author_email, author_date = author_date, committer_name = committer_name, committer_email = committer_email) # set the patch top to the new commit patch.set_top(commit_id) else: patch.set_top(top) self.log_patch(patch, 'new') return patch
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()
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, xrange(len(orig_patches)))) def patchname_cmp(p1, p2): i1 = orig_order.get(p1, len(orig_order)) i2 = orig_order.get(p2, len(orig_order)) return cmp((i1, p1), (i2, p2)) crt_series.set_applied(p.patch for p in applied) crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp)) crt_series.set_hidden(sorted(hidden_name_set, cmp = patchname_cmp)) 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): """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 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 post_rebase(applied, nopush, merged): # memorize that we rebased to here crt_series._set_field('orig-base', git.get_head()) # push the patches back if not nopush: push_patches(applied, merged)
def __branch_merge_patch(remote_series, pname): """Merge a patch from a remote branch into the current tree. """ patch = remote_series.get_patch(pname) git.merge(patch.get_bottom(), git.get_head(), patch.get_top())
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 __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: 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 new_patch(self, name, message=None, can_edit=True, unapplied=False, show_patch=False, top=None, bottom=None, commit=True, author_name=None, author_email=None, author_date=None, committer_name=None, committer_email=None, before_existing=False): """Creates a new patch """ if name != None: self.__patch_name_valid(name) if self.patch_exists(name): raise StackException, 'Patch "%s" already exists' % name if not message and can_edit: descr = edit_file( self, None, 'Please enter the description for the patch above.', show_patch) else: descr = message head = git.get_head() if name == None: name = make_patch_name(descr, self.patch_exists) patch = Patch(name, self.__patch_dir, self.__refs_dir) patch.create() if not bottom: bottom = head if not top: top = head patch.set_bottom(bottom) patch.set_top(top) patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) patch.set_authdate(author_date) patch.set_commname(committer_name) patch.set_commemail(committer_email) if before_existing: insert_string(self.__applied_file, patch.get_name()) # no need to commit anything as the object is already # present (mainly used by 'uncommit') commit = False elif unapplied: patches = [patch.get_name()] + self.get_unapplied() write_strings(self.__unapplied_file, patches) set_head = False else: append_string(self.__applied_file, patch.get_name()) set_head = True if commit: # create a commit for the patch (may be empty if top == bottom); # only commit on top of the current branch assert (unapplied or bottom == head) top_commit = git.get_commit(top) commit_id = git.commit(message=descr, parents=[bottom], cache_update=False, tree_id=top_commit.get_tree(), allowempty=True, set_head=set_head, author_name=author_name, author_email=author_email, author_date=author_date, committer_name=committer_name, committer_email=committer_email) # set the patch top to the new commit patch.set_top(commit_id) self.log_patch(patch, 'new') return patch
def new_patch( self, name, message=None, can_edit=True, unapplied=False, show_patch=False, top=None, bottom=None, commit=True, author_name=None, author_email=None, author_date=None, committer_name=None, committer_email=None, sign_str=None, ): """Creates a new patch, either pointing to an existing commit object, or by creating a new commit object. """ assert commit or (top and bottom) assert (top and bottom) or (not top and not bottom) assert commit or ( not top or bottom == git.get_commit(top).get_parent() ) if name is not None: self.__patch_name_valid(name) if self.patch_exists(name): raise StackException('Patch "%s" already exists' % name) # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction def sign(msg): return add_sign_line(msg, sign_str, committer_name or git.committer().name, committer_email or git.committer().email) if not message and can_edit: descr = edit_file( self, sign(''), 'Please enter the description for the patch above.', show_patch) else: descr = sign(message) head = git.get_head() if name is None: name = make_patch_name(descr, self.patch_exists) patch = self.get_patch(name) patch.create() patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) patch.set_authdate(author_date) patch.set_commname(committer_name) patch.set_commemail(committer_email) if unapplied: patches = [patch.get_name()] + self.get_unapplied() write_strings(self.__unapplied_file, patches) set_head = False else: append_string(self.__applied_file, patch.get_name()) set_head = True if commit: if top: top_commit = git.get_commit(top) else: bottom = head top_commit = git.get_commit(head) # create a commit for the patch (may be empty if top == bottom); # only commit on top of the current branch assert(unapplied or bottom == head) commit_id = git.commit( message=descr, parents=[bottom], cache_update=False, tree_id=top_commit.get_tree(), allowempty=True, set_head=set_head, author_name=author_name, author_email=author_email, author_date=author_date, committer_name=committer_name, committer_email=committer_email, ) # set the patch top to the new commit patch.set_top(commit_id) else: patch.set_top(top) self.log_patch(patch, 'new') return patch
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()