def log_patch(self, patch, message, notes = None): """Generate a log commit for a patch """ top = git.get_commit(patch.get_top()) old_log = patch.get_log() if message is None: # replace the current log entry if not old_log: raise StackException('No log entry to annotate for patch "%s"' % patch.get_name()) replace = True log_commit = git.get_commit(old_log) msg = log_commit.get_log().split('\n')[0] log_parent = log_commit.get_parent() if log_parent: parents = [log_parent] else: parents = [] else: # generate a new log entry replace = False msg = '%s\t%s' % (message, top.get_id_hash()) if old_log: parents = [old_log] else: parents = [] if notes: msg += '\n\n' + notes log = git.commit(message = msg, parents = parents, cache_update = False, tree_id = top.get_tree(), allowempty = True) patch.set_log(log)
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 empty_patch(self, name): """Returns True if the patch is empty """ self.__patch_name_valid(name) patch = self.get_patch(name) bottom = patch.get_bottom() top = patch.get_top() if bottom == top: return True elif git.get_commit(top).get_tree() == git.get_commit(bottom).get_tree(): return True return False
def show_log(log, options): """List the patch changelog """ commit = git.get_commit(log) diff_str = '' while commit: log = commit.get_log().split('\n') cmd_rev = log[0].split() if len(cmd_rev) >= 2: cmd = cmd_rev[0] rev = cmd_rev[1] elif len(cmd_rev) == 1: cmd = cmd_rev[0] rev = '' else: cmd = rev = '' if options.patch: if cmd in ['refresh', 'undo', 'sync']: diff_str = '%s%s\n' % (diff_str, git.pretty_commit(commit.get_id_hash())) else: if len(log) >= 3: notes = log[2] else: notes = '' author_name, author_email, author_date = \ name_email_date(commit.get_author()) secs, tz = author_date.split() date = '%s %s' % (time.ctime(int(secs)), tz) if options.full: out.stdout('%-7s %-40s %s' % (cmd[:7], rev[:40], date)) else: out.stdout('%-8s [%-7s] %-28s %s' % \ (rev[:8], cmd[:7], notes[:28], date)) parent = commit.get_parent() if parent: commit = git.get_commit(parent) else: commit = None if options.patch and diff_str: pager(diff_str.rstrip())
def set_top(self, value, backup=False): if backup: curr_top = self.get_top() self._set_field('top.old', curr_top) self._set_field( 'bottom.old', git.get_commit(curr_top).get_parent(), ) self._update_top_ref(value)
def log_patch(self, patch, message, notes=None): """Generate a log commit for a patch """ top = git.get_commit(patch.get_top()) old_log = patch.get_log() if message is None: # replace the current log entry if not old_log: raise StackException, \ 'No log entry to annotate for patch "%s"' \ % patch.get_name() replace = True log_commit = git.get_commit(old_log) msg = log_commit.get_log().split('\n')[0] log_parent = log_commit.get_parent() if log_parent: parents = [log_parent] else: parents = [] else: # generate a new log entry replace = False msg = '%s\t%s' % (message, top.get_id_hash()) if old_log: parents = [old_log] else: parents = [] if notes: msg += '\n\n' + notes log = git.commit(message=msg, parents=parents, cache_update=False, tree_id=top.get_tree(), allowempty=True) patch.set_log(log)
def log_patch(self, patch, message): """Generate a log commit for a patch """ top = git.get_commit(patch.get_top()) old_log = patch.get_log() # generate a new log entry msg = '%s\t%s' % (message, top.get_id_hash()) if old_log: parents = [old_log] else: parents = [] log = git.commit( message=msg, parents=parents, cache_update=False, tree_id=top.get_tree(), allowempty=True, ) patch.set_log(log)
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 refresh_patch(self, files = None, message = None, edit = False, empty = False, show_patch = False, cache_update = True, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None, backup = True, sign_str = None, log = 'refresh', notes = None, bottom = None): """Generates a new commit for the topmost patch """ patch = self.get_current_patch() if not patch: raise StackException('No patches applied') descr = patch.get_description() if not (message or descr): edit = True descr = '' elif message: descr = message # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction if not message and edit: descr = edit_file(self, descr.rstrip(), \ 'Please edit the description for patch "%s" ' \ 'above.' % patch.get_name(), show_patch) if not author_name: author_name = patch.get_authname() if not author_email: author_email = patch.get_authemail() if not committer_name: committer_name = patch.get_commname() if not committer_email: committer_email = patch.get_commemail() descr = add_sign_line(descr, sign_str, committer_name, committer_email) if not bottom: bottom = patch.get_bottom() if empty: tree_id = git.get_commit(bottom).get_tree() else: tree_id = None commit_id = git.commit(files = files, message = descr, parents = [bottom], cache_update = cache_update, tree_id = tree_id, set_head = True, 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(commit_id, backup = backup) 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 log: self.log_patch(patch, log, notes) return commit_id
def set_top(self, value, backup = False): if backup: curr_top = self.get_top() self._set_field('top.old', curr_top) self._set_field('bottom.old', git.get_commit(curr_top).get_parent()) self.__update_top_ref(value)
def get_bottom(self): return git.get_commit(self.get_top()).get_parent()
def __update_top_ref(self, ref): git.set_ref(self.__top_ref, ref) self._set_field('top', ref) self._set_field('bottom', git.get_commit(ref).get_parent())
def __update_top_ref(self, ref): git.set_ref(self.__top_ref, ref) self._set_field("top", ref) self._set_field("bottom", git.get_commit(ref).get_parent())
def __get_commit(self): if not self.__commit: self.__commit = git.get_commit(self.id) return self.__commit
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, 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 refresh_patch( self, files=None, message=None, edit=False, empty=False, show_patch=False, cache_update=True, author_name=None, author_email=None, author_date=None, committer_name=None, committer_email=None, backup=True, sign_str=None, log='refresh', notes=None, bottom=None ): """Generates a new commit for the topmost patch """ patch = self.get_current_patch() if not patch: raise StackException('No patches applied') descr = patch.get_description() if not (message or descr): edit = True descr = '' elif message: descr = message # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction if not message and edit: descr = edit_file( self, descr.rstrip(), 'Please edit the description for patch "%s" above.' % ( patch.get_name(), show_patch, ) ) if not author_name: author_name = patch.get_authname() if not author_email: author_email = patch.get_authemail() if not committer_name: committer_name = patch.get_commname() if not committer_email: committer_email = patch.get_commemail() descr = add_sign_line(descr, sign_str, committer_name, committer_email) if not bottom: bottom = patch.get_bottom() if empty: tree_id = git.get_commit(bottom).get_tree() else: tree_id = None commit_id = git.commit( files=files, message=descr, parents=[bottom], cache_update=cache_update, tree_id=tree_id, set_head=True, 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(commit_id, backup=backup) 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 log: self.log_patch(patch, log, notes) return commit_id
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 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.""" 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 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