Create a log branch (called <branchname>.stgit) for each StGit branch, and write to it whenever the stack is modified. Commands using the new infrastructure write to the log when they commit a transaction. Commands using the old infrastructure get a log entry write written for them when they exit, unless they explicitly ask for this not to happen. As of yet, nothing can be done with this log. Signed-off-by: Karl Hasselström <kha@xxxxxxxxxxx> --- stgit/commands/branch.py | 20 +++++-- stgit/commands/common.py | 8 ++- stgit/commands/diff.py | 2 - stgit/commands/export.py | 2 - stgit/commands/files.py | 2 - stgit/commands/id.py | 2 - stgit/commands/log.py | 2 - stgit/commands/mail.py | 2 - stgit/commands/patches.py | 2 - stgit/commands/series.py | 2 - stgit/commands/show.py | 2 - stgit/commands/status.py | 3 + stgit/commands/top.py | 2 - stgit/lib/log.py | 131 +++++++++++++++++++++++++++++++++++++++++++++ stgit/lib/transaction.py | 3 + stgit/main.py | 2 + 16 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 stgit/lib/log.py diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py index 50684bb..241b1ef 100644 --- a/stgit/commands/branch.py +++ b/stgit/commands/branch.py @@ -25,7 +25,7 @@ from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import stack, git, basedir - +from stgit.lib import log help = 'manage patch stacks' usage = """%prog [options] branch-name [commit-id] @@ -40,7 +40,7 @@ When displaying the branches, the names can be prefixed with If not given any options, switch to the named branch.""" -directory = DirectoryGotoToplevel() +directory = DirectoryGotoToplevel(log = False) options = [make_option('-c', '--create', help = 'create a new development branch', action = 'store_true'), @@ -161,6 +161,7 @@ def func(parser, options, args): parent_branch = parentbranch) out.info('Branch "%s" created' % args[0]) + log.compat_log_entry('stg branch --create') return elif options.clone: @@ -181,6 +182,8 @@ def func(parser, options, args): crt_series.clone(clone) out.done() + log.copy_log(log.default_repo(), crt_series.get_name(), clone, + 'stg branch --clone') return elif options.delete: @@ -188,6 +191,7 @@ def func(parser, options, args): if len(args) != 1: parser.error('incorrect number of arguments') __delete_branch(args[0], options.force) + log.delete_log(log.default_repo(), args[0]) return elif options.list: @@ -195,13 +199,16 @@ def func(parser, options, args): if len(args) != 0: parser.error('incorrect number of arguments') - branches = git.get_heads() - branches.sort() + branches = set(git.get_heads()) + for br in set(branches): + m = re.match(r'^(.*)\.stgit$', br) + if m and m.group(1) in branches: + branches.remove(br) if branches: out.info('Available branches:') max_len = max([len(i) for i in branches]) - for i in branches: + for i in sorted(branches): __print_branch(i, max_len) else: out.info('No branches') @@ -238,7 +245,8 @@ def func(parser, options, args): stack.Series(args[0]).rename(args[1]) out.info('Renamed branch "%s" to "%s"' % (args[0], args[1])) - + log.rename_log(log.default_repo(), args[0], args[1], + 'stg branch --rename') return elif options.unprotect: diff --git a/stgit/commands/common.py b/stgit/commands/common.py index d6df813..1a45d9e 100644 --- a/stgit/commands/common.py +++ b/stgit/commands/common.py @@ -28,6 +28,7 @@ from stgit.run import * from stgit import stack, git, basedir from stgit.config import config, file_extensions from stgit.lib import stack as libstack +from stgit.lib import log # Command exception class class CmdException(StgException): @@ -478,8 +479,9 @@ class DirectoryException(StgException): pass class _Directory(object): - def __init__(self, needs_current_series = True): + def __init__(self, needs_current_series = True, log = True): self.needs_current_series = needs_current_series + self.log = log @readonly_constant_property def git_dir(self): try: @@ -512,6 +514,9 @@ class _Directory(object): ).output_one_line()] def cd_to_topdir(self): os.chdir(self.__topdir_path) + def write_log(self, msg): + if self.log: + log.compat_log_entry(msg) class DirectoryAnywhere(_Directory): def setup(self): @@ -536,6 +541,7 @@ class DirectoryHasRepositoryLib(_Directory): """For commands that use the new infrastructure in stgit.lib.*.""" def __init__(self): self.needs_current_series = False + self.log = False # stgit.lib.transaction handles logging def setup(self): # This will throw an exception if we don't have a repository. self.repository = libstack.Repository.default() diff --git a/stgit/commands/diff.py b/stgit/commands/diff.py index fd6be34..8966642 100644 --- a/stgit/commands/diff.py +++ b/stgit/commands/diff.py @@ -42,7 +42,7 @@ rev = '([patch][//[bottom | top]]) | <tree-ish> | base' If neither bottom nor top are given but a '//' is present, the command shows the specified patch (defaulting to the current one).""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-r', '--range', metavar = 'rev1[..[rev2]]', dest = 'revs', help = 'show the diff between revisions'), diff --git a/stgit/commands/export.py b/stgit/commands/export.py index 50f6f67..552fd44 100644 --- a/stgit/commands/export.py +++ b/stgit/commands/export.py @@ -49,7 +49,7 @@ file: %(commemail)s - committer's e-mail """ -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-d', '--dir', help = 'export patches to DIR instead of the default'), make_option('-p', '--patch', diff --git a/stgit/commands/files.py b/stgit/commands/files.py index b43b12f..7844f8d 100644 --- a/stgit/commands/files.py +++ b/stgit/commands/files.py @@ -34,7 +34,7 @@ given patch. Note that this command doesn't show the files modified in the working tree and not yet included in the patch by a 'refresh' command. Use the 'diff' or 'status' commands for these files.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-s', '--stat', help = 'show the diff stat', action = 'store_true'), diff --git a/stgit/commands/id.py b/stgit/commands/id.py index 94b0229..5bb1ad2 100644 --- a/stgit/commands/id.py +++ b/stgit/commands/id.py @@ -33,7 +33,7 @@ the standard GIT id's like heads and tags, this command also accepts 'top' or 'bottom' are passed and <patch> is a valid patch name, 'top' will be used by default.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-b', '--branch', help = 'use BRANCH instead of the default one')] diff --git a/stgit/commands/log.py b/stgit/commands/log.py index 52d55a5..13e0baa 100644 --- a/stgit/commands/log.py +++ b/stgit/commands/log.py @@ -44,7 +44,7 @@ represent the changes to the entire base of the current patch. Conflicts reset the patch content and a subsequent 'refresh' will show the entire patch.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-b', '--branch', help = 'use BRANCH instead of the default one'), make_option('-p', '--patch', diff --git a/stgit/commands/mail.py b/stgit/commands/mail.py index b4d4e18..4027170 100644 --- a/stgit/commands/mail.py +++ b/stgit/commands/mail.py @@ -90,7 +90,7 @@ the following: %(prefix)s - 'prefix ' string passed on the command line %(shortdescr)s - the first line of the patch description""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-a', '--all', help = 'e-mail all the applied patches', action = 'store_true'), diff --git a/stgit/commands/patches.py b/stgit/commands/patches.py index 140699d..c95c40f 100644 --- a/stgit/commands/patches.py +++ b/stgit/commands/patches.py @@ -33,7 +33,7 @@ it shows the patches affected by the local tree modifications. The '--diff' option also lists the patch log and the diff for the given files.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-d', '--diff', help = 'show the diff for the given files', action = 'store_true'), diff --git a/stgit/commands/series.py b/stgit/commands/series.py index e3467cc..8d19dd7 100644 --- a/stgit/commands/series.py +++ b/stgit/commands/series.py @@ -34,7 +34,7 @@ range. The applied patches are prefixed with a '+', the unapplied ones with a '-' and the hidden ones with a '!'. The current patch is prefixed with a '>'. Empty patches are prefixed with a '0'.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-b', '--branch', help = 'use BRANCH instead of the default one'), make_option('-a', '--all', diff --git a/stgit/commands/show.py b/stgit/commands/show.py index b77a9c8..dd2a3a3 100644 --- a/stgit/commands/show.py +++ b/stgit/commands/show.py @@ -30,7 +30,7 @@ Show the commit log and the diff corresponding to the given patches. The output is similar to that generated by the 'git show' command.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-b', '--branch', help = 'use BRANCH instead of the default one'), make_option('-a', '--applied', diff --git a/stgit/commands/status.py b/stgit/commands/status.py index 6da4516..37b79c0 100644 --- a/stgit/commands/status.py +++ b/stgit/commands/status.py @@ -40,7 +40,7 @@ under revision control. The files are prefixed as follows: A 'refresh' command clears the status of the modified, new and deleted files.""" -directory = DirectoryHasRepository(needs_current_series = False) +directory = DirectoryHasRepository(needs_current_series = False, log = False) options = [make_option('-m', '--modified', help = 'show modified files only', action = 'store_true'), @@ -106,6 +106,7 @@ def func(parser, options, args): directory.cd_to_topdir() if options.reset: + directory.log = True if args: conflicts = git.get_conflicts() git.resolved(fn for fn in args if fn in conflicts) diff --git a/stgit/commands/top.py b/stgit/commands/top.py index e7cb275..96d680e 100644 --- a/stgit/commands/top.py +++ b/stgit/commands/top.py @@ -30,7 +30,7 @@ usage = """%prog [options] Print the name of the current (topmost) patch.""" -directory = DirectoryHasRepository() +directory = DirectoryHasRepository(log = False) options = [make_option('-b', '--branch', help = 'use BRANCH instead of the default one')] diff --git a/stgit/lib/log.py b/stgit/lib/log.py new file mode 100644 index 0000000..fac050f --- /dev/null +++ b/stgit/lib/log.py @@ -0,0 +1,131 @@ +from stgit.lib import git, stack +from stgit import exception +from stgit.out import out + +class LogException(exception.StgException): + pass + +_current_version = 1 + +def commit_info(cd): + return 'Author: %s\n\n%s' % (cd.author, cd.message) + +def patch_tree(repository, cd): + return repository.commit(git.Treedata({ + 'a': cd.parent.data.tree, 'b': cd.tree, + 'info': repository.commit(git.Blobdata(commit_info(cd))) })) + +def order_blob(repository, stack, kind): + return repository.commit(git.Blobdata(''.join( + '%s %s\n' % (stack.patches.get(pn).commit.sha1, pn) + for pn in getattr(stack.patchorder, kind)))) + +def log_entry_tree(repository, stack): + patches = repository.commit( + git.Treedata(dict((pn, patch_tree(repository, + stack.patches.get(pn).commit.data)) + for pn in stack.patchorder.all))) + return repository.commit(git.Treedata({ + 'version': repository.commit(git.Blobdata( + str(_current_version))), + 'head': repository.commit(git.Blobdata(str(stack.head.sha1))), + 'patches': patches, + 'applied': order_blob(repository, stack, 'applied'), + 'unapplied': order_blob(repository, stack, 'unapplied'), + })) + +def log_ref(branch): + return 'refs/heads/%s.stgit' % branch + +def log_entry(stack, msg): + """Write a new log entry for the stack.""" + ref = log_ref(stack.name) + try: + last_log = [FullLog(stack.repository, ref, + stack.repository.refs.get(ref))] + except KeyError: + last_log = [] + except LogException, e: + out.warn(str(e), 'No log entry written.') + return + log_tree = log_entry_tree(stack.repository, stack) + stack_log = stack.repository.commit( + git.Commitdata(tree = log_tree, message = msg, + parents = [ll.stack_log for ll in last_log])) + full_log = stack.repository.commit( + git.Commitdata(tree = log_tree, message = msg, + parents = ([stack_log] + [stack.head] + + [ll.full_log for ll in last_log]))) + stack.repository.refs.set(ref, full_log, msg) + +def compat_log_entry(msg): + repo = default_repo() + stack = repo.get_stack(repo.current_branch) + log_entry(stack, msg) + +class Log(object): + """Read a log entry.""" + def __init__(self, repo, ref, commit): + self.commit = commit + mode, vblob = self.commit.data.tree.data.entries.get( + 'version', (None, None)) + if not isinstance(vblob, git.Blob): + raise LogException('%s does not contain a valid log' % ref) + try: + version = int(vblob.data.str) + except ValueError: + raise LogException('"%s": invalid version number' % vblob.data.str) + if version < _current_version: + raise LogException( + '%s contains a stack log older than version %d;' + ' please delete it' % (ref, _current_version)) + elif version > _current_version: + raise LogException( + 'Log contains a stack log newer than version %d;' + ' this version of StGit cannot read it' % _current_version) + + # TODO: read the rest of the log lazily. + + def pl(name): + patches = [x.split() for x in + self.commit.data.tree.data.entries[name][1] + .data.str.strip().split('\n') if x] + # TODO: handle case where we don't have the commit object. + return ([pn for sha1, pn in patches], + dict((pn, repo.get_commit(sha1)) for sha1, pn in patches)) + self.patches = {} + self.applied, patches = pl('applied') + self.patches.update(patches) + self.unapplied, patches = pl('unapplied') + self.patches.update(patches) + self.head = repo.get_commit( + self.commit.data.tree.data.entries['head'][1].data.str) + if self.applied: + self.base = self.patches[self.applied[0]].data.parent + else: + self.base = self.head + +class FullLog(Log): + full_log = property(lambda self: self.commit) + stack_log = property(lambda self: self.commit.data.parents[0]) + +def delete_log(repo, branch): + ref = log_ref(branch) + if repo.refs.exists(ref): + repo.refs.delete(ref) + +def rename_log(repo, old_branch, new_branch, msg): + old_ref = log_ref(old_branch) + new_ref = log_ref(new_branch) + if repo.refs.exists(old_ref): + repo.refs.set(new_ref, repo.refs.get(old_ref), msg) + repo.refs.delete(old_ref) + +def copy_log(repo, src_branch, dst_branch, msg): + src_ref = log_ref(src_branch) + dst_ref = log_ref(dst_branch) + if repo.refs.exists(src_ref): + repo.refs.set(dst_ref, repo.refs.get(src_ref), msg) + +def default_repo(): + return stack.Repository.default() diff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py index 874f81b..4fb37ce 100644 --- a/stgit/lib/transaction.py +++ b/stgit/lib/transaction.py @@ -1,7 +1,7 @@ from stgit import exception, utils from stgit.utils import any, all from stgit.out import * -from stgit.lib import git +from stgit.lib import git, log class TransactionException(exception.StgException): pass @@ -131,6 +131,7 @@ class StackTransaction(object): _print_current_patch(self.__stack.patchorder.applied, self.__applied) self.__stack.patchorder.applied = self.__applied self.__stack.patchorder.unapplied = self.__unapplied + log.log_entry(self.__stack, self.__msg) if self.__error: return utils.STGIT_CONFLICT diff --git a/stgit/main.py b/stgit/main.py index aa1f8ef..bd1a187 100644 --- a/stgit/main.py +++ b/stgit/main.py @@ -277,6 +277,7 @@ def main(): ret = command.func(parser, options, args) except (StgException, IOError, ParsingError, NoSectionError), err: + directory.write_log('%s %s' % (prog, cmd)) out.error(str(err), title = '%s %s' % (prog, cmd)) if debug_level > 0: traceback.print_exc() @@ -292,4 +293,5 @@ def main(): traceback.print_exc() sys.exit(utils.STGIT_BUG_ERROR) + directory.write_log('%s %s' % (prog, cmd)) sys.exit(ret or utils.STGIT_SUCCESS) -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html