Git development
 help / color / mirror / Atom feed
From: "Karl Hasselström" <kha@treskal.com>
To: Catalin Marinas <catalin.marinas@gmail.com>
Cc: git@vger.kernel.org
Subject: [StGit PATCH 04/10] Write to a stack log when stack is modified
Date: Mon, 21 Apr 2008 00:10:44 +0200	[thread overview]
Message-ID: <20080420221044.5837.25817.stgit@yoghurt> (raw)
In-Reply-To: <20080420215625.5837.82896.stgit@yoghurt>

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@treskal.com>

---

 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)

  parent reply	other threads:[~2008-04-20 22:12 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-04-20 22:10 [StGit PATCH 00/10] Updated "stg reset" series Karl Hasselström
2008-04-20 22:10 ` [StGit PATCH 01/10] Prevent most commands from running when there are conflicts Karl Hasselström
2008-04-20 22:10 ` [StGit PATCH 02/10] Add property with a list of all patch names Karl Hasselström
2008-04-20 22:10 ` [StGit PATCH 03/10] Library functions for tree and blob manipulation Karl Hasselström
2008-04-20 22:10 ` Karl Hasselström [this message]
2008-04-20 22:10 ` [StGit PATCH 05/10] Add utility function for reordering patches Karl Hasselström
2008-04-20 22:10 ` [StGit PATCH 06/10] New command: stg reset Karl Hasselström
2008-04-20 22:11 ` [StGit PATCH 07/10] Log conflicts separately Karl Hasselström
2008-04-20 22:11 ` [StGit PATCH 08/10] Log conflicts separately for all commands Karl Hasselström
2008-04-20 22:11 ` [StGit PATCH 09/10] Add a --hard flag to stg reset Karl Hasselström
2008-04-20 22:11 ` [StGit PATCH 10/10] Don't write a log entry if there were no changes Karl Hasselström

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20080420221044.5837.25817.stgit@yoghurt \
    --to=kha@treskal.com \
    --cc=catalin.marinas@gmail.com \
    --cc=git@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox