[StGit PATCH 04/10] Write to a stack log when stack is modified

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux