{"thread":{"id":"12081","subject":"[StGit PATCH 0/2] Patch stack logging","startedAt":"2008-02-14T01:25:08Z","lastAt":"2008-02-22T15:58:25Z","messageCount":8,"participants":["Karl Hasselström","Catalin Marinas"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"68692","messageId":"20080214012119.22365.54526.stgit@yoghurt","threadId":"12081","inReplyTo":null,"subject":"[StGit PATCH 0/2] Patch stack logging","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-14T01:25:08Z","receivedAt":"2008-02-14T01:25:08Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"So here's what I have this far. It writes a log that's close to the\nformat I want, but doesn't do anything with it yet.\n\nIt's not in kha/experimental yet; it seems to work OK, but it leaves a\nlog behind that'll have to be cleaned up manually when the format\nchanges, which it will.\n\n---\n\nKarl Hasselström (2):\n      Write to a stack log when stack is modified\n      Library functions for tree and blob manipulation\n\n\n stgit/commands/branch.py   |   18 ++++--\n stgit/commands/clone.py    |    3 +\n stgit/commands/float.py    |    3 +\n stgit/commands/fold.py     |    3 +\n stgit/commands/imprt.py    |    3 +\n stgit/commands/init.py     |    3 +\n stgit/commands/pick.py     |    3 +\n stgit/commands/pop.py      |    3 +\n stgit/commands/pull.py     |    3 +\n stgit/commands/push.py     |    6 +-\n stgit/commands/rebase.py   |    4 +\n stgit/commands/refresh.py  |    4 +\n stgit/commands/rename.py   |    3 +\n stgit/commands/resolved.py |    4 +\n stgit/commands/sink.py     |    3 +\n stgit/commands/sync.py     |    5 +-\n stgit/lib/git.py           |  139 +++++++++++++++++++++++++++++++++++++-------\n stgit/lib/log.py           |   60 +++++++++++++++++++\n stgit/lib/transaction.py   |    3 +\n 19 files changed, 229 insertions(+), 44 deletions(-)\n create mode 100644 stgit/lib/log.py\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"68693","messageId":"20080214012522.22365.94327.stgit@yoghurt","threadId":"12081","inReplyTo":"20080214012119.22365.54526.stgit@yoghurt","subject":"[StGit PATCH 1/2] Library functions for tree and blob manipulation","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-14T01:29:06Z","receivedAt":"2008-02-14T01:29:06Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Wrap trees and blobs in Python objects (just like commits were already\nwrapped), so that StGit code can read and write them.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\nI'm quite pleased with the Foo/Foodata distinction (the former being a\nblob, tree, or commit in the git repository, the latter an immutable\nspecification for such an object). It's convenient to work with,\nwithout loss of efficiency.\n\n stgit/lib/git.py |  139 ++++++++++++++++++++++++++++++++++++++++++++++--------\n 1 files changed, 118 insertions(+), 21 deletions(-)\n\n\ndiff --git a/stgit/lib/git.py b/stgit/lib/git.py\nindex 6ee8a71..8184fec 100644\n--- a/stgit/lib/git.py\n+++ b/stgit/lib/git.py\n@@ -146,13 +146,100 @@ class Person(Repr):\n                 defaults = cls.user())\n         return cls.__committer\n \n-class Tree(Repr):\n+class GitObject(Repr):\n+    \"\"\"Base class for all git objects.\"\"\"\n+\n+class Blobdata(Repr):\n+    def __init__(self, string):\n+        self.__string = str(string)\n+    str = property(lambda self: self.__string)\n+    def commit(self, repository):\n+        sha1 = repository.run(['git', 'hash-object', '-w', '--stdin']\n+                              ).raw_input(self.str).output_one_line()\n+        return repository.get_blob(sha1)\n+\n+class Blob(GitObject):\n     \"\"\"Immutable.\"\"\"\n-    def __init__(self, sha1):\n+    typename = 'blob'\n+    default_perm = '100644'\n+    def __init__(self, repository, sha1):\n+        self.__repository = repository\n         self.__sha1 = sha1\n     sha1 = property(lambda self: self.__sha1)\n     def __str__(self):\n-        return 'Tree<%s>' % self.sha1\n+        return 'Blob<%s>' % self.sha1\n+    @property\n+    def data(self):\n+        return Blobdata(self.__repository.cat_object(self.sha1))\n+\n+class ImmutableDict(dict):\n+    def error(*args, **kwargs):\n+        raise TypeError('Cannot modify immutable dict')\n+    __delitem__ = error\n+    __setitem__ = error\n+    clear = error\n+    pop = error\n+    popitem = error\n+    setdefault = error\n+    update = error\n+\n+class Treedata(Repr):\n+    \"\"\"Immutable.\"\"\"\n+    @staticmethod\n+    def __x(po):\n+        if isinstance(po, GitObject):\n+            perm, object = po.default_perm, po\n+        else:\n+            perm, object = po\n+        return perm, object\n+    def __init__(self, entries):\n+        self.__entries = ImmutableDict((name, self.__x(po))\n+                                       for (name, po) in entries.iteritems())\n+    entries = property(lambda self: self.__entries)\n+    def set_entry(self, name, po):\n+        e = dict(self.entries)\n+        e[name] = self.__x(po)\n+        return type(self)(e)\n+    def del_entry(self, name):\n+        e = dict(self.entries)\n+        del e[name]\n+        return type(self)(e)\n+    def commit(self, repository):\n+        listing = ''.join(\n+            '%s %s %s\\t%s\\0' % (mode, obj.typename, obj.sha1, name)\n+            for (name, (mode, obj)) in self.entries.iteritems())\n+        sha1 = repository.run(['git', 'mktree', '-z']\n+                              ).raw_input(listing).output_one_line()\n+        return repository.get_tree(sha1)\n+    @classmethod\n+    def parse(cls, repository, s):\n+        entries = {}\n+        for line in s.split('\\0')[:-1]:\n+            m = re.match(r'^([0-7]{6}) ([a-z]) ([0-9a-f]{40})\\t(.*)$', line)\n+            assert m\n+            perm, type, sha1, name = m.groups()\n+            entries[name] = (perm, repository.get_object(type, sha1))\n+        return cls(entries)\n+\n+class Tree(GitObject):\n+    \"\"\"Immutable.\"\"\"\n+    typename = 'tree'\n+    default_perm = '040000'\n+    def __init__(self, repository, sha1):\n+        self.__sha1 = sha1\n+        self.__repository = repository\n+        self.__data = None\n+    sha1 = property(lambda self: self.__sha1)\n+    @property\n+    def data(self):\n+        if self.__data == None:\n+            self.__data = Treedata.parse(\n+                self.__repository,\n+                self.__repository.run(['git', 'ls-tree', '-z', self.sha1]\n+                                      ).raw_output())\n+        return self.__data\n+    def __str__(self):\n+        return 'Tree<sha1: %s>' % self.sha1\n \n class Commitdata(Repr):\n     \"\"\"Immutable.\"\"\"\n@@ -202,6 +289,22 @@ class Commitdata(Repr):\n         return ('Commitdata<tree: %s, parents: %s, author: %s,'\n                 ' committer: %s, message: \"%s\">'\n                 ) % (tree, parents, self.author, self.committer, self.message)\n+    def commit(self, repository):\n+        c = ['git', 'commit-tree', self.tree.sha1]\n+        for p in self.parents:\n+            c.append('-p')\n+            c.append(p.sha1)\n+        env = {}\n+        for p, v1 in ((self.author, 'AUTHOR'),\n+                       (self.committer, 'COMMITTER')):\n+            if p != None:\n+                for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),\n+                                 ('date', 'DATE')):\n+                    if getattr(p, attr) != None:\n+                        env['GIT_%s_%s' % (v1, v2)] = str(getattr(p, attr))\n+        sha1 = repository.run(c, env = env).raw_input(self.message\n+                                                      ).output_one_line()\n+        return repository.get_commit(sha1)\n     @classmethod\n     def parse(cls, repository, s):\n         cd = cls()\n@@ -223,8 +326,9 @@ class Commitdata(Repr):\n                 assert False\n         assert False\n \n-class Commit(Repr):\n+class Commit(GitObject):\n     \"\"\"Immutable.\"\"\"\n+    typename = 'commit'\n     def __init__(self, repository, sha1):\n         self.__sha1 = sha1\n         self.__repository = repository\n@@ -302,7 +406,8 @@ class Repository(RunWithEnv):\n     def __init__(self, directory):\n         self.__git_dir = directory\n         self.__refs = Refs(self)\n-        self.__trees = ObjectCache(lambda sha1: Tree(sha1))\n+        self.__blobs = ObjectCache(lambda sha1: Blob(self, sha1))\n+        self.__trees = ObjectCache(lambda sha1: Tree(self, sha1))\n         self.__commits = ObjectCache(lambda sha1: Commit(self, sha1))\n         self.__default_index = None\n         self.__default_worktree = None\n@@ -353,26 +458,18 @@ class Repository(RunWithEnv):\n                     ).output_one_line())\n         except run.RunException:\n             raise RepositoryException('%s: No such revision' % rev)\n+    def get_blob(self, sha1):\n+        return self.__blobs[sha1]\n     def get_tree(self, sha1):\n         return self.__trees[sha1]\n     def get_commit(self, sha1):\n         return self.__commits[sha1]\n-    def commit(self, commitdata):\n-        c = ['git', 'commit-tree', commitdata.tree.sha1]\n-        for p in commitdata.parents:\n-            c.append('-p')\n-            c.append(p.sha1)\n-        env = {}\n-        for p, v1 in ((commitdata.author, 'AUTHOR'),\n-                       (commitdata.committer, 'COMMITTER')):\n-            if p != None:\n-                for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),\n-                                 ('date', 'DATE')):\n-                    if getattr(p, attr) != None:\n-                        env['GIT_%s_%s' % (v1, v2)] = str(getattr(p, attr))\n-        sha1 = self.run(c, env = env).raw_input(commitdata.message\n-                                                ).output_one_line()\n-        return self.get_commit(sha1)\n+    def get_object(self, type, sha1):\n+        return { Blob.typename: self.get_blob,\n+                 Tree.typename: self.get_tree,\n+                 Commit.typename: self.get_commit }[type](sha1)\n+    def commit(self, objectdata):\n+        return objectdata.commit(self)\n     @property\n     def head(self):\n         try:\n"},{"id":"68694","messageId":"20080214012912.22365.83207.stgit@yoghurt","threadId":"12081","inReplyTo":"20080214012119.22365.54526.stgit@yoghurt","subject":"[StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-14T01:32:01Z","receivedAt":"2008-02-14T01:32:01Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Create a log branch (called <branchname>.stgit) for each StGit branch,\nand write to it whenever the stack is modified.\n\nAs of yet, nothing can be done with this log.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\nLots of noise here since all the old-infrastructure commands update\nthe log manually. But note how few lines it takes to actually write\nthe blobs, trees, and commits (in log.py, near the end).\n\n stgit/commands/branch.py   |   18 ++++++++++---\n stgit/commands/clone.py    |    3 +-\n stgit/commands/float.py    |    3 ++\n stgit/commands/fold.py     |    3 +-\n stgit/commands/imprt.py    |    3 +-\n stgit/commands/init.py     |    3 +-\n stgit/commands/pick.py     |    3 +-\n stgit/commands/pop.py      |    3 +-\n stgit/commands/pull.py     |    3 +-\n stgit/commands/push.py     |    6 ++--\n stgit/commands/rebase.py   |    4 +--\n stgit/commands/refresh.py  |    4 ++-\n stgit/commands/rename.py   |    3 +-\n stgit/commands/resolved.py |    4 ++-\n stgit/commands/sink.py     |    3 +-\n stgit/commands/sync.py     |    5 +++-\n stgit/lib/log.py           |   60 ++++++++++++++++++++++++++++++++++++++++++++\n stgit/lib/transaction.py   |    3 +-\n 18 files changed, 111 insertions(+), 23 deletions(-)\n create mode 100644 stgit/lib/log.py\n\n\ndiff --git a/stgit/commands/branch.py b/stgit/commands/branch.py\nindex 50684bb..131dfea 100644\n--- a/stgit/commands/branch.py\n+++ b/stgit/commands/branch.py\n@@ -25,7 +25,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git, basedir\n-\n+from stgit.lib import log\n \n help = 'manage patch stacks'\n usage = \"\"\"%prog [options] branch-name [commit-id]\n@@ -161,6 +161,7 @@ def func(parser, options, args):\n                                    parent_branch = parentbranch)\n \n         out.info('Branch \"%s\" created' % args[0])\n+        log.log_entry(log.default_stack(), 'stg branch --create')\n         return\n \n     elif options.clone:\n@@ -181,6 +182,8 @@ def func(parser, options, args):\n         crt_series.clone(clone)\n         out.done()\n \n+        log.copy_log(log.default_repo(), crt_series.get_name(), clone,\n+                     'stg branch --clone')\n         return\n \n     elif options.delete:\n@@ -188,6 +191,7 @@ def func(parser, options, args):\n         if len(args) != 1:\n             parser.error('incorrect number of arguments')\n         __delete_branch(args[0], options.force)\n+        log.delete_log(log.default_repo(), args[0])\n         return\n \n     elif options.list:\n@@ -195,13 +199,16 @@ def func(parser, options, args):\n         if len(args) != 0:\n             parser.error('incorrect number of arguments')\n \n-        branches = git.get_heads()\n-        branches.sort()\n+        branches = set(git.get_heads())\n+        for br in set(branches):\n+            m = re.match(r'^(.*)\\.stgit$', br)\n+            if m and m.group(1) in branches:\n+                branches.remove(br)\n \n         if branches:\n             out.info('Available branches:')\n             max_len = max([len(i) for i in branches])\n-            for i in branches:\n+            for i in sorted(branches):\n                 __print_branch(i, max_len)\n         else:\n             out.info('No branches')\n@@ -238,7 +245,8 @@ def func(parser, options, args):\n         stack.Series(args[0]).rename(args[1])\n \n         out.info('Renamed branch \"%s\" to \"%s\"' % (args[0], args[1]))\n-\n+        log.rename_log(log.default_repo(), args[0], args[1],\n+                       'stg branch --rename')\n         return\n \n     elif options.unprotect:\ndiff --git a/stgit/commands/clone.py b/stgit/commands/clone.py\nindex c3b0bbe..8c3cccd 100644\n--- a/stgit/commands/clone.py\n+++ b/stgit/commands/clone.py\n@@ -21,7 +21,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'make a local clone of a remote repository'\n usage = \"\"\"%prog [options] <repository> <dir>\n@@ -56,5 +56,6 @@ def func(parser, options, args):\n     # to work on a brand new repository\n     basedir.clear_cache()\n     stack.Series().init()\n+    log.log_entry(log.default_stack(), 'stg clone')\n \n     print 'done'\ndiff --git a/stgit/commands/float.py b/stgit/commands/float.py\nindex 0ba4446..5fec87e 100644\n--- a/stgit/commands/float.py\n+++ b/stgit/commands/float.py\n@@ -22,6 +22,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n+from stgit.lib import log\n \n help = 'push patches to the top, even if applied'\n usage = \"\"\"%prog [<patches> | -s [<series>] ]\n@@ -89,3 +90,5 @@ def func(parser, options, args):\n         if topush:\n             topush.reverse()\n             push_patches(crt_series, topush)\n+\n+    log.log_entry(log.default_stack(), 'stg float')\ndiff --git a/stgit/commands/fold.py b/stgit/commands/fold.py\nindex 3930a1f..105f54a 100644\n--- a/stgit/commands/fold.py\n+++ b/stgit/commands/fold.py\n@@ -22,7 +22,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'integrate a GNU diff patch into the current patch'\n usage = \"\"\"%prog [options] [<file>]\n@@ -80,3 +80,4 @@ def func(parser, options, args):\n         git.apply_patch(filename = filename)\n \n     out.done()\n+    log.log_entry(log.default_stack(), 'stg fold')\ndiff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py\nindex 4a4b792..d90e45d 100644\n--- a/stgit/commands/imprt.py\n+++ b/stgit/commands/imprt.py\n@@ -24,7 +24,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'import a GNU diff file as a new patch'\n usage = \"\"\"%prog [options] [<file>|<url>]\n@@ -289,3 +289,4 @@ def func(parser, options, args):\n         __import_file(filename, options)\n \n     print_crt_patch(crt_series)\n+    log.log_entry(log.default_stack(), 'stg import')\ndiff --git a/stgit/commands/init.py b/stgit/commands/init.py\nindex 475a4ce..b49b7b2 100644\n--- a/stgit/commands/init.py\n+++ b/stgit/commands/init.py\n@@ -22,7 +22,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'initialise the current branch for use with StGIT'\n usage = \"\"\"%prog [options]\n@@ -42,3 +42,4 @@ def func(parser, options, args):\n         parser.error('incorrect number of arguments')\n \n     crt_series.init()\n+    log.log_entry(log.default_stack(), 'stg init')\ndiff --git a/stgit/commands/pick.py b/stgit/commands/pick.py\nindex e011a84..4cc8944 100644\n--- a/stgit/commands/pick.py\n+++ b/stgit/commands/pick.py\n@@ -23,7 +23,7 @@ from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n from stgit.stack import Series\n-\n+from stgit.lib import log\n \n help = 'import a patch from a different branch or a commit object'\n usage = \"\"\"%prog [options] [<patch@branch>|<commit>]\n@@ -163,4 +163,5 @@ def func(parser, options, args):\n         else:\n             out.done()\n \n+    log.log_entry(log.default_stack(), 'stg pick')\n     print_crt_patch(crt_series)\ndiff --git a/stgit/commands/pop.py b/stgit/commands/pop.py\nindex dedf41b..e60361e 100644\n--- a/stgit/commands/pop.py\n+++ b/stgit/commands/pop.py\n@@ -22,7 +22,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'pop one or more patches from the stack'\n usage = \"\"\"%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>]\n@@ -87,4 +87,5 @@ def func(parser, options, args):\n     if topush:\n         push_patches(crt_series, topush)\n \n+    log.log_entry(log.default_stack(), 'stg pop')\n     print_crt_patch(crt_series)\ndiff --git a/stgit/commands/pull.py b/stgit/commands/pull.py\nindex bec7fd7..8be5a14 100644\n--- a/stgit/commands/pull.py\n+++ b/stgit/commands/pull.py\n@@ -23,7 +23,7 @@ from stgit.utils import *\n from stgit.out import *\n from stgit.config import GitConfigException\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'pull the changes from the remote repository'\n usage = \"\"\"%prog [options] [<repository>]\n@@ -106,4 +106,5 @@ def func(parser, options, args):\n     if config.get('stgit.keepoptimized') == 'yes':\n         git.repack()\n \n+    log.log_entry(log.default_stack(), 'stg pull')\n     print_crt_patch(crt_series)\ndiff --git a/stgit/commands/push.py b/stgit/commands/push.py\nindex 979835b..148a0b5 100644\n--- a/stgit/commands/push.py\n+++ b/stgit/commands/push.py\n@@ -23,7 +23,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'push one or more patches onto of the stack'\n usage = \"\"\"%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>]\n@@ -69,11 +69,11 @@ def func(parser, options, args):\n         out.start('Undoing push of \"%s\"' % patch)\n         resolved_all()\n         if crt_series.undo_push():\n+            log.log_entry(log.default_stack(), 'stg push --undo')\n             out.done()\n         else:\n             out.done('patch unchanged')\n         print_crt_patch(crt_series)\n-\n         return\n \n     check_local_changes()\n@@ -100,5 +100,5 @@ def func(parser, options, args):\n         patches.reverse()\n \n     push_patches(crt_series, patches, options.merged)\n-\n+    log.log_entry(log.default_stack(), 'stg push')\n     print_crt_patch(crt_series)\ndiff --git a/stgit/commands/rebase.py b/stgit/commands/rebase.py\nindex 12faaf8..41e93ce 100644\n--- a/stgit/commands/rebase.py\n+++ b/stgit/commands/rebase.py\n@@ -21,7 +21,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'move the stack base to another point in history'\n usage = \"\"\"%prog [options] <new-base-id>\n@@ -57,5 +57,5 @@ def func(parser, options, args):\n     applied = prepare_rebase(crt_series)\n     rebase(crt_series, args[0])\n     post_rebase(crt_series, applied, options.nopush, options.merged)\n-\n+    log.log_entry(log.default_stack(), 'stg rebase')\n     print_crt_patch(crt_series)\ndiff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py\nindex 4695c62..017ec5b 100644\n--- a/stgit/commands/refresh.py\n+++ b/stgit/commands/refresh.py\n@@ -24,7 +24,7 @@ from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n from stgit.config import config\n-\n+from stgit.lib import log\n \n help = 'generate a new commit for the current patch'\n usage = \"\"\"%prog [options] [<files or dirs>]\n@@ -91,6 +91,7 @@ def func(parser, options, args):\n     if options.undo:\n         out.start('Undoing the refresh of \"%s\"' % patch)\n         crt_series.undo_refresh()\n+        log.log_entry(log.default_stack(), 'stg refresh --undo')\n         out.done()\n         return\n \n@@ -131,6 +132,7 @@ def func(parser, options, args):\n         if options.patch:\n             between.reverse()\n             push_patches(crt_series, between)\n+        log.log_entry(log.default_stack(), 'stg refresh')\n     elif options.annotate:\n         # only annotate the top log entry as there is no need to\n         # refresh the patch and generate a full commit\ndiff --git a/stgit/commands/rename.py b/stgit/commands/rename.py\nindex e2b0fa4..4e9aac0 100644\n--- a/stgit/commands/rename.py\n+++ b/stgit/commands/rename.py\n@@ -22,7 +22,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'rename a patch in the series'\n usage = \"\"\"%prog [options] <oldpatch> <newpatch>\n@@ -42,4 +42,5 @@ def func(parser, options, args):\n \n     out.start('Renaming patch \"%s\" to \"%s\"' % (args[0], args[1]))\n     crt_series.rename_patch(args[0], args[1])\n+    log.log_entry(log.default_stack(), 'stg rename')\n     out.done()\ndiff --git a/stgit/commands/resolved.py b/stgit/commands/resolved.py\nindex 4ee75b8..cb21d84 100644\n--- a/stgit/commands/resolved.py\n+++ b/stgit/commands/resolved.py\n@@ -24,7 +24,7 @@ from stgit.utils import *\n from stgit import stack, git, basedir\n from stgit.config import config, file_extensions\n from stgit.gitmergeonefile import interactive_merge\n-\n+from stgit.lib import log\n \n help = 'mark a file conflict as solved'\n usage = \"\"\"%prog [options] [<files...>]\n@@ -55,6 +55,7 @@ def func(parser, options, args):\n \n     if options.all and not options.interactive:\n         resolved_all(options.reset)\n+        log.log_entry(log.default_stack(), 'stg resolved')\n         return\n \n     conflicts = git.get_conflicts()\n@@ -82,3 +83,4 @@ def func(parser, options, args):\n             git.resolved([filename])\n     else:\n         git.resolved(files, options.reset)\n+    log.log_entry(log.default_stack(), 'stg resolved')\ndiff --git a/stgit/commands/sink.py b/stgit/commands/sink.py\nindex 2167d87..f3a9ada 100644\n--- a/stgit/commands/sink.py\n+++ b/stgit/commands/sink.py\n@@ -22,7 +22,7 @@ from optparse import OptionParser, make_option\n from stgit.commands.common import *\n from stgit.utils import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'send patches deeper down the stack'\n usage = \"\"\"%prog [-t <target patch>] [-n] [<patches>]\n@@ -68,3 +68,4 @@ def func(parser, options, args):\n         def not_reapplied_yet(p):\n             return not p in newapplied\n         push_patches(crt_series, filter(not_reapplied_yet, oldapplied))\n+    log.log_entry(log.default_stack(), 'stg sink')\ndiff --git a/stgit/commands/sync.py b/stgit/commands/sync.py\nindex 99ab7de..168199d 100644\n--- a/stgit/commands/sync.py\n+++ b/stgit/commands/sync.py\n@@ -23,7 +23,7 @@ from stgit.commands.common import *\n from stgit.utils import *\n from stgit.out import *\n from stgit import stack, git\n-\n+from stgit.lib import log\n \n help = 'synchronise patches with a branch or a series'\n usage = \"\"\"%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>]\n@@ -77,6 +77,7 @@ def func(parser, options, args):\n         out.start('Undoing the sync of \"%s\"' % crt_series.get_current())\n         crt_series.undo_refresh()\n         git.reset()\n+        log.log_entry(log.default_stack(), 'stg sync --undo')\n         out.done()\n         return\n \n@@ -166,3 +167,5 @@ def func(parser, options, args):\n     # push the remaining patches\n     if popped:\n         push_patches(crt_series, popped)\n+\n+    log.log_entry(log.default_stack(), 'stg sync')\ndiff --git a/stgit/lib/log.py b/stgit/lib/log.py\nnew file mode 100644\nindex 0000000..1a9a326\n--- /dev/null\n+++ b/stgit/lib/log.py\n@@ -0,0 +1,60 @@\n+from stgit.lib import git, stack\n+\n+def patch_tree(repository, cd):\n+    return repository.commit(git.Treedata({ 'a': cd.parent.data.tree,\n+                                            'b': cd.tree }))\n+\n+def order_blob(repository, stack, kind):\n+    return repository.commit(git.Blobdata(''.join(\n+                '%s\\n' % pn for pn in getattr(stack.patchorder, kind))))\n+\n+def log_entry_tree(repository, stack):\n+    patches = repository.commit(\n+        git.Treedata(dict((pn, patch_tree(repository,\n+                                          stack.patches.get(pn).commit.data))\n+                          for pn in stack.patchorder.all)))\n+    return repository.commit(git.Treedata({\n+                'version': repository.commit(git.Blobdata('0')),\n+                'patches': patches,\n+                'applied': order_blob(repository, stack, 'applied'),\n+                'unapplied': order_blob(repository, stack, 'unapplied'),\n+                }))\n+\n+def log_ref(branch):\n+    return 'refs/heads/%s.stgit' % branch\n+\n+def log_entry(stack, msg):\n+    ref = log_ref(stack.name)\n+    try:\n+        last = [stack.repository.refs.get(ref)]\n+    except KeyError:\n+        last = []\n+    commit = stack.repository.commit(\n+        git.Commitdata(tree = log_entry_tree(stack.repository, stack),\n+                       parents = last, message = msg))\n+    stack.repository.refs.set(ref, commit, msg)\n+\n+def delete_log(repo, branch):\n+    ref = log_ref(branch)\n+    if repo.refs.exists(ref):\n+        repo.refs.delete(ref)\n+\n+def rename_log(repo, old_branch, new_branch, msg):\n+    old_ref = log_ref(old_branch)\n+    new_ref = log_ref(new_branch)\n+    if repo.refs.exists(old_ref):\n+        repo.refs.set(new_ref, repo.refs.get(old_ref), msg)\n+        repo.refs.delete(old_ref)\n+\n+def copy_log(repo, src_branch, dst_branch, msg):\n+    src_ref = log_ref(src_branch)\n+    dst_ref = log_ref(dst_branch)\n+    if repo.refs.exists(src_ref):\n+        repo.refs.set(dst_ref, repo.refs.get(src_ref), msg)\n+\n+def default_repo():\n+    return stack.Repository.default()\n+\n+def default_stack(branch = None):\n+    repo = default_repo()\n+    return repo.get_stack(branch or repo.current_branch)\ndiff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py\nindex 3613b15..0b0b52d 100644\n--- a/stgit/lib/transaction.py\n+++ b/stgit/lib/transaction.py\n@@ -1,6 +1,6 @@\n from stgit import exception, utils\n from stgit.out import *\n-from stgit.lib import git\n+from stgit.lib import git, log\n \n class TransactionException(exception.StgException):\n     pass\n@@ -117,6 +117,7 @@ class StackTransaction(object):\n         _print_current_patch(self.__stack.patchorder.applied, self.__applied)\n         self.__stack.patchorder.applied = self.__applied\n         self.__stack.patchorder.unapplied = self.__unapplied\n+        log.log_entry(self.__stack, self.__msg)\n \n         if self.__error:\n             return utils.STGIT_CONFLICT\n"},{"id":"69417","messageId":"b0943d9e0802201446g2840c3d9v576f840936253525@mail.gmail.com","threadId":"12081","inReplyTo":"20080214012912.22365.83207.stgit@yoghurt","subject":"Re: [StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2008-02-20T22:46:48Z","receivedAt":"2008-02-20T22:46:48Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 14/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n> Create a log branch (called <branchname>.stgit) for each StGit branch,\n>  and write to it whenever the stack is modified.\n\nThe abstractions are really nice (and I still wonder how StGIT\ncodebase increased that much when all I needed two years ago was a\nsimple script-like application to reorder commits :-)).\n\nAnyway, I don't really like the idea of an additional commit (I don't\neven like the old patch log implementation) when the stack is\nmodified. It needs some profiling but it has a visible impact on\nstacks with a big number of patches (my last kernel release at\nwww.linux-arm.org/git had 80 patches and it takes a lot of time to\npush them).\n\nCan we not use some of the automatic reflog recording that GIT does\ninstead of writing a commit? It's cheaper to write a text file than\ngenerating a commit. In my kernel repository I have several branches\nwith many patches and, even after \"git gc\" and repacking, it is still\nslow (mainly because of git-read-tree but I'd like to reduce the\nnumber of calls to GIT).\n\n-- \nCatalin\n"},{"id":"69462","messageId":"20080221071819.GA8250@diana.vm.bytemark.co.uk","threadId":"12081","inReplyTo":"b0943d9e0802201446g2840c3d9v576f840936253525@mail.gmail.com","subject":"Re: [StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-21T07:18:19Z","receivedAt":"2008-02-21T07:18:19Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2008-02-20 22:46:48 +0000, Catalin Marinas wrote:\n\n> On 14/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n>\n> > Create a log branch (called <branchname>.stgit) for each StGit\n> > branch, and write to it whenever the stack is modified.\n>\n> The abstractions are really nice (and I still wonder how StGIT\n> codebase increased that much when all I needed two years ago was a\n> simple script-like application to reorder commits :-)).\n\n:-) I'll take some of the blame, but StGit was quite large already\nwhen I started submitting patches to it.\n\n> Anyway, I don't really like the idea of an additional commit (I\n> don't even like the old patch log implementation) when the stack is\n> modified. It needs some profiling but it has a visible impact on\n> stacks with a big number of patches (my last kernel release at\n> www.linux-arm.org/git had 80 patches and it takes a lot of time to\n> push them).\n\nOne thing to remember is that my patch stack log gets one new commit\nper StGit operation, not one per changed patch. So if you push 80\npatches with one command, that will give you just one commit on the\nlog. (If someone makes a scripts that calls push 80 times this won't\nhelp, of course.)\n\n> Can we not use some of the automatic reflog recording that GIT does\n> instead of writing a commit? It's cheaper to write a text file than\n> generating a commit.\n\nAs I said, I'm not convinced that it's a huge difference when you have\njust one log commit per operation, not one per changed patch. But\nlet's stop arguing about it until I have some numbers to back my claim\nup. ;-)\n\n> In my kernel repository I have several branches with many patches\n> and, even after \"git gc\" and repacking, it is still slow (mainly\n> because of git-read-tree but I'd like to reduce the number of calls\n> to GIT).\n\nWhen you say \"it's still slow\", are you referring to the existing\nper-patch log, my per-branch log, or just StGit in general?\n\nHave you noticed any difference between commands using the old and new\ninfrastructure (say, stg push vs. stg goto)? The latter should be\ntaking less time, due to touching the worktree only when necessary.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"69581","messageId":"b0943d9e0802220605t56257718p850c677a59671862@mail.gmail.com","threadId":"12081","inReplyTo":"20080221071819.GA8250@diana.vm.bytemark.co.uk","subject":"Re: [StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2008-02-22T14:05:55Z","receivedAt":"2008-02-22T14:05:55Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 21/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n> On 2008-02-20 22:46:48 +0000, Catalin Marinas wrote:\n>\n>  > The abstractions are really nice (and I still wonder how StGIT\n>  > codebase increased that much when all I needed two years ago was a\n>  > simple script-like application to reorder commits :-)).\n>\n>\n> :-) I'll take some of the blame, but StGit was quite large already\n>  when I started submitting patches to it.\n\nAnyway, the new restructuring is much cleaner, though heavily OO and\nsome people might not like it (me not included).\n\n>\n>\n>  > Anyway, I don't really like the idea of an additional commit (I\n>  > don't even like the old patch log implementation) when the stack is\n>  > modified. It needs some profiling but it has a visible impact on\n>  > stacks with a big number of patches (my last kernel release at\n>  > www.linux-arm.org/git had 80 patches and it takes a lot of time to\n>  > push them).\n>\n>\n> One thing to remember is that my patch stack log gets one new commit\n>  per StGit operation, not one per changed patch. So if you push 80\n>  patches with one command, that will give you just one commit on the\n>  log. (If someone makes a scripts that calls push 80 times this won't\n>  help, of course.)\n\nAh, I got it now. Indeed, it would be faster than the current\nper-patch log. I no longer that worried :-)\n\n>  > In my kernel repository I have several branches with many patches\n>  > and, even after \"git gc\" and repacking, it is still slow (mainly\n>  > because of git-read-tree but I'd like to reduce the number of calls\n>  > to GIT).\n>\n>\n> When you say \"it's still slow\", are you referring to the existing\n>  per-patch log, my per-branch log, or just StGit in general?\n\nI think it's more GIT in general. Checking the tree status takes some\ntime and a 3-way merge on a 512MB RAM machine with GIT using over\n200MB gets a bit slow.\n\n>  Have you noticed any difference between commands using the old and new\n>  infrastructure (say, stg push vs. stg goto)? The latter should be\n>  taking less time, due to touching the worktree only when necessary.\n\nIn the patch pushing functions, it now first calls simple_merge()\nwhich is still a 3-way merge but without rename detection. The old\nStGIT implementation was using \"git-diff | git-apply\" and falling back\nto the recursive merge. Most of the patches apply cleanly and we don't\nneed the three-way merge which uses more RAM.\n\nThe \"(modified)\" information was also displayed for a three-way merge\n(either read-tree or recursive-merge) but now it is only shown for the\nrecursive one. Correctly, merging git-read-tree modifies a patch as\nwell since it no longer applied cleanly. We could use this \"modified\"\nfeature to automatically export patches (some people asked for this in\nthe past, as means for backup in case of StGIT failures).\n\n-- \nCatalin\n"},{"id":"69583","messageId":"20080222145609.GA19172@diana.vm.bytemark.co.uk","threadId":"12081","inReplyTo":"b0943d9e0802220605t56257718p850c677a59671862@mail.gmail.com","subject":"Re: [StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-22T14:56:09Z","receivedAt":"2008-02-22T14:56:09Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2008-02-22 14:05:55 +0000, Catalin Marinas wrote:\n\n> On 21/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n>\n> > On 2008-02-20 22:46:48 +0000, Catalin Marinas wrote:\n> >\n> >  > The abstractions are really nice (and I still wonder how StGIT\n> >  > codebase increased that much when all I needed two years ago\n> >  > was a simple script-like application to reorder commits :-)).\n> >\n> > :-) I'll take some of the blame, but StGit was quite large already\n> >  when I started submitting patches to it.\n>\n> Anyway, the new restructuring is much cleaner, though heavily OO and\n> some people might not like it (me not included).\n\nThat it's conceptually OO is unavoidable, I think (the only way to\navoid that would be through obfuscation). And using Python's OO\nfeatures to write code for such a model is the least bad you can do in\nPython. IMHO.\n\n> > When you say \"it's still slow\", are you referring to the existing\n> > per-patch log, my per-branch log, or just StGit in general?\n>\n> I think it's more GIT in general. Checking the tree status takes\n> some time and a 3-way merge on a 512MB RAM machine with GIT using\n> over 200MB gets a bit slow.\n\nI just upgraded my laptop from 512 MB to 2 GB, so I'll start\nneglecting this kind of problem, I fear. :-/\n\n> >  Have you noticed any difference between commands using the old\n> >  and new infrastructure (say, stg push vs. stg goto)? The latter\n> >  should be taking less time, due to touching the worktree only\n> >  when necessary.\n>\n> In the patch pushing functions, it now first calls simple_merge()\n> which is still a 3-way merge but without rename detection. The old\n> StGIT implementation was using \"git-diff | git-apply\" and falling\n> back to the recursive merge. Most of the patches apply cleanly and\n> we don't need the three-way merge which uses more RAM.\n\nI didn't include patch application because it wasn't necessary to get\nthings working, and is easy to add at any later point.\n\nI'd be curious to see how much of a difference it makes.\n\n> The \"(modified)\" information was also displayed for a three-way\n> merge (either read-tree or recursive-merge) but now it is only shown\n> for the recursive one. Correctly, merging git-read-tree modifies a\n> patch as well since it no longer applied cleanly.\n\nAh, so the precise meaning of a patch being \"modified\" is that git\napply failed to apply it. I didn't know that.\n\n> We could use this \"modified\" feature to automatically export patches\n> (some people asked for this in the past, as means for backup in case\n> of StGIT failures).\n\nYou mean automatically export only the changed patches? (Otherwise stg\nexport would do the job -- I used to use that before I got confident\nenough with StGit.)\n\nOne cool thing we could do is export the patches as files in a git\nbranch -- the log machinery I'm building should make it trivial. That\nway, you'd get the history of the patches too.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"69588","messageId":"b0943d9e0802220758j76248cf3rb4dbb86f03b57b16@mail.gmail.com","threadId":"12081","inReplyTo":"20080222145609.GA19172@diana.vm.bytemark.co.uk","subject":"Re: [StGit PATCH 2/2] Write to a stack log when stack is modified","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2008-02-22T15:58:25Z","receivedAt":"2008-02-22T15:58:25Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 22/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n> On 2008-02-22 14:05:55 +0000, Catalin Marinas wrote:\n>\n>  > On 21/02/2008, Karl Hasselström <kha@treskal.com> wrote:\n>  >\n>  > > On 2008-02-20 22:46:48 +0000, Catalin Marinas wrote:\n>  > >\n>  > >  > The abstractions are really nice (and I still wonder how StGIT\n>  > >  > codebase increased that much when all I needed two years ago\n>  > >  > was a simple script-like application to reorder commits :-)).\n>  > >\n>  > > :-) I'll take some of the blame, but StGit was quite large already\n>  > >  when I started submitting patches to it.\n>  >\n>  > Anyway, the new restructuring is much cleaner, though heavily OO and\n>  > some people might not like it (me not included).\n>\n>\n> That it's conceptually OO is unavoidable, I think (the only way to\n>  avoid that would be through obfuscation). And using Python's OO\n>  features to write code for such a model is the least bad you can do in\n>  Python. IMHO.\n\nI agree.\n\n>  > > When you say \"it's still slow\", are you referring to the existing\n>  > > per-patch log, my per-branch log, or just StGit in general?\n>  >\n>  > I think it's more GIT in general. Checking the tree status takes\n>  > some time and a 3-way merge on a 512MB RAM machine with GIT using\n>  > over 200MB gets a bit slow.\n>\n>\n> I just upgraded my laptop from 512 MB to 2 GB, so I'll start\n>  neglecting this kind of problem, I fear. :-/\n\nYou can boot with mem=512MB (or even less) :-).\n\n>  > >  Have you noticed any difference between commands using the old\n>  > >  and new infrastructure (say, stg push vs. stg goto)? The latter\n>  > >  should be taking less time, due to touching the worktree only\n>  > >  when necessary.\n>  >\n>  > In the patch pushing functions, it now first calls simple_merge()\n>  > which is still a 3-way merge but without rename detection. The old\n>  > StGIT implementation was using \"git-diff | git-apply\" and falling\n>  > back to the recursive merge. Most of the patches apply cleanly and\n>  > we don't need the three-way merge which uses more RAM.\n>\n>\n> I didn't include patch application because it wasn't necessary to get\n>  things working, and is easy to add at any later point.\n>\n>  I'd be curious to see how much of a difference it makes.\n\nIt makes a difference if you don't have much RAM available. I think\nonce a patch falls back to three-way merge, Linux already makes room\navailable for the subsequent merges. But, as I said, most of the time\nthe patches just apply cleanly.\n\n>  > We could use this \"modified\" feature to automatically export patches\n>  > (some people asked for this in the past, as means for backup in case\n>  > of StGIT failures).\n>\n>\n> You mean automatically export only the changed patches?\n\nYes, at refresh and pushing, but for the latter, only if a patch was modified.\n\n>  One cool thing we could do is export the patches as files in a git\n>  branch -- the log machinery I'm building should make it trivial. That\n>  way, you'd get the history of the patches too.\n\nYes, I don't think it is difficult to do (it just need to be optional\nas some people might not need it).\n\n-- \nCatalin\n"}]}