{"thread":{"id":"7997","subject":"[StGIT PATCH] Don't use patches/<branch>/current","startedAt":"2007-05-06T15:13:44Z","lastAt":"2007-07-01T14:35:50Z","messageCount":36,"participants":["Karl Hasselström","Catalin Marinas","Peter Oberndorfer","Yann Dirson"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"41214","messageId":"20070506150852.8985.98091.stgit@yoghurt","threadId":"7997","inReplyTo":null,"subject":"[StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-06T15:13:44Z","receivedAt":"2007-05-06T15:13:44Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"The name of the current patch, if any, is always the last line of\npatches/<branch>/applied (and there is no current patch if and only if\nthe \"applied\" file is empty). So use that instead, and stop having to\nworry about keeping the redundant \"current\" file up-to-date.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\nThis is another remove-redundant-metadata cleanup patch. Not only does\nit remove more code than it adds, the removed code (mostly calls to\n__set_current) is the kind that one easily forgets to insert in the\nproper places when writing new code.\n\n stgit/stack.py |   35 +++++++++--------------------------\n 1 files changed, 9 insertions(+), 26 deletions(-)\n\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 2477ac6..3e9fc4f 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -295,7 +295,6 @@ class Series(StgitObject):\n         self.__applied_file = os.path.join(self._dir(), 'applied')\n         self.__unapplied_file = os.path.join(self._dir(), 'unapplied')\n         self.__hidden_file = os.path.join(self._dir(), 'hidden')\n-        self.__current_file = os.path.join(self._dir(), 'current')\n         self.__descr_file = os.path.join(self._dir(), 'description')\n \n         # where this series keeps its patches\n@@ -325,11 +324,6 @@ class Series(StgitObject):\n         \"\"\"\n         return self.__name\n \n-    def __set_current(self, name):\n-        \"\"\"Sets the topmost patch\n-        \"\"\"\n-        self._set_field('current', name)\n-\n     def get_patch(self, name):\n         \"\"\"Return a Patch object for the given name\n         \"\"\"\n@@ -346,11 +340,16 @@ class Series(StgitObject):\n     def get_current(self):\n         \"\"\"Return the name of the topmost patch, or None if there is\n         no such patch.\"\"\"\n-        name = self._get_field('current')\n-        if name == '':\n+        try:\n+            applied = self.get_applied()\n+        except StackException:\n+            # No \"applied\" file: branch is not initialized.\n+            return None\n+        try:\n+            return applied[-1]\n+        except IndexError:\n+            # No patches applied.\n             return None\n-        else:\n-            return name\n \n     def get_applied(self):\n         if not os.path.isfile(self.__applied_file):\n@@ -650,8 +649,6 @@ class Series(StgitObject):\n                 os.remove(self.__unapplied_file)\n             if os.path.exists(self.__hidden_file):\n                 os.remove(self.__hidden_file)\n-            if os.path.exists(self.__current_file):\n-                os.remove(self.__current_file)\n             if os.path.exists(self.__descr_file):\n                 os.remove(self.__descr_file)\n             if os.path.exists(self._dir()+'/orig-base'):\n@@ -825,11 +822,8 @@ class Series(StgitObject):\n             self.log_patch(patch, 'new')\n \n             insert_string(self.__applied_file, patch.get_name())\n-            if not self.get_current():\n-                self.__set_current(name)\n         else:\n             append_string(self.__applied_file, patch.get_name())\n-            self.__set_current(name)\n             if refresh:\n                 self.refresh_patch(cache_update = False, log = 'new')\n \n@@ -936,8 +930,6 @@ class Series(StgitObject):\n         f.writelines([line + '\\n' for line in unapplied])\n         f.close()\n \n-        self.__set_current(name)\n-\n         return forwarded\n \n     def merged_patches(self, names):\n@@ -1019,8 +1011,6 @@ class Series(StgitObject):\n         f.writelines([line + '\\n' for line in unapplied])\n         f.close()\n \n-        self.__set_current(name)\n-\n         # head == bottom case doesn't need to refresh the patch\n         if empty or head != bottom:\n             if not ex:\n@@ -1098,11 +1088,6 @@ class Series(StgitObject):\n         f.writelines([line + '\\n' for line in applied])\n         f.close()\n \n-        if applied == []:\n-            self.__set_current(None)\n-        else:\n-            self.__set_current(applied[-1])\n-\n     def empty_patch(self, name):\n         \"\"\"Returns True if the patch is empty\n         \"\"\"\n@@ -1144,8 +1129,6 @@ class Series(StgitObject):\n             f.close()\n         elif oldname in applied:\n             Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)\n-            if oldname == self.get_current():\n-                self.__set_current(newname)\n \n             applied[applied.index(oldname)] = newname\n \n"},{"id":"42228","messageId":"b0943d9e0705150856n771cb696h6e8225a0bbd5d43d@mail.gmail.com","threadId":"7997","inReplyTo":"20070506150852.8985.98091.stgit@yoghurt","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-15T15:56:33Z","receivedAt":"2007-05-15T15:56:33Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> The name of the current patch, if any, is always the last line of\n> patches/<branch>/applied (and there is no current patch if and only if\n> the \"applied\" file is empty). So use that instead, and stop having to\n> worry about keeping the redundant \"current\" file up-to-date.\n\nI applied this patch. Could you also send me a patch for the\nbash-completion script as it uses this file?\n\nI think the self.__current_file (same for the base file removed in a\ndifferent patch) should still be available in the Series object and\nremoved when deleting a branch, otherwise you get a \"Series directory\n... is not empty\" exception.\n\nThanks.\n\n-- \nCatalin\n"},{"id":"42230","messageId":"200705151821.08568.kumbayo84@arcor.de","threadId":"7997","inReplyTo":"b0943d9e0705150856n771cb696h6e8225a0bbd5d43d@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Peter Oberndorfer","fromEmail":"kumbayo84@arcor.de","sentAt":"2007-05-15T16:21:08Z","receivedAt":"2007-05-15T16:21:08Z","isPatch":true,"sender":{"key":"kumbayo84@arcor.de","avatar":"https://avatars.githubusercontent.com/u/1041267?v=4"},"body":"On Tuesday 15 May 2007 17:56, Catalin Marinas wrote:\n> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> > The name of the current patch, if any, is always the last line of\n> > patches/<branch>/applied (and there is no current patch if and only if\n> > the \"applied\" file is empty). So use that instead, and stop having to\n> > worry about keeping the redundant \"current\" file up-to-date.\n> \n> I applied this patch. Could you also send me a patch for the\n> bash-completion script as it uses this file?\n> \n> I think the self.__current_file (same for the base file removed in a\n> different patch) should still be available in the Series object and\n> removed when deleting a branch, otherwise you get a \"Series directory\n> ... is not empty\" exception.\n> \n> Thanks.\n> \nHi,\nthis is a bit OT,\nbut when i wanted to try out this changes i found that 2 unrelated patches in you repo[1] are empty.\n* Store branch description in the config file \n* Make the \"name\" argument to \"stg new\" optional\n\nIs that a problem on my side, or are they really empty?\n\nGreetings Peter\n\n[1] http://homepage.ntlworld.com/cmarinas/stgit.git\nwhich is mirrored at\nhttp://repo.or.cz/w/stgit.git\n"},{"id":"42233","messageId":"b0943d9e0705150950g14261b94h7133514965ebe85c@mail.gmail.com","threadId":"7997","inReplyTo":"200705151821.08568.kumbayo84@arcor.de","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-15T16:50:52Z","receivedAt":"2007-05-15T16:50:52Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 15/05/07, Peter Oberndorfer <kumbayo84@arcor.de> wrote:\n> this is a bit OT,\n> but when i wanted to try out this changes i found that 2 unrelated patches in you repo[1] are empty.\n> * Store branch description in the config file\n> * Make the \"name\" argument to \"stg new\" optional\n\nThanks for pointing out. They failed to apply cleanly last night and\nforgot to delete the empty patches created. I fixed the conflicts and\nadded them today (I'll push them tonight).\n\n-- \nCatalin\n"},{"id":"42236","messageId":"20070515182549.GB17851@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705150856n771cb696h6e8225a0bbd5d43d@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-15T18:25:49Z","receivedAt":"2007-05-15T18:25:49Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-15 16:56:33 +0100, Catalin Marinas wrote:\n\n> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:\n>\n> > The name of the current patch, if any, is always the last line of\n> > patches/<branch>/applied (and there is no current patch if and\n> > only if the \"applied\" file is empty). So use that instead, and\n> > stop having to worry about keeping the redundant \"current\" file\n> > up-to-date.\n>\n> I applied this patch. Could you also send me a patch for the\n> bash-completion script as it uses this file?\n\nI realized this myself yesterday or so, and patched it to not need the\ncurrent, applied, and unapplied files. Are you OK with that patch, or\nwould you like one that keeps using {,un}applied?\n\n> I think the self.__current_file (same for the base file removed in a\n> different patch) should still be available in the Series object and\n> removed when deleting a branch, otherwise you get a \"Series\n> directory ... is not empty\" exception.\n\nAh, very true. I'll whip up a fix.\n\nSame question there: are you OK with a single fix for base, current,\napplied, and unapplied, or do you want them separate?\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42244","messageId":"20070515193824.31968.87744.stgit@yoghurt","threadId":"7997","inReplyTo":"20070515182549.GB17851@diana.vm.bytemark.co.uk","subject":"[StGIT PATCH] Remove obsolete files when deleting a branch","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-15T19:38:40Z","receivedAt":"2007-05-15T19:38:40Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Signed-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/stack.py |   10 ++++++++++\n 1 files changed, 10 insertions(+), 0 deletions(-)\n\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 52f39a6..4ed3a73 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -660,6 +660,16 @@ class Series(StgitObject):\n             if os.path.exists(self._dir()+'/orig-base'):\n                 os.remove(self._dir()+'/orig-base')\n \n+            # Remove obsolete files that StGIT no longer uses, but\n+            # that might still be around if this is an old repository.\n+            for obsolete in ([os.path.join(self._dir(), fn)\n+                              for fn in ['current', 'description',\n+                                         'applied', 'unapplied']]\n+                             + [os.path.join(self.__base_dir,\n+                                             'refs', 'bases', self.__name)]):\n+                if os.path.exists(obsolete):\n+                    os.remove(obsolete)\n+\n             if not os.listdir(self.__patch_dir):\n                 os.rmdir(self.__patch_dir)\n             else:\n"},{"id":"42246","messageId":"b0943d9e0705151301r7412109qd73a6008b888f8bc@mail.gmail.com","threadId":"7997","inReplyTo":"20070515182549.GB17851@diana.vm.bytemark.co.uk","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-15T20:01:43Z","receivedAt":"2007-05-15T20:01:43Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 15/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> On 2007-05-15 16:56:33 +0100, Catalin Marinas wrote:\n>\n> > On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> >\n> > > The name of the current patch, if any, is always the last line of\n> > > patches/<branch>/applied (and there is no current patch if and\n> > > only if the \"applied\" file is empty). So use that instead, and\n> > > stop having to worry about keeping the redundant \"current\" file\n> > > up-to-date.\n> >\n> > I applied this patch. Could you also send me a patch for the\n> > bash-completion script as it uses this file?\n>\n> I realized this myself yesterday or so, and patched it to not need the\n> current, applied, and unapplied files. Are you OK with that patch, or\n> would you like one that keeps using {,un}applied?\n\nWhat is the impact on the bash completion for calling StGIT rather\nthan reading those files? Is it visible? If I integrate the DAG\npatches, there probably isn't other way anyway.\n\n> > I think the self.__current_file (same for the base file removed in a\n> > different patch) should still be available in the Series object and\n> > removed when deleting a branch, otherwise you get a \"Series\n> > directory ... is not empty\" exception.\n>\n> Ah, very true. I'll whip up a fix.\n>\n> Same question there: are you OK with a single fix for base, current,\n> applied, and unapplied, or do you want them separate?\n\nWhatever is easier for you :-), I don't have any preference.\n\nI'll push the patches I integrated in the next hour or so and you can\nbase your changes on them.\n\nThanks.\n\n-- \nCatalin\n"},{"id":"42248","messageId":"20070515210801.GO19253@nan92-1-81-57-214-146.fbx.proxad.net","threadId":"7997","inReplyTo":"b0943d9e0705150856n771cb696h6e8225a0bbd5d43d@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-05-15T21:08:01Z","receivedAt":"2007-05-15T21:08:01Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"On Tue, May 15, 2007 at 04:56:33PM +0100, Catalin Marinas wrote:\n> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> >The name of the current patch, if any, is always the last line of\n> >patches/<branch>/applied (and there is no current patch if and only if\n> >the \"applied\" file is empty). So use that instead, and stop having to\n> >worry about keeping the redundant \"current\" file up-to-date.\n> \n> I applied this patch. Could you also send me a patch for the\n> bash-completion script as it uses this file?\n> \n> I think the self.__current_file (same for the base file removed in a\n> different patch) should still be available in the Series object and\n> removed when deleting a branch, otherwise you get a \"Series directory\n> .. is not empty\" exception.\n\nShouldn't we also migrate to new format as soon as we need to touch a\ndata - in this case, whenever we push/pop ?\n\nOr maybe declare a new \"stgit stack format version\" ?  Currently we\nhave \"stg branch --convert\", which switches between a \"new\" and an\n\"old\" format which noone probably uses any more.  What about\nversionning the on-disk format, and possibly provide the \"convert\"\nfunctionnality back and forth between one format and the next, with\nformal documentation about which version works with which stack\nformat ?\n\nBest regards,\n-- \nYann.\n"},{"id":"42250","messageId":"b0943d9e0705151436g64e24a32r23c45c19f750082@mail.gmail.com","threadId":"7997","inReplyTo":"20070515210801.GO19253@nan92-1-81-57-214-146.fbx.proxad.net","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-15T21:36:05Z","receivedAt":"2007-05-15T21:36:05Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 15/05/07, Yann Dirson <ydirson@altern.org> wrote:\n> On Tue, May 15, 2007 at 04:56:33PM +0100, Catalin Marinas wrote:\n> > I think the self.__current_file (same for the base file removed in a\n> > different patch) should still be available in the Series object and\n> > removed when deleting a branch, otherwise you get a \"Series directory\n> > .. is not empty\" exception.\n>\n> Shouldn't we also migrate to new format as soon as we need to touch a\n> data - in this case, whenever we push/pop ?\n>\n> Or maybe declare a new \"stgit stack format version\" ?  Currently we\n> have \"stg branch --convert\", which switches between a \"new\" and an\n> \"old\" format which noone probably uses any more.  What about\n> versionning the on-disk format, and possibly provide the \"convert\"\n> functionnality back and forth between one format and the next, with\n> formal documentation about which version works with which stack\n> format ?\n\nI think it would be useful to have a version file (probably per\nbranch) and just upgrade when a mismatch is detected (in the __init__\nfunction). The other option is to keep ignoring the unused files until\nthe branch is deleted but we might make a change at some point that\nwould break things.\n\nWe should write successive convert() functions and keep all of them in\ncase one skips an intermediate version.\n\n-- \nCatalin\n"},{"id":"42253","messageId":"20070515214952.GQ19253@nan92-1-81-57-214-146.fbx.proxad.net","threadId":"7997","inReplyTo":"b0943d9e0705151436g64e24a32r23c45c19f750082@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-05-15T21:49:52Z","receivedAt":"2007-05-15T21:49:52Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"On Tue, May 15, 2007 at 10:36:05PM +0100, Catalin Marinas wrote:\n> I think it would be useful to have a version file (probably per\n> branch) and just upgrade when a mismatch is detected (in the __init__\n> function).\n\nSounds reasonable, but I'd rather keep that in the config file\n(eg. branch.<name>.stgit.formatversion).\n\nBest regards,\n-- \nYann.\n"},{"id":"42290","messageId":"20070516062711.GA7235@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"20070515214952.GQ19253@nan92-1-81-57-214-146.fbx.proxad.net","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-16T06:27:11Z","receivedAt":"2007-05-16T06:27:11Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-15 23:49:52 +0200, Yann Dirson wrote:\n\n> On Tue, May 15, 2007 at 10:36:05PM +0100, Catalin Marinas wrote:\n>\n> > I think it would be useful to have a version file (probably per\n> > branch) and just upgrade when a mismatch is detected (in the\n> > __init__ function).\n>\n> Sounds reasonable, but I'd rather keep that in the config file (eg.\n> branch.<name>.stgit.formatversion).\n\nI agree that explicit versioning would be a good idea -- doing\nexplicit upgrades at well-defined points is good headache prevention.\n\nAnd I agree that the config file is a good place to put it.\n\nI'll probably have time to whip up a patch later today. I think I'll\ncall the old \"old\" format 0, the old \"new\" format 1, and then use\nsuccessive integers from then on. I'll make a single version bump for\nthe format changes you've alreay applied, and re-do the\nformat-changing patches you haven't applied yet so that they have\nversion bumping integrated.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42299","messageId":"20070516071130.GB7453@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705151301r7412109qd73a6008b888f8bc@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-16T07:11:30Z","receivedAt":"2007-05-16T07:11:30Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-15 21:01:43 +0100, Catalin Marinas wrote:\n\n> What is the impact on the bash completion for calling StGIT rather\n> than reading those files? Is it visible?\n\nYes, it's visible, but not annoying (to me anyway). The overhead is\nakin to the overhead we used to have when \"stg help\" generated the\ncommand names -- on the order of 100-200 ms, when StGIT is in the\ncache. The expensive part is to start stgit; the git calls are cheap.\nSo theoretically the completion script could duplicate the logic in\nStGIT and avoid most of the overhead, if someone wanted it badly\nenough.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42313","messageId":"b0943d9e0705160507h2b3c4a34xe9895d007c41d91a@mail.gmail.com","threadId":"7997","inReplyTo":"20070516071130.GB7453@diana.vm.bytemark.co.uk","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-16T12:07:14Z","receivedAt":"2007-05-16T12:07:14Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 16/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> On 2007-05-15 21:01:43 +0100, Catalin Marinas wrote:\n>\n> > What is the impact on the bash completion for calling StGIT rather\n> > than reading those files? Is it visible?\n>\n> Yes, it's visible, but not annoying (to me anyway). The overhead is\n> akin to the overhead we used to have when \"stg help\" generated the\n> command names -- on the order of 100-200 ms, when StGIT is in the\n> cache. The expensive part is to start stgit; the git calls are cheap.\n> So theoretically the completion script could duplicate the logic in\n> StGIT and avoid most of the overhead, if someone wanted it badly\n> enough.\n\nI did a quick test of 'stg series' with the DAG patches applied, on a\nLinux kernel repository ('du -sh .git' is 285M) with 42 patches (only\n25 applied). It constantly takes over 2 seconds to complete (compared\nto < 200ms without the DAG patches). The problem is that this delay\nwill happen for bash completion as well.\n\nIt seems that most of the time is spent in git._output_lines() called\nfrom stack.read_refs() (for git-show-ref). I attach the profiling\noutput generated by stg-prof.\n\n-- \nCatalin\n\n\n+ cross-arm\n+ init-err\n+ ll-printk\n+ compressed-head-stack\n+ armv7-uncompress\n+ armv7-tlb\n+ armv7-xenon\n+ armv7-vfpv3\n+ armv7-neon\n+ armv7-thumb2-user\n+ pb926-pci-io\n+ mpcore-smsc911x\n+ smsc911x-driver\n+ realview-eb-pci-mem\n+ realview-eb-pci-io\n+ nommu-consistent-api\n+ nommu-fault\n+ nommu-page-tlb\n+ nommu-context-id\n+ nommu-integrator\n+ nommu-realview-eb\n+ nommu-kconfig\n+ nommu-ptrace\n+ nommu-armv6\n> nommu-armv7\n- per-cpu-asids\n- arch-dsb-mmiowb\n- t2-arch-arm-clone\n- t2-function-type\n- t2-exception-handling\n- t2-unified-syntax\n- t2-exception-handling-alternative\n- t2-srs-variant\n- thumb2-kernel-port\n- errata-arm1136-364296\n- errata-arm1136-411920\n- pipt-icache-flush\n- smp-writealloc\n         3150 function calls (3112 primitive calls) in 2.229 CPU seconds\n\n   Ordered by: standard name\n\n   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n        1    0.000    0.000    2.229    2.229 <string>:1(?)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:105(Error)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:117(NoSectionError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:124(DuplicateSectionError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:131(NoOptionError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:140(InterpolationError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:148(InterpolationMissingOptionError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:161(InterpolationSyntaxError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:165(InterpolationDepthError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:176(ParsingError)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:188(MissingSectionHeaderError)\n        1    0.000    0.000    0.009    0.009 ConfigParser.py:202(RawConfigParser)\n        1    0.000    0.000    0.000    0.000 ConfigParser.py:487(ConfigParser)\n        1    0.000    0.000    0.002    0.002 ConfigParser.py:570(SafeConfigParser)\n        1    0.000    0.000    0.011    0.011 ConfigParser.py:88(?)\n        1    0.000    0.000    0.000    0.000 UserDict.py:19(__getitem__)\n        1    0.000    0.000    0.000    0.000 UserDict.py:62(__contains__)\n        1    0.000    0.000    0.000    0.000 basedir.py:2(?)\n        1    0.007    0.007    0.007    0.007 basedir.py:23(__output)\n        1    0.000    0.000    0.007    0.007 basedir.py:33(get)\n        1    0.003    0.003    0.011    0.011 common.py:2(?)\n        1    0.000    0.000    0.000    0.000 common.py:32(CmdException)\n        1    0.000    0.000    0.000    0.000 common.py:37(RevParseException)\n        1    0.000    0.000    0.006    0.006 config.py:125(config_setup)\n        1    0.000    0.000    0.000    0.000 config.py:134(ConfigOption)\n        2    0.000    0.000    0.000    0.000 config.py:137(__init__)\n        1    0.000    0.000    0.000    0.000 config.py:2(?)\n        1    0.000    0.000    0.000    0.000 config.py:24(GitConfigException)\n        1    0.000    0.000    0.000    0.000 config.py:27(GitConfig)\n        1    0.006    0.006    0.006    0.006 config.py:65(get)\n        1    0.005    0.005    0.023    0.023 git.py:180(_output_one_line)\n        2    2.029    1.015    2.067    1.034 git.py:192(_output_lines)\n        1    0.002    0.002    0.003    0.003 git.py:2(?)\n        1    0.000    0.000    0.023    0.023 git.py:288(get_head_file)\n        1    0.000    0.000    0.000    0.000 git.py:30(GitException)\n        1    0.000    0.000    0.000    0.000 git.py:39(Person)\n        1    0.000    0.000    0.000    0.000 git.py:71(Commit)\n        1    0.000    0.000    0.000    0.000 gitmergeonefile.py:2(?)\n        1    0.000    0.000    0.000    0.000 gitmergeonefile.py:27(GitMergeException)\n        1    0.001    0.001    2.228    2.228 main.py:197(main)\n        1    0.000    0.000    0.000    0.000 main.py:32(canonical_cmd)\n        1    0.000    0.000    0.015    0.015 main.py:51(__getitem__)\n        1    0.000    0.000    0.000    0.000 optparse.py:1007(_create_option_list)\n        1    0.000    0.000    0.001    0.001 optparse.py:1012(_populate_option_list)\n        1    0.000    0.000    0.000    0.000 optparse.py:1022(_init_parsing_state)\n        1    0.000    0.000    0.000    0.000 optparse.py:1036(set_usage)\n        1    0.000    0.000    0.000    0.000 optparse.py:1059(get_default_values)\n        1    0.000    0.000    0.000    0.000 optparse.py:1091(_get_args)\n        1    0.000    0.000    0.000    0.000 optparse.py:1097(parse_args)\n        1    0.000    0.000    0.000    0.000 optparse.py:1136(check_values)\n        1    0.000    0.000    0.000    0.000 optparse.py:1149(_process_args)\n        1    0.000    0.000    0.000    0.000 optparse.py:140(__init__)\n        1    0.000    0.000    0.000    0.000 optparse.py:247(__init__)\n       12    0.001    0.000    0.002    0.000 optparse.py:413(__init__)\n       12    0.000    0.000    0.000    0.000 optparse.py:432(_check_opt_strings)\n       12    0.000    0.000    0.000    0.000 optparse.py:441(_set_opt_strings)\n       12    0.000    0.000    0.000    0.000 optparse.py:462(_set_attrs)\n       12    0.000    0.000    0.000    0.000 optparse.py:480(_check_action)\n       12    0.000    0.000    0.000    0.000 optparse.py:486(_check_type)\n       12    0.000    0.000    0.000    0.000 optparse.py:504(_check_choice)\n       12    0.000    0.000    0.000    0.000 optparse.py:517(_check_dest)\n       12    0.000    0.000    0.000    0.000 optparse.py:528(_check_const)\n       12    0.000    0.000    0.000    0.000 optparse.py:534(_check_nargs)\n       12    0.000    0.000    0.000    0.000 optparse.py:543(_check_callback)\n        1    0.000    0.000    0.000    0.000 optparse.py:662(__init__)\n        1    0.000    0.000    0.000    0.000 optparse.py:748(__init__)\n        1    0.000    0.000    0.000    0.000 optparse.py:759(_create_option_mappings)\n        1    0.000    0.000    0.000    0.000 optparse.py:775(set_conflict_handler)\n        1    0.000    0.000    0.000    0.000 optparse.py:780(set_description)\n       13    0.000    0.000    0.000    0.000 optparse.py:786(_check_conflict)\n       13    0.000    0.000    0.001    0.000 optparse.py:815(add_option)\n        1    0.000    0.000    0.001    0.001 optparse.py:845(add_options)\n        1    0.000    0.000    0.001    0.001 optparse.py:975(__init__)\n        1    0.000    0.000    0.000    0.000 os.py:444(__setitem__)\n        4    0.000    0.000    0.000    0.000 popen2.py:21(_cleanup)\n        1    0.000    0.000    0.000    0.000 popen2.py:25(Popen3)\n        4    0.064    0.016    0.064    0.016 popen2.py:31(__init__)\n        1    0.000    0.000    0.000    0.000 popen2.py:7(?)\n        3    0.000    0.000    0.000    0.000 popen2.py:87(wait)\n        1    0.000    0.000    0.000    0.000 popen2.py:97(Popen4)\n        1    0.000    0.000    0.000    0.000 posixpath.py:110(basename)\n        5    0.000    0.000    0.000    0.000 posixpath.py:184(isdir)\n        4    0.000    0.000    0.000    0.000 posixpath.py:197(isfile)\n        8    0.000    0.000    0.000    0.000 posixpath.py:56(join)\n        1    0.000    0.000    0.000    0.000 posixpath.py:74(split)\n        1    0.000    0.000    2.229    2.229 profile:0(main())\n        0    0.000             0.000          profile:0(profiler)\n        1    0.001    0.001    2.163    2.163 series.py:111(func)\n        1    0.001    0.001    0.015    0.015 series.py:2(?)\n       38    0.001    0.000    0.001    0.000 series.py:88(__print_patch)\n       46    0.000    0.000    0.000    0.000 sets.py:119(__iter__)\n        1    0.000    0.000    0.000    0.000 sets.py:356(_update)\n        1    0.000    0.000    0.000    0.000 sets.py:393(ImmutableSet)\n        1    0.001    0.001    0.001    0.001 sets.py:41(?)\n        1    0.000    0.000    0.000    0.000 sets.py:418(Set)\n       72    0.001    0.000    0.001    0.000 sets.py:425(__init__)\n       89    0.001    0.000    0.001    0.000 sets.py:515(add)\n       25    0.000    0.000    0.000    0.000 sets.py:528(remove)\n        1    0.000    0.000    0.000    0.000 sets.py:564(_TemporarilyImmutableSet)\n        1    0.000    0.000    0.000    0.000 sets.py:83(BaseSet)\n       26    0.000    0.000    0.000    0.000 sets.py:99(__len__)\n        1    0.000    0.000    0.000    0.000 shutil.py:16(Error)\n        1    0.000    0.000    0.000    0.000 shutil.py:5(?)\n      896    0.017    0.000    0.028    0.000 sre.py:129(match)\n        4    0.000    0.000    0.013    0.003 sre.py:177(compile)\n      900    0.012    0.000    0.025    0.000 sre.py:216(_compile)\n        5    0.000    0.000    0.001    0.000 sre_compile.py:151(_compile_charset)\n        5    0.000    0.000    0.000    0.000 sre_compile.py:180(_optimize_charset)\n     17/4    0.001    0.000    0.002    0.001 sre_compile.py:24(_compile)\n        7    0.000    0.000    0.000    0.000 sre_compile.py:324(_simple)\n        4    0.000    0.000    0.001    0.000 sre_compile.py:331(_compile_info)\n       12    0.000    0.000    0.000    0.000 sre_compile.py:42(<lambda>)\n        8    0.000    0.000    0.000    0.000 sre_compile.py:440(isstring)\n        4    0.000    0.000    0.004    0.001 sre_compile.py:446(_code)\n        4    0.000    0.000    0.013    0.003 sre_compile.py:461(compile)\n       32    0.000    0.000    0.000    0.000 sre_parse.py:133(__len__)\n       51    0.000    0.000    0.000    0.000 sre_parse.py:137(__getitem__)\n        7    0.000    0.000    0.000    0.000 sre_parse.py:139(__setitem__)\n        7    0.000    0.000    0.000    0.000 sre_parse.py:141(__getslice__)\n       44    0.000    0.000    0.000    0.000 sre_parse.py:145(append)\n    24/11    0.001    0.000    0.001    0.000 sre_parse.py:147(getwidth)\n        4    0.000    0.000    0.000    0.000 sre_parse.py:183(__init__)\n      125    0.001    0.000    0.001    0.000 sre_parse.py:187(__next)\n       59    0.001    0.000    0.001    0.000 sre_parse.py:200(match)\n       98    0.002    0.000    0.003    0.000 sre_parse.py:206(get)\n       23    0.000    0.000    0.000    0.000 sre_parse.py:215(isident)\n        4    0.000    0.000    0.001    0.000 sre_parse.py:221(isname)\n        2    0.000    0.000    0.000    0.000 sre_parse.py:240(_class_escape)\n        6    0.000    0.000    0.000    0.000 sre_parse.py:269(_escape)\n     10/4    0.000    0.000    0.008    0.002 sre_parse.py:312(_parse_sub)\n     10/4    0.003    0.000    0.008    0.002 sre_parse.py:367(_parse)\n        4    0.000    0.000    0.009    0.002 sre_parse.py:614(parse)\n        4    0.000    0.000    0.000    0.000 sre_parse.py:75(__init__)\n        6    0.000    0.000    0.000    0.000 sre_parse.py:80(opengroup)\n        6    0.000    0.000    0.000    0.000 sre_parse.py:91(closegroup)\n       17    0.000    0.000    0.000    0.000 sre_parse.py:98(__init__)\n        1    0.000    0.000    0.000    0.000 stack.py:112(StgitObject)\n        1    0.000    0.000    0.000    0.000 stack.py:115(_set_dir)\n        6    0.000    0.000    0.000    0.000 stack.py:117(_dir)\n        1    0.000    0.000    0.000    0.000 stack.py:142(Patch)\n        1    0.004    0.004    0.008    0.008 stack.py:2(?)\n        1    0.000    0.000    0.000    0.000 stack.py:277(PatchorderCache)\n        1    0.000    0.000    0.000    0.000 stack.py:280(__init__)\n        1    0.000    0.000    0.000    0.000 stack.py:283(__invalidate)\n       59    0.001    0.000    0.001    0.000 stack.py:286(__cache)\n        2    0.000    0.000    0.000    0.000 stack.py:293(read_file)\n        1    0.000    0.000    0.000    0.000 stack.py:30(StackException)\n       59    0.005    0.000    0.007    0.000 stack.py:315(cmp)\n        1    0.014    0.014    0.110    0.110 stack.py:327(read_refs)\n        1    0.000    0.000    0.000    0.000 stack.py:33(FilterUntil)\n        1    0.002    0.002    2.006    2.006 stack.py:342(unapplied_patches)\n        1    0.026    0.026    0.037    0.037 stack.py:358(sort_applied_patches)\n        1    0.000    0.000    0.000    0.000 stack.py:382(AppliedCache)\n        1    0.000    0.000    0.000    0.000 stack.py:385(__init__)\n        2    0.000    0.000    2.161    1.080 stack.py:389(get_applied)\n        1    0.000    0.000    0.000    0.000 stack.py:392(get_unapplied)\n        1    0.000    0.000    0.000    0.000 stack.py:417(__invalidate)\n        3    0.000    0.000    0.000    0.000 stack.py:420(__cached)\n        3    0.001    0.000    2.161    0.720 stack.py:422(__cache)\n        1    0.000    0.000    0.000    0.000 stack.py:434(Series)\n        1    0.000    0.000    0.031    0.031 stack.py:437(__init__)\n        1    0.000    0.000    0.000    0.000 stack.py:479(get_branch)\n        1    0.000    0.000    0.000    0.000 stack.py:497(get_current)\n        2    0.000    0.000    2.161    1.080 stack.py:507(get_applied)\n        1    0.000    0.000    0.000    0.000 stack.py:510(get_unapplied)\n        1    0.000    0.000    0.000    0.000 stack.py:513(get_hidden)\n        2    0.000    0.000    0.000    0.000 stack.py:646(is_initialised)\n        8    0.000    0.000    0.000    0.000 stat.py:29(S_IFMT)\n        5    0.000    0.000    0.000    0.000 stat.py:45(S_ISDIR)\n        3    0.000    0.000    0.000    0.000 stat.py:54(S_ISREG)\n        1    0.000    0.000    0.000    0.000 templates.py:2(?)\n        1    0.000    0.000    0.000    0.000 utils.py:107(strip_prefix)\n        1    0.000    0.000    0.000    0.000 utils.py:153(EditorException)\n        1    0.001    0.001    0.001    0.001 utils.py:2(?)\n\n\n   Ordered by: standard name\n\nFunction                                              called...\n<string>:1(?)                                          main.py:197(main)(1)    2.228\nConfigParser.py:105(Error)                             --\nConfigParser.py:117(NoSectionError)                    --\nConfigParser.py:124(DuplicateSectionError)             --\nConfigParser.py:131(NoOptionError)                     --\nConfigParser.py:140(InterpolationError)                --\nConfigParser.py:148(InterpolationMissingOptionError)   --\nConfigParser.py:161(InterpolationSyntaxError)          --\nConfigParser.py:165(InterpolationDepthError)           --\nConfigParser.py:176(ParsingError)                      --\nConfigParser.py:188(MissingSectionHeaderError)         --\nConfigParser.py:202(RawConfigParser)                   sre.py:177(compile)(2)    0.013\nConfigParser.py:487(ConfigParser)                      --\nConfigParser.py:570(SafeConfigParser)                  sre.py:177(compile)(1)    0.013\nConfigParser.py:88(?)                                  ConfigParser.py:105(Error)(1)    0.000\n                                                       ConfigParser.py:117(NoSectionError)(1)    0.000\n                                                       ConfigParser.py:124(DuplicateSectionError)(1)    0.000\n                                                       ConfigParser.py:131(NoOptionError)(1)    0.000\n                                                       ConfigParser.py:140(InterpolationError)(1)    0.000\n                                                       ConfigParser.py:148(InterpolationMissingOptionError)(1)    0.000\n                                                       ConfigParser.py:161(InterpolationSyntaxError)(1)    0.000\n                                                       ConfigParser.py:165(InterpolationDepthError)(1)    0.000\n                                                       ConfigParser.py:176(ParsingError)(1)    0.000\n                                                       ConfigParser.py:188(MissingSectionHeaderError)(1)    0.000\n                                                       ConfigParser.py:202(RawConfigParser)(1)    0.009\n                                                       ConfigParser.py:487(ConfigParser)(1)    0.000\n                                                       ConfigParser.py:570(SafeConfigParser)(1)    0.002\nUserDict.py:19(__getitem__)                            --\nUserDict.py:62(__contains__)                           --\nbasedir.py:2(?)                                        --\nbasedir.py:23(__output)                                --\nbasedir.py:33(get)                                     UserDict.py:62(__contains__)(1)    0.000\n                                                       basedir.py:23(__output)(1)    0.007\ncommon.py:2(?)                                         common.py:32(CmdException)(1)    0.000\n                                                       common.py:37(RevParseException)(1)    0.000\n                                                       stack.py:2(?)(1)    0.008\n                                                       utils.py:2(?)(1)    0.001\ncommon.py:32(CmdException)                             --\ncommon.py:37(RevParseException)                        --\nconfig.py:125(config_setup)                            config.py:65(get)(1)    0.006\n                                                       os.py:444(__setitem__)(1)    0.000\nconfig.py:134(ConfigOption)                            --\nconfig.py:137(__init__)                                --\nconfig.py:2(?)                                         basedir.py:2(?)(1)    0.000\n                                                       config.py:24(GitConfigException)(1)    0.000\n                                                       config.py:27(GitConfig)(1)    0.000\n                                                       config.py:134(ConfigOption)(1)    0.000\nconfig.py:24(GitConfigException)                       --\nconfig.py:27(GitConfig)                                --\nconfig.py:65(get)                                      --\ngit.py:180(_output_one_line)                           popen2.py:31(__init__)(1)    0.064\n                                                       popen2.py:87(wait)(1)    0.000\ngit.py:192(_output_lines)                              popen2.py:31(__init__)(2)    0.064\n                                                       popen2.py:87(wait)(2)    0.000\ngit.py:2(?)                                            git.py:30(GitException)(1)    0.000\n                                                       git.py:39(Person)(1)    0.000\n                                                       git.py:71(Commit)(1)    0.000\n                                                       gitmergeonefile.py:2(?)(1)    0.000\n                                                       sets.py:41(?)(1)    0.001\n                                                       shutil.py:5(?)(1)    0.000\ngit.py:288(get_head_file)                              git.py:180(_output_one_line)(1)    0.023\n                                                       utils.py:107(strip_prefix)(1)    0.000\ngit.py:30(GitException)                                --\ngit.py:39(Person)                                      --\ngit.py:71(Commit)                                      --\ngitmergeonefile.py:2(?)                                config.py:137(__init__)(2)    0.000\n                                                       gitmergeonefile.py:27(GitMergeException)(1)    0.000\ngitmergeonefile.py:27(GitMergeException)               --\nmain.py:197(main)                                      ConfigParser.py:88(?)(1)    0.011\n                                                       UserDict.py:19(__getitem__)(1)    0.000\n                                                       config.py:125(config_setup)(1)    0.006\n                                                       main.py:32(canonical_cmd)(1)    0.000\n                                                       main.py:51(__getitem__)(1)    0.015\n                                                       optparse.py:975(__init__)(1)    0.001\n                                                       optparse.py:1097(parse_args)(1)    0.000\n                                                       posixpath.py:110(basename)(1)    0.000\n                                                       series.py:111(func)(1)    2.163\n                                                       stack.py:437(__init__)(1)    0.031\nmain.py:32(canonical_cmd)                              --\nmain.py:51(__getitem__)                                series.py:2(?)(1)    0.015\noptparse.py:1007(_create_option_list)                  optparse.py:759(_create_option_mappings)(1)    0.000\noptparse.py:1012(_populate_option_list)                optparse.py:815(add_option)(1)    0.001\n                                                       optparse.py:845(add_options)(1)    0.001\noptparse.py:1022(_init_parsing_state)                  --\noptparse.py:1036(set_usage)                            --\noptparse.py:1059(get_default_values)                   optparse.py:662(__init__)(1)    0.000\noptparse.py:1091(_get_args)                            --\noptparse.py:1097(parse_args)                           optparse.py:1059(get_default_values)(1)    0.000\n                                                       optparse.py:1091(_get_args)(1)    0.000\n                                                       optparse.py:1136(check_values)(1)    0.000\n                                                       optparse.py:1149(_process_args)(1)    0.000\noptparse.py:1136(check_values)                         --\noptparse.py:1149(_process_args)                        --\noptparse.py:140(__init__)                              --\noptparse.py:247(__init__)                              optparse.py:140(__init__)(1)    0.000\noptparse.py:413(__init__)                              optparse.py:432(_check_opt_strings)(12)    0.000\n                                                       optparse.py:441(_set_opt_strings)(12)    0.000\n                                                       optparse.py:462(_set_attrs)(12)    0.000\n                                                       optparse.py:480(_check_action)(12)    0.000\n                                                       optparse.py:486(_check_type)(12)    0.000\n                                                       optparse.py:504(_check_choice)(12)    0.000\n                                                       optparse.py:517(_check_dest)(12)    0.000\n                                                       optparse.py:528(_check_const)(12)    0.000\n                                                       optparse.py:534(_check_nargs)(12)    0.000\n                                                       optparse.py:543(_check_callback)(12)    0.000\noptparse.py:432(_check_opt_strings)                    --\noptparse.py:441(_set_opt_strings)                      --\noptparse.py:462(_set_attrs)                            --\noptparse.py:480(_check_action)                         --\noptparse.py:486(_check_type)                           --\noptparse.py:504(_check_choice)                         --\noptparse.py:517(_check_dest)                           --\noptparse.py:528(_check_const)                          --\noptparse.py:534(_check_nargs)                          --\noptparse.py:543(_check_callback)                       --\noptparse.py:662(__init__)                              --\noptparse.py:748(__init__)                              optparse.py:775(set_conflict_handler)(1)    0.000\n                                                       optparse.py:780(set_description)(1)    0.000\n                                                       optparse.py:1007(_create_option_list)(1)    0.000\noptparse.py:759(_create_option_mappings)               --\noptparse.py:775(set_conflict_handler)                  --\noptparse.py:780(set_description)                       --\noptparse.py:786(_check_conflict)                       --\noptparse.py:815(add_option)                            optparse.py:786(_check_conflict)(13)    0.000\noptparse.py:845(add_options)                           optparse.py:815(add_option)(12)    0.001\noptparse.py:975(__init__)                              optparse.py:247(__init__)(1)    0.000\n                                                       optparse.py:748(__init__)(1)    0.000\n                                                       optparse.py:1012(_populate_option_list)(1)    0.001\n                                                       optparse.py:1022(_init_parsing_state)(1)    0.000\n                                                       optparse.py:1036(set_usage)(1)    0.000\nos.py:444(__setitem__)                                 --\npopen2.py:21(_cleanup)                                 --\npopen2.py:25(Popen3)                                   --\npopen2.py:31(__init__)                                 popen2.py:21(_cleanup)(4)    0.000\npopen2.py:7(?)                                         popen2.py:25(Popen3)(1)    0.000\n                                                       popen2.py:97(Popen4)(1)    0.000\npopen2.py:87(wait)                                     --\npopen2.py:97(Popen4)                                   --\nposixpath.py:110(basename)                             posixpath.py:74(split)(1)    0.000\nposixpath.py:184(isdir)                                stat.py:45(S_ISDIR)(5)    0.000\nposixpath.py:197(isfile)                               stat.py:54(S_ISREG)(3)    0.000\nposixpath.py:56(join)                                  --\nposixpath.py:74(split)                                 --\nprofile:0(main())                                      <string>:1(?)(1)    2.229\nprofile:0(profiler)                                    profile:0(main())(1)    2.229\nseries.py:111(func)                                    series.py:88(__print_patch)(38)    0.001\n                                                       stack.py:497(get_current)(1)    0.000\n                                                       stack.py:507(get_applied)(1)    2.161\n                                                       stack.py:510(get_unapplied)(1)    0.000\n                                                       stack.py:513(get_hidden)(1)    0.000\nseries.py:2(?)                                         common.py:2(?)(1)    0.011\n                                                       optparse.py:413(__init__)(12)    0.002\nseries.py:88(__print_patch)                            --\nsets.py:119(__iter__)                                  --\nsets.py:356(_update)                                   --\nsets.py:393(ImmutableSet)                              --\nsets.py:41(?)                                          sets.py:83(BaseSet)(1)    0.000\n                                                       sets.py:393(ImmutableSet)(1)    0.000\n                                                       sets.py:418(Set)(1)    0.000\n                                                       sets.py:564(_TemporarilyImmutableSet)(1)    0.000\nsets.py:418(Set)                                       --\nsets.py:425(__init__)                                  sets.py:356(_update)(1)    0.000\nsets.py:515(add)                                       --\nsets.py:528(remove)                                    --\nsets.py:564(_TemporarilyImmutableSet)                  --\nsets.py:83(BaseSet)                                    --\nsets.py:99(__len__)                                    --\nshutil.py:16(Error)                                    --\nshutil.py:5(?)                                         shutil.py:16(Error)(1)    0.000\nsre.py:129(match)                                      sre.py:216(_compile)(896)    0.025\nsre.py:177(compile)                                    sre.py:216(_compile)(4)    0.025\nsre.py:216(_compile)                                   sre_compile.py:440(isstring)(4)    0.000\n                                                       sre_compile.py:461(compile)(4)    0.013\nsre_compile.py:151(_compile_charset)                   sre_compile.py:42(<lambda>)(6)    0.000\n                                                       sre_compile.py:180(_optimize_charset)(5)    0.000\nsre_compile.py:180(_optimize_charset)                  sre_compile.py:42(<lambda>)(6)    0.000\nsre_compile.py:24(_compile)                            sre_compile.py:24(_compile)(13)    0.002\n                                                       sre_compile.py:151(_compile_charset)(5)    0.001\n                                                       sre_compile.py:324(_simple)(7)    0.000\n                                                       sre_parse.py:137(__getitem__)(27)    0.000\nsre_compile.py:324(_simple)                            sre_parse.py:137(__getitem__)(7)    0.000\n                                                       sre_parse.py:147(getwidth)(7)    0.001\nsre_compile.py:331(_compile_info)                      sre_parse.py:133(__len__)(4)    0.000\n                                                       sre_parse.py:137(__getitem__)(3)    0.000\n                                                       sre_parse.py:147(getwidth)(4)    0.001\nsre_compile.py:42(<lambda>)                            --\nsre_compile.py:440(isstring)                           --\nsre_compile.py:446(_code)                              sre_compile.py:24(_compile)(4)    0.002\n                                                       sre_compile.py:331(_compile_info)(4)    0.001\nsre_compile.py:461(compile)                            sre_compile.py:440(isstring)(4)    0.000\n                                                       sre_compile.py:446(_code)(4)    0.004\n                                                       sre_parse.py:614(parse)(4)    0.009\nsre_parse.py:133(__len__)                              --\nsre_parse.py:137(__getitem__)                          --\nsre_parse.py:139(__setitem__)                          --\nsre_parse.py:141(__getslice__)                         sre_parse.py:98(__init__)(7)    0.000\nsre_parse.py:145(append)                               --\nsre_parse.py:147(getwidth)                             sre_parse.py:147(getwidth)(13)    0.001\nsre_parse.py:183(__init__)                             sre_parse.py:187(__next)(4)    0.001\nsre_parse.py:187(__next)                               --\nsre_parse.py:200(match)                                sre_parse.py:187(__next)(23)    0.001\nsre_parse.py:206(get)                                  sre_parse.py:187(__next)(98)    0.001\nsre_parse.py:215(isident)                              --\nsre_parse.py:221(isname)                               sre_parse.py:215(isident)(23)    0.000\nsre_parse.py:240(_class_escape)                        --\nsre_parse.py:269(_escape)                              --\nsre_parse.py:312(_parse_sub)                           sre_parse.py:200(match)(16)    0.001\n                                                       sre_parse.py:367(_parse)(10)    0.008\nsre_parse.py:367(_parse)                               sre_parse.py:80(opengroup)(6)    0.000\n                                                       sre_parse.py:91(closegroup)(6)    0.000\n                                                       sre_parse.py:98(__init__)(10)    0.000\n                                                       sre_parse.py:133(__len__)(28)    0.000\n                                                       sre_parse.py:137(__getitem__)(14)    0.000\n                                                       sre_parse.py:139(__setitem__)(7)    0.000\n                                                       sre_parse.py:141(__getslice__)(7)    0.000\n                                                       sre_parse.py:145(append)(44)    0.000\n                                                       sre_parse.py:200(match)(43)    0.001\n                                                       sre_parse.py:206(get)(94)    0.003\n                                                       sre_parse.py:221(isname)(4)    0.001\n                                                       sre_parse.py:240(_class_escape)(2)    0.000\n                                                       sre_parse.py:269(_escape)(6)    0.000\n                                                       sre_parse.py:312(_parse_sub)(6)    0.008\nsre_parse.py:614(parse)                                sre_parse.py:75(__init__)(4)    0.000\n                                                       sre_parse.py:183(__init__)(4)    0.000\n                                                       sre_parse.py:206(get)(4)    0.003\n                                                       sre_parse.py:312(_parse_sub)(4)    0.008\nsre_parse.py:75(__init__)                              --\nsre_parse.py:80(opengroup)                             --\nsre_parse.py:91(closegroup)                            --\nsre_parse.py:98(__init__)                              --\nstack.py:112(StgitObject)                              --\nstack.py:115(_set_dir)                                 --\nstack.py:117(_dir)                                     --\nstack.py:142(Patch)                                    --\nstack.py:2(?)                                          git.py:2(?)(1)    0.003\n                                                       popen2.py:7(?)(1)    0.000\n                                                       stack.py:30(StackException)(1)    0.000\n                                                       stack.py:33(FilterUntil)(1)    0.000\n                                                       stack.py:112(StgitObject)(1)    0.000\n                                                       stack.py:142(Patch)(1)    0.000\n                                                       stack.py:277(PatchorderCache)(1)    0.000\n                                                       stack.py:382(AppliedCache)(1)    0.000\n                                                       stack.py:434(Series)(1)    0.000\n                                                       templates.py:2(?)(1)    0.000\nstack.py:277(PatchorderCache)                          --\nstack.py:280(__init__)                                 stack.py:283(__invalidate)(1)    0.000\nstack.py:283(__invalidate)                             --\nstack.py:286(__cache)                                  posixpath.py:56(join)(3)    0.000\n                                                       posixpath.py:197(isfile)(3)    0.000\n                                                       stack.py:117(_dir)(3)    0.000\n                                                       stack.py:293(read_file)(2)    0.000\nstack.py:293(read_file)                                --\nstack.py:30(StackException)                            --\nstack.py:315(cmp)                                      stack.py:286(__cache)(59)    0.001\nstack.py:327(read_refs)                                git.py:192(_output_lines)(1)    2.067\n                                                       sre.py:129(match)(896)    0.028\n                                                       sre.py:177(compile)(1)    0.013\nstack.py:33(FilterUntil)                               --\nstack.py:342(unapplied_patches)                        git.py:192(_output_lines)(1)    2.067\n                                                       sets.py:119(__iter__)(19)    0.000\n                                                       sets.py:425(__init__)(46)    0.001\n                                                       sets.py:515(add)(64)    0.001\nstack.py:358(sort_applied_patches)                     popen2.py:31(__init__)(1)    0.064\n                                                       sets.py:99(__len__)(26)    0.000\n                                                       sets.py:119(__iter__)(25)    0.000\n                                                       sets.py:425(__init__)(26)    0.001\n                                                       sets.py:515(add)(25)    0.001\n                                                       sets.py:528(remove)(25)    0.000\nstack.py:382(AppliedCache)                             --\nstack.py:385(__init__)                                 stack.py:280(__init__)(1)    0.000\n                                                       stack.py:417(__invalidate)(1)    0.000\nstack.py:389(get_applied)                              stack.py:422(__cache)(2)    2.161\nstack.py:392(get_unapplied)                            stack.py:422(__cache)(1)    2.161\nstack.py:417(__invalidate)                             --\nstack.py:420(__cached)                                 --\nstack.py:422(__cache)                                  sets.py:119(__iter__)(2)    0.000\n                                                       stack.py:315(cmp)(59)    0.007\n                                                       stack.py:327(read_refs)(1)    0.110\n                                                       stack.py:342(unapplied_patches)(1)    2.006\n                                                       stack.py:358(sort_applied_patches)(1)    0.037\n                                                       stack.py:420(__cached)(3)    0.000\n                                                       stack.py:479(get_branch)(1)    0.000\nstack.py:434(Series)                                   --\nstack.py:437(__init__)                                 basedir.py:33(get)(1)    0.007\n                                                       git.py:288(get_head_file)(1)    0.023\n                                                       posixpath.py:56(join)(5)    0.000\n                                                       posixpath.py:184(isdir)(3)    0.000\n                                                       stack.py:115(_set_dir)(1)    0.000\n                                                       stack.py:117(_dir)(3)    0.000\n                                                       stack.py:385(__init__)(1)    0.000\n                                                       stack.py:646(is_initialised)(2)    0.000\nstack.py:479(get_branch)                               --\nstack.py:497(get_current)                              stack.py:507(get_applied)(1)    2.161\nstack.py:507(get_applied)                              stack.py:389(get_applied)(2)    2.161\nstack.py:510(get_unapplied)                            stack.py:392(get_unapplied)(1)    0.000\nstack.py:513(get_hidden)                               posixpath.py:197(isfile)(1)    0.000\nstack.py:646(is_initialised)                           posixpath.py:184(isdir)(2)    0.000\nstat.py:29(S_IFMT)                                     --\nstat.py:45(S_ISDIR)                                    stat.py:29(S_IFMT)(5)    0.000\nstat.py:54(S_ISREG)                                    stat.py:29(S_IFMT)(3)    0.000\ntemplates.py:2(?)                                      --\nutils.py:107(strip_prefix)                             --\nutils.py:153(EditorException)                          --\nutils.py:2(?)                                          config.py:2(?)(1)    0.000\n                                                       utils.py:153(EditorException)(1)    0.000\n\n\n"},{"id":"42328","messageId":"20070516194002.GA17805@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705160507h2b3c4a34xe9895d007c41d91a@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-16T19:40:02Z","receivedAt":"2007-05-16T19:40:02Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-16 13:07:14 +0100, Catalin Marinas wrote:\n\n> I did a quick test of 'stg series' with the DAG patches applied, on\n> a Linux kernel repository ('du -sh .git' is 285M) with 42 patches\n> (only 25 applied). It constantly takes over 2 seconds to complete\n> (compared to < 200ms without the DAG patches). The problem is that\n> this delay will happen for bash completion as well.\n\nThis is more than ten times as expensive as in my measurement.\nCurious. And the algorithm is designed so that it shouldn't take time\nproportional to the repository size, just proportional to the number\nof patches.\n\nThere are three git calls involved:\n\n  * List the references. There can't be more than a few hundred of\n    them.\n\n  * rev-list all patches, subtracting everything that's reachable from\n    the branch head. This set of commits should not be much larger\n    than the number of unapplied patches.\n\n  * rev-list the branch head, but stop walking as soon as all applied\n    patches have been seen. This set of commits should not be much\n    larger than the number of applied patches.\n\nNone of the calls should be expensive.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42330","messageId":"20070516204029.GB17805@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"20070516194002.GA17805@diana.vm.bytemark.co.uk","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-16T20:40:29Z","receivedAt":"2007-05-16T20:40:29Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-16 21:40:02 +0200, Karl Hasselström wrote:\n\n> On 2007-05-16 13:07:14 +0100, Catalin Marinas wrote:\n>\n> > I did a quick test of 'stg series' with the DAG patches applied,\n> > on a Linux kernel repository ('du -sh .git' is 285M) with 42\n> > patches (only 25 applied). It constantly takes over 2 seconds to\n> > complete (compared to < 200ms without the DAG patches). The\n> > problem is that this delay will happen for bash completion as\n> > well.\n>\n> This is more than ten times as expensive as in my measurement.\n> Curious. And the algorithm is designed so that it shouldn't take\n> time proportional to the repository size, just proportional to the\n> number of patches.\n\nI set up a kernel repository with 100 applied and 100 unapplied\npatches:\n\n  $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n  $ cd linux-2.6/\n  $ stg init\n  $ for ((i=0;i<200;i++)); do stg new p$(printf '%03d' $i) -m \"Patch $i\"; done\n  $ stg goto p099\n\nThen repeatedly:\n\n  $ time stg series >/dev/null\n\nThis gives times such as\n\n  real    0m0.247s\n  user    0m0.156s\n  sys     0m0.060s\n\n  real    0m0.147s\n  user    0m0.088s\n  sys     0m0.036s\n\n  real    0m0.153s\n  user    0m0.088s\n  sys     0m0.032s\n\n  real    0m0.144s\n  user    0m0.104s\n  sys     0m0.024s\n\nTab completion also feels like it takes 0.1-0.2 seconds -- which it\nshould, since it's implemented with series, applied, and unapplied,\nallow which do the same amount of work.\n\nBut my kernel repository is _much_ smaller than yours:\n\n  $ du -sh .git\n  183M    .git\n\nDo you perchance have a bunch of loose objects in there?\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42391","messageId":"b0943d9e0705170543g4d8c7542p6b44a4e6fa54676a@mail.gmail.com","threadId":"7997","inReplyTo":"20070516204029.GB17805@diana.vm.bytemark.co.uk","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-17T12:43:35Z","receivedAt":"2007-05-17T12:43:35Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 16/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> I set up a kernel repository with 100 applied and 100 unapplied\n> patches:\n[...]\n> Then repeatedly:\n>\n>   $ time stg series >/dev/null\n\nI ran 'git repack -a -d' and 'git prune'. There are no other objects\napart from the generated pack:\n\n$ du -sh .git\n211M    .git\n\nAnd then repeatedly 'time stg series > /dev/null':\n\nreal    0m1.638s\nuser    0m1.422s\nsys     0m0.088s\n\nreal    0m2.542s\nuser    0m1.436s\nsys     0m0.078s\n\nreal    0m2.916s\nuser    0m1.424s\nsys     0m0.083s\n\nreal    0m2.940s\nuser    0m1.425s\nsys     0m0.081s\n\nreal    0m1.614s\nuser    0m1.421s\nsys     0m0.081s\n\nreal    0m1.587s\nuser    0m1.423s\nsys     0m0.081s\n\nreal    0m2.653s\nuser    0m1.427s\nsys     0m0.075s\n\n> But my kernel repository is _much_ smaller than yours:\n>\n>   $ du -sh .git\n>   183M    .git\n>\n> Do you perchance have a bunch of loose objects in there?\n\nIt got smaller after repacking but it is still bigger than yours.\nMaybe the reason is that I have 14 branches with various patches, some\nof them just for historical reasons but going back to 2.6.12. There\nare also several commits generated for the patch logs.\n\nThe CPU is a P4 at 2.5GHz and the 'stg series' operation seems to be\nCPU bound rather than IO. I'm also using Python 2.3 on this PC and for\nthis reason I changed 2 generator constructs (x for x in ...) with\nlist comprehension (see the attached patch).\n\n-- \nCatalin\n\n\ncommit f7cc1f7fa6b2ee4f42e812970760ba5db5d2cafc\nAuthor: Catalin Marinas <catalin.marinas@gmail.com>\nDate:   Wed May 16 13:08:45 2007 +0100\n\n    refresh\tb5003aa7bb5ec384e1dbe2887c07885a791384c2\n\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 13eb692..0ef26b8 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -349,8 +349,8 @@ def unapplied_patches(ref2hash):\n     unapplied = Set()\n     for line in git._output_lines(\n         'git-rev-list --stdin',\n-        ('%s%s\\n' % (['', '^'][ref == None], sha1)\n-         for ref, sha1 in ref2hash.iteritems())):\n+        ['%s%s\\n' % (['', '^'][ref == None], sha1)\n+         for ref, sha1 in ref2hash.iteritems()]):\n         for ref in hash2refs.get(line.strip(), []):\n             unapplied.add(ref)\n     return unapplied\n@@ -364,7 +364,7 @@ def sort_applied_patches(ref2hash):\n         if r != None:\n             hash2refs.setdefault(h, Set()).add(r)\n \n-    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)\n+    missing = Set([ref for ref in ref2hash.iterkeys() if ref != None])\n     if not missing:\n         return []\n     applied = []\n"},{"id":"42404","messageId":"20070517145724.GA905@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705170543g4d8c7542p6b44a4e6fa54676a@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-17T14:57:24Z","receivedAt":"2007-05-17T14:57:24Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-17 13:43:35 +0100, Catalin Marinas wrote:\n\n> I ran 'git repack -a -d' and 'git prune'. There are no other objects\n> apart from the generated pack:\n>\n> $ du -sh .git\n> 211M    .git\n>\n> And then repeatedly 'time stg series > /dev/null':\n\nHmm, it seems there is a problem then. :-(\n\n> It got smaller after repacking but it is still bigger than yours.\n> Maybe the reason is that I have 14 branches with various patches,\n> some of them just for historical reasons but going back to 2.6.12.\n> There are also several commits generated for the patch logs.\n\nOK. That shouldn't matter, though, since that extra history shouldn't\nbe examined anyway.\n\n> The CPU is a P4 at 2.5GHz and the 'stg series' operation seems to be\n> CPU bound rather than IO. I'm also using Python 2.3 on this PC and\n> for this reason I changed 2 generator constructs (x for x in ...)\n> with list comprehension (see the attached patch).\n\nI don't think that's the problem, since those lists are both small.\n\nThe only possibility I can think of that might explain this is that\nsome of your unapplied patches are attached to a place in the commit\nDAG that's far away from the branch head (e.g. you have rebased to\nsome entirely different place since you last had them applied), so\nthat \"git-rev-list patch ^branch\" outputs a large part of the commit\nDAG.\n\nCould you put counters in unapplied_patches() and\nsort_applied_patches() to see how many lines each of them reads from\ngit-rev-list? The expected number (if it had taken just a little time,\nlike it did for me) is a small constant times the number of patches in\nboth cases.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42432","messageId":"b0943d9e0705171351j1a86a4f4ge0283c522a351645@mail.gmail.com","threadId":"7997","inReplyTo":"20070517145724.GA905@diana.vm.bytemark.co.uk","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-17T20:51:19Z","receivedAt":"2007-05-17T20:51:19Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 17/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> The only possibility I can think of that might explain this is that\n> some of your unapplied patches are attached to a place in the commit\n> DAG that's far away from the branch head (e.g. you have rebased to\n> some entirely different place since you last had them applied), so\n> that \"git-rev-list patch ^branch\" outputs a large part of the commit\n> DAG.\n\nThat's probably the case. I have patches that I haven't rebased for\nmonths but I keep them in case they might be needed in the future.\nThat's the reason for the hide/unhide commands. Anyway, I'm not yet\nprepared to give up my current workflow.\n\nI haven't tried to understand your patch yet but the unapplied patches\nwill never be in a linear DAG similar to the applied patches. Because\nof this, we need to keep their order in a file anyway and we might not\nneed to run git-rev-list (BTW, how do you preserve the unapplied\npatches order with the DAG implementation?).\n\n> Could you put counters in unapplied_patches() and\n> sort_applied_patches() to see how many lines each of them reads from\n> git-rev-list? The expected number (if it had taken just a little time,\n> like it did for me) is a small constant times the number of patches in\n> both cases.\n\nI'll do this tomorrow to confirm but that's probably the cause of the slow-down.\n\n-- \nCatalin\n"},{"id":"42448","messageId":"20070518063015.GA13516@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705171351j1a86a4f4ge0283c522a351645@mail.gmail.com","subject":"Re: [StGIT PATCH] Don't use patches/<branch>/current","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-18T06:30:15Z","receivedAt":"2007-05-18T06:30:15Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-17 21:51:19 +0100, Catalin Marinas wrote:\n\n> On 17/05/07, Karl Hasselström <kha@treskal.com> wrote:\n>\n> > The only possibility I can think of that might explain this is\n> > that some of your unapplied patches are attached to a place in the\n> > commit DAG that's far away from the branch head (e.g. you have\n> > rebased to some entirely different place since you last had them\n> > applied), so that \"git-rev-list patch ^branch\" outputs a large\n> > part of the commit DAG.\n>\n> That's probably the case. I have patches that I haven't rebased for\n> months but I keep them in case they might be needed in the future.\n\nThat's probably it, then. I'll need to come up with a clever scheme to\nmake that case cheap again.\n\n> That's the reason for the hide/unhide commands.\n\nAh.\n\n> Anyway, I'm not yet prepared to give up my current workflow.\n\nI didn't intend to make that workflow painful, so don't despair yet --\nthere might be a way to make it work.\n\n> I haven't tried to understand your patch yet but the unapplied\n> patches will never be in a linear DAG similar to the applied\n> patches.\n\nI know.\n\n> Because of this, we need to keep their order in a file anyway and we\n> might not need to run git-rev-list (BTW, how do you preserve the\n> unapplied patches order with the DAG implementation?).\n\nThe order of _all_ patches is kept in the \"patchorder\" file. This file\nis consulted only when the relative order of two unapplied patches\nneed to be established. (Applied patches are sorted by the DAG, and\napplied patches always precede unapplied patches.)\n\nI would like to avoid keeping a record of known-to-be-unapplied\npatches; while this is nowhere near as important as not keeping a\nrecord of applied patches, it would still provide an opening for the\nuser to confuse StGIT.\n\nBut I believe I have a cunning plan ...\n\n> > Could you put counters in unapplied_patches() and\n> > sort_applied_patches() to see how many lines each of them reads\n> > from git-rev-list? The expected number (if it had taken just a\n> > little time, like it did for me) is a small constant times the\n> > number of patches in both cases.\n>\n> I'll do this tomorrow to confirm but that's probably the cause of\n> the slow-down.\n\nThanks for hanging in there.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42560","messageId":"20070519000451.4906.87089.stgit@yoghurt","threadId":"7997","inReplyTo":"20070516062711.GA7235@diana.vm.bytemark.co.uk","subject":"[StGIT PATCH 0/5] Metadata format versioning","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:09:46Z","receivedAt":"2007-05-19T00:09:46Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-16 08:27:11 +0200, Karl Hasselström wrote:\n\n> I'll probably have time to whip up a patch later today.\n\nOK, so this turned out to be a slight miscalculation. But here it is,\nwith a test! The test contains two small tarballs, so I had to whip up\nbinary patch support as well.\n\n> I'll make a single version bump for the format changes you've alreay\n> applied, and re-do the format-changing patches you haven't applied\n> yet so that they have version bumping integrated.\n\nThis contains a single version bump (1 -> 2) for the applied changes,\nbut i haven't updated the DAG patch to use this yet. That's for\nanother day.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"42561","messageId":"20070519000951.4906.61408.stgit@yoghurt","threadId":"7997","inReplyTo":"20070519000451.4906.87089.stgit@yoghurt","subject":"[StGIT PATCH 1/5] Fix config caching so that get, set, get works","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:09:51Z","receivedAt":"2007-05-19T00:09:51Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"The config caching was never invalidated or updated, which caused the\ntwo gets to always return the same value regardless of the value\npassed to set.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/config.py |    3 +++\n 1 files changed, 3 insertions(+), 0 deletions(-)\n\ndiff --git a/stgit/config.py b/stgit/config.py\nindex 79cd12f..2fd1273 100644\n--- a/stgit/config.py\n+++ b/stgit/config.py\n@@ -99,12 +99,15 @@ class GitConfig:\n \n     def rename_section(self, from_name, to_name):\n         self.__run('git-repo-config --rename-section', [from_name, to_name])\n+        self.__cache.clear()\n \n     def set(self, name, value):\n         self.__run('git-repo-config', [name, value])\n+        self.__cache[name] = value\n \n     def unset(self, name):\n         self.__run('git-repo-config --unset', [name])\n+        self.__cache[name] = None\n \n     def sections_matching(self, regexp):\n         \"\"\"Takes a regexp with a single group, matches it against all\n"},{"id":"42562","messageId":"20070519000956.4906.54444.stgit@yoghurt","threadId":"7997","inReplyTo":"20070519000451.4906.87089.stgit@yoghurt","subject":"[StGIT PATCH 2/5] Have only a single command in each test_expect_failure","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:09:56Z","receivedAt":"2007-05-19T00:09:56Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Otherwise, we can't know which one failed.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n t/t1000-branch-create.sh |   71 ++++++++++++++++++++++++++++------------------\n 1 files changed, 43 insertions(+), 28 deletions(-)\n\ndiff --git a/t/t1000-branch-create.sh b/t/t1000-branch-create.sh\nindex 58209e7..cca5504 100755\n--- a/t/t1000-branch-create.sh\n+++ b/t/t1000-branch-create.sh\n@@ -12,57 +12,72 @@ Exercises the \"stg branch\" commands.\n \n stg init\n \n+test_expect_success \\\n+    'Create a spurious refs/patches/ entry' '\n+    find .git -name foo | xargs rm -rf &&\n+    touch .git/refs/patches/foo\n+'\n+\n test_expect_failure \\\n-    'Try to create an stgit branch with a spurious refs/patches/ entry' \\\n-    'find .git -name foo | xargs rm -rf &&\n-     touch .git/refs/patches/foo &&\n-     stg branch -c foo\n+    'Try to create an stgit branch with a spurious refs/patches/ entry' '\n+    stg branch -c foo\n+'\n+\n+test_expect_success \\\n+    'Check that no part of the branch was created' '\n+    test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/patches/foo\" &&\n+    ( grep foo .git/HEAD; test $? = 1 )\n '\n \n test_expect_success \\\n-    'Check no part of the branch was created' \\\n-    'test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/patches/foo\" &&\n-     ( grep foo .git/HEAD; test $? = 1 )\n+    'Create a spurious patches/ entry' '\n+    find .git -name foo | xargs rm -rf &&\n+    touch .git/patches/foo\n '\n \n test_expect_failure \\\n-    'Try to create an stgit branch with a spurious patches/ entry' \\\n-    'find .git -name foo | xargs rm -rf &&\n-     touch .git/patches/foo &&\n-     stg branch -c foo\n+    'Try to create an stgit branch with a spurious patches/ entry' '\n+    stg branch -c foo\n '\n \n test_expect_success \\\n-    'Check no part of the branch was created' \\\n-    'test \"`find .git -name foo | tee /dev/stderr`\" = \".git/patches/foo\" &&\n-     ( grep foo .git/HEAD; test $? = 1 )\n+    'Check that no part of the branch was created' '\n+    test \"`find .git -name foo | tee /dev/stderr`\" = \".git/patches/foo\" &&\n+    ( grep foo .git/HEAD; test $? = 1 )\n+'\n+\n+test_expect_success \\\n+    'Create a git branch' '\n+    find .git -name foo | xargs rm -rf &&\n+    cp .git/refs/heads/master .git/refs/heads/foo\n '\n \n test_expect_failure \\\n-    'Try to create an stgit branch with an existing git branch by that name' \\\n-    'find .git -name foo | xargs rm -rf &&\n-     cp .git/refs/heads/master .git/refs/heads/foo &&\n-     stg branch -c foo\n+    'Try to create an stgit branch with an existing git branch by that name' '\n+    stg branch -c foo\n '\n \n test_expect_success \\\n-    'Check no part of the branch was created' \\\n-    'test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/heads/foo\" &&\n-     ( grep foo .git/HEAD; test $? = 1 )\n+    'Check that no part of the branch was created' '\n+    test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/heads/foo\" &&\n+    ( grep foo .git/HEAD; test $? = 1 )\n '\n \n+test_expect_success \\\n+    'Create an invalid refs/heads/ entry' '\n+    find .git -name foo | xargs rm -rf &&\n+    touch .git/refs/heads/foo\n+'\n \n test_expect_failure \\\n-    'Try to create an stgit branch with an invalid refs/heads/ entry' \\\n-    'find .git -name foo | xargs rm -rf &&\n-     touch .git/refs/heads/foo &&\n-     stg branch -c foo\n+    'Try to create an stgit branch with an invalid refs/heads/ entry' '\n+    stg branch -c foo\n '\n \n test_expect_success \\\n-    'Check no part of the branch was created' \\\n-    'test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/heads/foo\" &&\n-     ( grep foo .git/HEAD; test $? = 1 )\n+    'Check that no part of the branch was created' '\n+    test \"`find .git -name foo | tee /dev/stderr`\" = \".git/refs/heads/foo\" &&\n+    ( grep foo .git/HEAD; test $? = 1 )\n '\n \n test_done\n"},{"id":"42563","messageId":"20070519001001.4906.11625.stgit@yoghurt","threadId":"7997","inReplyTo":"20070519000451.4906.87089.stgit@yoghurt","subject":"[StGIT PATCH 3/5] Upgrade old StGIT branches to new-format metadata","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:10:02Z","receivedAt":"2007-05-19T00:10:02Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"There used to be a \"stg branch --convert\" command that switched\nbetween \"old\" and \"new\" format metadata. But my recent metadata\ncleanup patches introduced a \"new new\" format, and more are hopefully\non the way, so it was time to start versioning the metadata format\nexplicitly in order to avoid future headaches.\n\nThis patch removes the \"stg branch --convert\" command, and makes StGIT\nautomatically upgrade older formats to the latest format. It stores\nthe format (as an integer) in the config file. The current metadata\nformat version number is 2 (the \"old\" format is 0, and the \"new\"\nformat is 1).\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n Documentation/stg-branch.txt |    4 -\n stgit/commands/branch.py     |   11 ---\n stgit/stack.py               |  153 ++++++++++++++++++++++++------------------\n 3 files changed, 86 insertions(+), 82 deletions(-)\n\ndiff --git a/Documentation/stg-branch.txt b/Documentation/stg-branch.txt\nindex 25c9c19..25ca951 100644\n--- a/Documentation/stg-branch.txt\n+++ b/Documentation/stg-branch.txt\n@@ -20,7 +20,6 @@ SYNOPSIS\n 'stg' branch --protect [<branch>]\n 'stg' branch --unprotect [<branch>]\n 'stg' branch --delete [--force] <branch>\n-'stg' branch --convert\n \n DESCRIPTION\n -----------\n@@ -91,9 +90,6 @@ the \"master\" branch if it exists.\n Branch \"master\" is treated specially (see bug #8732), in that only the\n StGIT metadata are removed, the GIT branch itself is not destroyed.\n \n-'stg' branch --convert::\n-\tSwitch current stack between old and new format.\n-\n OPTIONS\n -------\n \ndiff --git a/stgit/commands/branch.py b/stgit/commands/branch.py\nindex b043c69..5e7b0df 100644\n--- a/stgit/commands/branch.py\n+++ b/stgit/commands/branch.py\n@@ -45,9 +45,6 @@ options = [make_option('-c', '--create',\n            make_option('--clone',\n                        help = 'clone the contents of the current branch',\n                        action = 'store_true'),\n-           make_option('--convert',\n-                       help = 'switch between old and new format branches',\n-                       action = 'store_true'),\n            make_option('--delete',\n                        help = 'delete an existing development branch',\n                        action = 'store_true'),\n@@ -186,14 +183,6 @@ def func(parser, options, args):\n \n         return\n \n-    elif options.convert:\n-\n-        if len(args) != 0:\n-            parser.error('incorrect number of arguments')\n-\n-        crt_series.convert()\n-        return\n-\n     elif options.delete:\n \n         if len(args) != 1:\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex d9c4b99..223f3ee 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -273,6 +273,79 @@ class Patch(StgitObject):\n         self._set_field('log', value)\n         self.__update_log_ref(value)\n \n+# The current StGIT metadata format version.\n+FORMAT_VERSION = 2\n+\n+def format_version_key(branch):\n+    return 'branch.%s.stgitformatversion' % branch\n+\n+def update_to_current_format_version(branch, git_dir):\n+    \"\"\"Update a potentially older StGIT directory structure to the\n+    latest version. Note: This function should depend as little as\n+    possible on external functions that may change during a format\n+    version bump, since it must remain able to process older formats.\"\"\"\n+\n+    branch_dir = os.path.join(git_dir, 'patches', branch)\n+    def get_format_version():\n+        \"\"\"Return the integer format version number, or None if the\n+        branch doesn't have any StGIT metadata at all, of any version.\"\"\"\n+        fv = config.get(format_version_key(branch))\n+        if fv:\n+            # Great, there's an explicitly recorded format version\n+            # number, which means that the branch is initialized and\n+            # of that exact version.\n+            return int(fv)\n+        elif os.path.isdir(os.path.join(branch_dir, 'patches')):\n+            # There's a .git/patches/<branch>/patches dirctory, which\n+            # means this is an initialized version 1 branch.\n+            return 1\n+        elif os.path.isdir(branch_dir):\n+            # There's a .git/patches/<branch> directory, which means\n+            # this is an initialized version 0 branch.\n+            return 0\n+        else:\n+            # The branch doesn't seem to be initialized at all.\n+            return None\n+    def set_format_version(v):\n+        config.set(format_version_key(branch), '%d' % v)\n+    def mkdir(d):\n+        if not os.path.isdir(d):\n+            os.makedirs(d)\n+    def rm(f):\n+        if os.path.exists(f):\n+            os.remove(f)\n+\n+    # Update 0 -> 1.\n+    if get_format_version() == 0:\n+        mkdir(os.path.join(branch_dir, 'trash'))\n+        patch_dir = os.path.join(branch_dir, 'patches')\n+        mkdir(patch_dir)\n+        refs_dir = os.path.join(git_dir, 'refs', 'patches', branch)\n+        mkdir(refs_dir)\n+        for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()\n+                      + file(os.path.join(branch_dir, 'applied')).readlines()):\n+            patch = patch.strip()\n+            os.rename(os.path.join(branch_dir, patch),\n+                      os.path.join(patch_dir, patch))\n+            Patch(patch, patch_dir, refs_dir).update_top_ref()\n+        set_format_version(1)\n+\n+    # Update 1 -> 2.\n+    if get_format_version() == 1:\n+        desc_file = os.path.join(branch_dir, 'description')\n+        if os.path.isfile(desc_file):\n+            desc = read_string(desc_file)\n+            if desc:\n+                config.set('branch.%s.description' % branch, desc)\n+            rm(desc_file)\n+        rm(os.path.join(branch_dir, 'current'))\n+        rm(os.path.join(git_dir, 'refs', 'bases', branch))\n+        set_format_version(2)\n+\n+    # Make sure we're at the latest version.\n+    if not get_format_version() in [None, FORMAT_VERSION]:\n+        raise StackException('Branch %s is at format version %d, expected %d'\n+                             % (branch, get_format_version(), FORMAT_VERSION))\n \n class Series(StgitObject):\n     \"\"\"Class including the operations on series\n@@ -290,6 +363,11 @@ class Series(StgitObject):\n             raise StackException, 'GIT tree not initialised: %s' % ex\n \n         self._set_dir(os.path.join(self.__base_dir, 'patches', self.__name))\n+\n+        # Update the branch to the latest format version if it is\n+        # initialized, but don't touch it if it isn't.\n+        update_to_current_format_version(self.__name, self.__base_dir)\n+\n         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',\n                                        self.__name)\n \n@@ -299,19 +377,9 @@ class Series(StgitObject):\n \n         # where this series keeps its patches\n         self.__patch_dir = os.path.join(self._dir(), 'patches')\n-        if not os.path.isdir(self.__patch_dir):\n-            self.__patch_dir = self._dir()\n-\n-        # if no __refs_dir, create and populate it (upgrade old repositories)\n-        if self.is_initialised() and not os.path.isdir(self.__refs_dir):\n-            os.makedirs(self.__refs_dir)\n-            for patch in self.get_applied() + self.get_unapplied():\n-                self.get_patch(patch).update_top_ref()\n \n         # trash directory\n         self.__trash_dir = os.path.join(self._dir(), 'trash')\n-        if self.is_initialised() and not os.path.isdir(self.__trash_dir):\n-            os.makedirs(self.__trash_dir)\n \n     def __patch_name_valid(self, name):\n         \"\"\"Raise an exception if the patch name is not valid.\n@@ -410,19 +478,13 @@ class Series(StgitObject):\n         return 'branch.%s.description' % self.get_branch()\n \n     def get_description(self):\n-        # Fall back to the .git/patches/<branch>/description file if\n-        # the config variable is unset.\n-        return (config.get(self.__branch_descr())\n-                or self._get_field('description') or '')\n+        return config.get(self.__branch_descr())\n \n     def set_description(self, line):\n         if line:\n             config.set(self.__branch_descr(), line)\n         else:\n             config.unset(self.__branch_descr())\n-        # Delete the old .git/patches/<branch>/description file if it\n-        # exists.\n-        self._set_field('description', None)\n \n     def get_parent_remote(self):\n         value = config.get('branch.%s.remote' % self.__name)\n@@ -503,15 +565,16 @@ class Series(StgitObject):\n     def is_initialised(self):\n         \"\"\"Checks if series is already initialised\n         \"\"\"\n-        return os.path.isdir(self.__patch_dir)\n+        return bool(config.get(format_version_key(self.get_branch())))\n \n     def init(self, create_at=False, parent_remote=None, parent_branch=None):\n         \"\"\"Initialises the stgit series\n         \"\"\"\n-        if os.path.exists(self.__patch_dir):\n-            raise StackException, self.__patch_dir + ' already exists'\n-        if os.path.exists(self.__refs_dir):\n-            raise StackException, self.__refs_dir + ' already exists'\n+        if self.is_initialised():\n+            raise StackException, '%s already initialized' % self.get_branch()\n+        for d in [self._dir(), self.__refs_dir]:\n+            if os.path.exists(d):\n+                raise StackException, '%s already exists' % d\n \n         if (create_at!=False):\n             git.create_branch(self.__name, create_at)\n@@ -522,45 +585,10 @@ class Series(StgitObject):\n \n         self.create_empty_field('applied')\n         self.create_empty_field('unapplied')\n-        os.makedirs(os.path.join(self._dir(), 'patches'))\n         os.makedirs(self.__refs_dir)\n         self._set_field('orig-base', git.get_head())\n \n-    def convert(self):\n-        \"\"\"Either convert to use a separate patch directory, or\n-        unconvert to place the patches in the same directory with\n-        series control files\n-        \"\"\"\n-        if self.__patch_dir == self._dir():\n-            print 'Converting old-style to new-style...',\n-            sys.stdout.flush()\n-\n-            self.__patch_dir = os.path.join(self._dir(), 'patches')\n-            os.makedirs(self.__patch_dir)\n-\n-            for p in self.get_applied() + self.get_unapplied():\n-                src = os.path.join(self._dir(), p)\n-                dest = os.path.join(self.__patch_dir, p)\n-                os.rename(src, dest)\n-\n-            print 'done'\n-\n-        else:\n-            print 'Converting new-style to old-style...',\n-            sys.stdout.flush()\n-\n-            for p in self.get_applied() + self.get_unapplied():\n-                src = os.path.join(self.__patch_dir, p)\n-                dest = os.path.join(self._dir(), p)\n-                os.rename(src, dest)\n-\n-            if not os.listdir(self.__patch_dir):\n-                os.rmdir(self.__patch_dir)\n-                print 'done'\n-            else:\n-                print 'Patch directory %s is not empty.' % self.__patch_dir\n-\n-            self.__patch_dir = self._dir()\n+        config.set(format_version_key(self.get_branch()), str(FORMAT_VERSION))\n \n     def rename(self, to_name):\n         \"\"\"Renames a series\n@@ -666,15 +694,6 @@ class Series(StgitObject):\n             if os.path.exists(self._dir()+'/orig-base'):\n                 os.remove(self._dir()+'/orig-base')\n \n-            # Remove obsolete files that StGIT no longer uses, but\n-            # that might still be around if this is an old repository.\n-            for obsolete in ([os.path.join(self._dir(), fn)\n-                              for fn in ['current', 'description']]\n-                             + [os.path.join(self.__base_dir,\n-                                             'refs', 'bases', self.__name)]):\n-                if os.path.exists(obsolete):\n-                    os.remove(obsolete)\n-\n             if not os.listdir(self.__patch_dir):\n                 os.rmdir(self.__patch_dir)\n             else:\n"},{"id":"42564","messageId":"20070519001007.4906.25065.stgit@yoghurt","threadId":"7997","inReplyTo":"20070519000451.4906.87089.stgit@yoghurt","subject":"[StGIT PATCH 4/5] Test the format version upgrade code","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:10:07Z","receivedAt":"2007-05-19T00:10:07Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"This test contains tarballs of repositories created with older\nversions of StGIT. It also contains the script used to generate them,\nbut at some point we will lose the ability to easily run old versions\n-- for example, if git changes incompatibly -- so tarballs will be the\nonly practical option for sufficiently old versions.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n t/t4000-upgrade.sh           |   40 ++++++++++++++++++++++++\n t/t4000-upgrade/.gitignore   |    4 ++\n t/t4000-upgrade/0.12.tar.gz  |  Bin\n t/t4000-upgrade/0.8.tar.gz   |  Bin\n t/t4000-upgrade/make-repo.sh |   71 ++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 115 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t4000-upgrade.sh b/t/t4000-upgrade.sh\nnew file mode 100755\nindex 0000000..8a308fb\n--- /dev/null\n+++ b/t/t4000-upgrade.sh\n@@ -0,0 +1,40 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Karl Hasselström\n+#\n+\n+test_description='Make sure that we can use old StGIT repositories'\n+\n+. ./test-lib.sh\n+\n+for ver in 0.12 0.8; do\n+\n+    tar zxf ../t4000-upgrade/$ver.tar.gz\n+    cd $ver\n+\n+    test_expect_success \\\n+        \"v$ver: Check the list of applied and unapplied patches\" '\n+        [ \"$(echo $(stg applied))\" = \"p0 p1 p2\" ] &&\n+        [ \"$(echo $(stg unapplied))\" = \"p3 p4\" ]\n+    '\n+\n+    test_expect_success \\\n+        \"v$ver: Make sure the 'description' file is no longer there\" '\n+        [ ! -e .git/patches/master/description ] &&\n+        [ \"$(echo $(git config branch.master.description))\" = \"cool branch\" ]\n+    '\n+\n+    test_expect_success \\\n+        \"v$ver: Make sure the 'current' file is no longer there\" '\n+        [ ! -e .git/patches/master/current ]\n+    '\n+\n+    test_expect_success \\\n+        \"v$ver: Make sure the base ref is no longer there\" '\n+        ! git show-ref --verify --quiet refs/bases/master\n+    '\n+\n+    cd ..\n+done\n+\n+test_done\ndiff --git a/t/t4000-upgrade/.gitignore b/t/t4000-upgrade/.gitignore\nnew file mode 100644\nindex 0000000..d412eb6\n--- /dev/null\n+++ b/t/t4000-upgrade/.gitignore\n@@ -0,0 +1,4 @@\n+/stgit-0.8\n+/stgit-0.12\n+/0.8\n+/0.12\ndiff --git a/t/t4000-upgrade/0.12.tar.gz b/t/t4000-upgrade/0.12.tar.gz\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..b183fcb1983eaa11c1a2eba3f0bb47b5c1c845c8\nGIT binary patch\nliteral 11077\n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\nHi12>^a3NeI\n\nliteral 0\nHc$@<O00001\n\ndiff --git a/t/t4000-upgrade/0.8.tar.gz b/t/t4000-upgrade/0.8.tar.gz\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..7a7d3c2f8d916dc79c55f0f0b2426005224aca97\nGIT binary patch\nliteral 8224\n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\nS0001J1N;{VbU3j97y$r>Aaw))\n\nliteral 0\nHc$@<O00001\n\ndiff --git a/t/t4000-upgrade/make-repo.sh b/t/t4000-upgrade/make-repo.sh\nnew file mode 100644\nindex 0000000..98b5020\n--- /dev/null\n+++ b/t/t4000-upgrade/make-repo.sh\n@@ -0,0 +1,71 @@\n+# This script makes several versions of a small test repository that\n+# can be used for testing the format version upgrade code.\n+\n+LANG=C\n+LC_ALL=C\n+PAGER=cat\n+TZ=UTC\n+export LANG LC_ALL PAGER TZ\n+unset AUTHOR_DATE\n+unset AUTHOR_EMAIL\n+unset AUTHOR_NAME\n+unset COMMIT_AUTHOR_EMAIL\n+unset COMMIT_AUTHOR_NAME\n+unset GIT_ALTERNATE_OBJECT_DIRECTORIES\n+unset GIT_AUTHOR_DATE\n+GIT_AUTHOR_EMAIL=author@example.com\n+GIT_AUTHOR_NAME='A U Thor'\n+unset GIT_COMMITTER_DATE\n+GIT_COMMITTER_EMAIL=committer@example.com\n+GIT_COMMITTER_NAME='C O Mitter'\n+unset GIT_DIFF_OPTS\n+unset GIT_DIR\n+unset GIT_EXTERNAL_DIFF\n+unset GIT_INDEX_FILE\n+unset GIT_OBJECT_DIRECTORY\n+unset SHA1_FILE_DIRECTORIES\n+unset SHA1_FILE_DIRECTORY\n+export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME\n+export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME\n+\n+for ver in 0.12 0.8; do\n+    if [ -e $ver.tar.gz ]; then continue; fi\n+\n+    # Get the required stgit version.\n+    (\n+        cd ../..\n+        git archive --format=tar --prefix=stgit-$ver/ v$ver\n+    ) | tar xf -\n+\n+    # Set up a repository.\n+    mkdir $ver\n+    cd $ver\n+    git init\n+    touch foo\n+    git add foo\n+    git commit -m 'Initial commit'\n+\n+    # Use the old stgit.\n+    (\n+        pwd\n+        PATH=../stgit-$ver:$PATH\n+\n+        stg --version\n+        stg init\n+        echo 'cool branch' > .git/patches/master/description\n+\n+        for i in 0 1 2 3 4; do\n+            stg new p$i -m \"Patch $i\"\n+            echo \"Line $i\" >> foo\n+            stg refresh\n+        done\n+        stg pop -n 2\n+    )\n+\n+    # Reduce the number of small files.\n+    git gc\n+\n+    # Make a tarball.\n+    cd ..\n+    tar zcf $ver.tar.gz $ver\n+done\n"},{"id":"42565","messageId":"20070519001012.4906.86287.stgit@yoghurt","threadId":"7997","inReplyTo":"20070519000451.4906.87089.stgit@yoghurt","subject":"[StGIT PATCH 5/5] Add --binary flag to commands that generate diffs","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-19T00:10:12Z","receivedAt":"2007-05-19T00:10:12Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"This just passes the --binary option to git-diff-*, which causes the\ngenerated diffs to contain an applyable diff even when binary files\ndiffer. It's necessary to do this if you want to mail patches to\nbinary files.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/commands/diff.py   |    6 +++++-\n stgit/commands/export.py |    5 ++++-\n stgit/commands/mail.py   |    6 +++++-\n stgit/git.py             |   16 ++++++++++++----\n 4 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/stgit/commands/diff.py b/stgit/commands/diff.py\nindex 8678a0a..d3e1190 100644\n--- a/stgit/commands/diff.py\n+++ b/stgit/commands/diff.py\n@@ -44,6 +44,9 @@ shows the specified patch (defaulting to the current one).\"\"\"\n options = [make_option('-r', '--range',\n                        metavar = 'rev1[..[rev2]]', dest = 'revs',\n                        help = 'show the diff between revisions'),\n+           make_option('--binary',\n+                       help = 'output a diff even for binary files',\n+                       action = 'store_true'),\n            make_option('-s', '--stat',\n                        help = 'show the stat instead of the diff',\n                        action = 'store_true')]\n@@ -79,6 +82,7 @@ def func(parser, options, args):\n     if options.stat:\n         print git.diffstat(args, git_id(rev1), git_id(rev2))\n     else:\n-        diff_str = git.diff(args, git_id(rev1), git_id(rev2))\n+        diff_str = git.diff(args, git_id(rev1), git_id(rev2),\n+                            binary = options.binary)\n         if diff_str:\n             pager(diff_str)\ndiff --git a/stgit/commands/export.py b/stgit/commands/export.py\nindex 79b8630..20d8f67 100644\n--- a/stgit/commands/export.py\n+++ b/stgit/commands/export.py\n@@ -62,6 +62,9 @@ options = [make_option('-d', '--dir',\n                        help = 'Use FILE as a template'),\n            make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n+           make_option('--binary',\n+                       help = 'output a diff even for binary files',\n+                       action = 'store_true'),\n            make_option('-s', '--stdout',\n                        help = 'dump the patches to the standard output',\n                        action = 'store_true')]\n@@ -172,7 +175,7 @@ def func(parser, options, args):\n         # write the diff\n         git.diff(rev1 = patch.get_bottom(),\n                  rev2 = patch.get_top(),\n-                 out_fd = f)\n+                 out_fd = f, binary = options.binary)\n         if not options.stdout:\n             f.close()\n         patch_no += 1\ndiff --git a/stgit/commands/mail.py b/stgit/commands/mail.py\nindex 151a408..2fcaa5f 100644\n--- a/stgit/commands/mail.py\n+++ b/stgit/commands/mail.py\n@@ -120,6 +120,9 @@ options = [make_option('-a', '--all',\n                        help = 'username for SMTP authentication'),\n            make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n+           make_option('--binary',\n+                       help = 'output a diff even for binary files',\n+                       action = 'store_true'),\n            make_option('-m', '--mbox',\n                        help = 'generate an mbox file instead of sending',\n                        action = 'store_true')]\n@@ -390,7 +393,8 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):\n                  # for backward template compatibility\n                  'endofheaders': '',\n                  'diff':         git.diff(rev1 = git_id('%s//bottom' % patch),\n-                                          rev2 = git_id('%s//top' % patch)),\n+                                          rev2 = git_id('%s//top' % patch),\n+                                          binary = options.binary),\n                  'diffstat':     git.diffstat(rev1 = git_id('%s//bottom'%patch),\n                                               rev2 = git_id('%s//top' % patch)),\n                  # for backward template compatibility\ndiff --git a/stgit/git.py b/stgit/git.py\nindex 837f927..86630ce 100644\n--- a/stgit/git.py\n+++ b/stgit/git.py\n@@ -771,20 +771,28 @@ def status(files = None, modified = False, new = False, deleted = False,\n         else:\n             print '%s' % fs[1]\n \n-def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None):\n+def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None,\n+         binary = False):\n     \"\"\"Show the diff between rev1 and rev2\n     \"\"\"\n     if not files:\n         files = []\n \n+    args = []\n+    if binary:\n+        args.append('--binary')\n+\n     if rev1 and rev2:\n-        diff_str = _output(['git-diff-tree', '-p', rev1, rev2, '--'] + files)\n+        diff_str = _output(['git-diff-tree', '-p'] + args\n+                           + [rev1, rev2, '--'] + files)\n     elif rev1 or rev2:\n         refresh_index()\n         if rev2:\n-            diff_str = _output(['git-diff-index', '-p', '-R', rev2, '--'] + files)\n+            diff_str = _output(['git-diff-index', '-p', '-R']\n+                               + args + [rev2, '--'] + files)\n         else:\n-            diff_str = _output(['git-diff-index', '-p', rev1, '--'] + files)\n+            diff_str = _output(['git-diff-index', '-p']\n+                               + args + [rev1, '--'] + files)\n     else:\n         diff_str = ''\n \n"},{"id":"42972","messageId":"b0943d9e0705220515m7eb380cdw277847cf07382761@mail.gmail.com","threadId":"7997","inReplyTo":"20070519001012.4906.86287.stgit@yoghurt","subject":"Re: [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2007-05-22T12:15:13Z","receivedAt":"2007-05-22T12:15:13Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"On 19/05/07, Karl Hasselström <kha@treskal.com> wrote:\n> This just passes the --binary option to git-diff-*, which causes the\n> generated diffs to contain an applyable diff even when binary files\n> differ. It's necessary to do this if you want to mail patches to\n> binary files.\n\nI applied this patch but is there anything wrong if we have this\noption on by default, at least for some commands? Maybe we don't need\nit for 'show' and 'diff' but we definitely need it for 'mail' and\n'export'.\n\nThere is also git.apply_diff() which calls git.diff(). This is first\ntried when pushing a patch and followed by a three-way merged if it\nfails. I think we should always have the --binary option in this case.\n\n-- \nCatalin\n"},{"id":"42975","messageId":"20070522133129.GB12626@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"b0943d9e0705220515m7eb380cdw277847cf07382761@mail.gmail.com","subject":"Re: [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-05-22T13:31:29Z","receivedAt":"2007-05-22T13:31:29Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-05-22 13:15:13 +0100, Catalin Marinas wrote:\n\n> On 19/05/07, Karl Hasselström <kha@treskal.com> wrote:\n>\n> > This just passes the --binary option to git-diff-*, which causes\n> > the generated diffs to contain an applyable diff even when binary\n> > files differ. It's necessary to do this if you want to mail\n> > patches to binary files.\n>\n> I applied this patch but is there anything wrong if we have this\n> option on by default, at least for some commands? Maybe we don't\n> need it for 'show' and 'diff' but we definitely need it for 'mail'\n> and 'export'.\n\nI'd be fine with that.\n\n> There is also git.apply_diff() which calls git.diff(). This is first\n> tried when pushing a patch and followed by a three-way merged if it\n> fails. I think we should always have the --binary option in this\n> case.\n\nYes, that sounds good.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"44572","messageId":"20070610094322.12000.56284.stgit@bill","threadId":"7997","inReplyTo":"20070518063015.GA13516@diana.vm.bytemark.co.uk","subject":"[StGIT PATCH 0/6] New and improved DAG appliedness series","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:54:47Z","receivedAt":"2007-06-10T09:54:47Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"This took a while, but here it is. (Actually, I finished this about a\nweek ago, but had no Internet connection so I couldn't send it.)\n\nThe series starts with basically the same DAG appliedness test as\nbefore, with the same known performance bug. Later on in the series,\nthe mechanism is changed to one that doesn't have the bug. I kept the\nintermediate, slow state because the changesets read better that way,\nand because the new mechanism is more complicated than the old so it\nmight be useful to be able to compare their output in case some bug\nturns up further down the road.\n\nTo test the performance, I used a script (which I've unfortunately\nmisplaced) that (in the kernel repository) reset to one point 10000\ncommits in the past and one 5000 commits in the past, pushed a few\npatches at each spot, and then created 100 applied and 100 unapplied\npatches on top of upstream HEAD. This triggers the performance bug\nwith the first algorithm since we have unapplied commits very far from\nHEAD.\n\nBoth algorithms are documented in the patches that introduce them.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"44573","messageId":"20070610095455.12000.43959.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 1/6] Verify patch status during the test","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:54:55Z","receivedAt":"2007-06-10T09:54:55Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n t/t1200-push-modified.sh |   55 +++++++++++++++++++++++++++-------------------\n 1 files changed, 32 insertions(+), 23 deletions(-)\n\ndiff --git a/t/t1200-push-modified.sh b/t/t1200-push-modified.sh\nindex 6769667..aa4ffd0 100755\n--- a/t/t1200-push-modified.sh\n+++ b/t/t1200-push-modified.sh\n@@ -19,23 +19,26 @@ specify --merged, then rollback and retry with the correct flag.'\n test_create_repo foo\n \n test_expect_success \\\n-    'Clone tree and setup changes' \\\n-    \"stg clone foo bar &&\n-     (cd bar && stg new p1 -m p1\n-      printf 'a\\nc\\n' > file && stg add file && stg refresh &&\n-      stg new p2 -m p2\n-      printf 'a\\nb\\nc\\n' > file && stg refresh\n-     )\n-\"\n+    'Clone tree and setup changes' '\n+    stg clone foo bar &&\n+    (\n+        cd bar && stg new p1 -m p1\n+        printf \"a\\nc\\n\" > file && stg add file && stg refresh &&\n+        stg new p2 -m p2 &&\n+        printf \"a\\nb\\nc\\n\" > file && stg refresh &&\n+        [ \"$(echo $(stg applied))\" = \"p1 p2\" ] &&\n+        [ \"$(echo $(stg unapplied))\" = \"\" ]\n+    )\n+'\n \n test_expect_success \\\n-    'Port those patches to orig tree' \\\n-    '(cd foo &&\n-      GIT_DIR=../bar/.git git-format-patch --stdout \\\n-          $(cd ../bar && stg id base@master)..HEAD |\n-      git-am -3 -k\n-     )\n-    '\n+    'Port those patches to orig tree' '\n+    (\n+        cd foo &&\n+        GIT_DIR=../bar/.git git-format-patch --stdout \\\n+          $(cd ../bar && stg id base@master)..HEAD | git-am -3 -k\n+    )\n+'\n \n test_expect_success \\\n     'Pull to sync with parent, preparing for the problem' \\\n@@ -51,15 +54,21 @@ test_expect_failure \\\n \"\n \n test_expect_success \\\n-    'Rollback the push' \\\n-    \"(cd bar && stg push --undo\n-     )\n-\"\n+    'Rollback the push' '\n+    (\n+        cd bar && stg push --undo &&\n+        [ \"$(echo $(stg applied))\" = \"\" ] &&\n+        [ \"$(echo $(stg unapplied))\" = \"p1 p2\" ]\n+    )\n+'\n \n test_expect_success \\\n-    'Push those patches while checking they were merged upstream' \\\n-    \"(cd bar && stg push --merged --all\n-     )\n-\"\n+    'Push those patches while checking they were merged upstream' '\n+    (\n+        cd bar && stg push --merged --all\n+        [ \"$(echo $(stg applied))\" = \"p1 p2\" ] &&\n+        [ \"$(echo $(stg unapplied))\" = \"\" ]\n+    )\n+'\n \n test_done\n"},{"id":"44574","messageId":"20070610095502.12000.56056.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 2/6] Make use of the get_patch() utility function","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:55:02Z","receivedAt":"2007-06-10T09:55:02Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"We already had it, but no one was using it\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/stack.py |   27 +++++++++++++--------------\n 1 files changed, 13 insertions(+), 14 deletions(-)\n\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 7a06458..0a486bd 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -404,7 +404,7 @@ class Series(StgitObject):\n         crt = self.get_current()\n         if not crt:\n             return None\n-        return Patch(crt, self.__patch_dir, self.__refs_dir)\n+        return self.get_patch(crt)\n \n     def get_current(self):\n         \"\"\"Return the name of the topmost patch, or None if there is\n@@ -678,7 +678,7 @@ class Series(StgitObject):\n                 raise StackException, \\\n                       'Cannot delete: the series still contains patches'\n             for p in patches:\n-                Patch(p, self.__patch_dir, self.__refs_dir).delete()\n+                self.get_patch(p).delete()\n \n             # remove the trash directory\n             for fname in os.listdir(self.__trash_dir):\n@@ -732,7 +732,7 @@ class Series(StgitObject):\n         if not name:\n             raise StackException, 'No patches applied'\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n \n         descr = patch.get_description()\n         if not (message or descr):\n@@ -798,7 +798,7 @@ class Series(StgitObject):\n         name = self.get_current()\n         assert(name)\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n         old_bottom = patch.get_old_bottom()\n         old_top = patch.get_old_top()\n \n@@ -839,7 +839,7 @@ class Series(StgitObject):\n         if name == None:\n             name = make_patch_name(descr, self.patch_exists)\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n         patch.create()\n \n         if bottom:\n@@ -919,7 +919,7 @@ class Series(StgitObject):\n         for name in names:\n             assert(name in unapplied)\n \n-            patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+            patch = self.get_patch(name)\n \n             head = top\n             bottom = patch.get_bottom()\n@@ -988,8 +988,7 @@ class Series(StgitObject):\n         patches detected to have been applied. The state of the tree\n         is restored to the original one\n         \"\"\"\n-        patches = [Patch(name, self.__patch_dir, self.__refs_dir)\n-                   for name in names]\n+        patches = [self.get_patch(name) for name in names]\n         patches.reverse()\n \n         merged = []\n@@ -1008,7 +1007,7 @@ class Series(StgitObject):\n         unapplied = self.get_unapplied()\n         assert(name in unapplied)\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n \n         head = git.get_head()\n         bottom = patch.get_bottom()\n@@ -1084,7 +1083,7 @@ class Series(StgitObject):\n         name = self.get_current()\n         assert(name)\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n         old_bottom = patch.get_old_bottom()\n         old_top = patch.get_old_top()\n \n@@ -1110,7 +1109,7 @@ class Series(StgitObject):\n         applied.reverse()\n         assert(name in applied)\n \n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n \n         if git.get_head_file() == self.get_branch():\n             if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):\n@@ -1142,7 +1141,7 @@ class Series(StgitObject):\n         \"\"\"Returns True if the patch is empty\n         \"\"\"\n         self.__patch_name_valid(name)\n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        patch = self.get_patch(name)\n         bottom = patch.get_bottom()\n         top = patch.get_top()\n \n@@ -1171,14 +1170,14 @@ class Series(StgitObject):\n             self.hide_patch(newname)\n \n         if oldname in unapplied:\n-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)\n+            self.get_patch(oldname).rename(newname)\n             unapplied[unapplied.index(oldname)] = newname\n \n             f = file(self.__unapplied_file, 'w+')\n             f.writelines([line + '\\n' for line in unapplied])\n             f.close()\n         elif oldname in applied:\n-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)\n+            self.get_patch(oldname).rename(newname)\n \n             applied[applied.index(oldname)] = newname\n \n"},{"id":"44575","messageId":"20070610095509.12000.48275.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 3/6] Compute patch appliedness from commit DAG","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:55:09Z","receivedAt":"2007-06-10T09:55:09Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Don't rely on cached metadata in the \"applied\" and \"unapplied\" files\nto tell which patches are applied. Instead, consider the patches\nreachable from the branch head to be applied, and the rest unapplied.\n\nThe order of the applied patches is also taken from the DAG, but we\ncan't do that for the unapplied patches. So the patch order is saved\nto a file whenever it changes, and that file is consulted whenever we\nneed to compute the order of the unapplied patches.\n\nThe point of this excercise is to let users do things such as \"git\nreset\" without confusing stgit. This gives incrased flexibility to\npower users, and increased safety to other users. The advantages come\nfrom the removal of redundant metadata: it is no longer possible for\nStGIT's appliedness status to get out of sync with the underlying git\ncommit DAG.\n\nThis is how the appliedness and order is computed:\n\n  * First, a single call to git-show-ref gives the hashes of all\n    patches and the branch head.\n\n  * Then, \"git-rev-list patch1 patch2 patch3 ^branch\" lists a small\n    set of hashes that contains all the unapplied patches and none of\n    the applied patches.\n\n  * Last, \"git-rev-list head\" lists all commits in the branch. The\n    applied patches are listed in the correct order.\n\nThis is efficient because none of the two rev-list calls need to look\nat more than a small part of the DAG. The first call returns a small\nset of commits, and the last call is abandoned before it has time to\nlook far back in the DAG.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/commands/commit.py  |    8 -\n stgit/commands/float.py   |    2 \n stgit/commands/imprt.py   |    2 \n stgit/commands/refresh.py |    2 \n stgit/commands/sync.py    |    2 \n stgit/git.py              |    5 +\n stgit/stack.py            |  333 +++++++++++++++++++++++++++++----------------\n t/t4000-upgrade.sh        |    6 +\n 8 files changed, 227 insertions(+), 133 deletions(-)\n\ndiff --git a/stgit/commands/commit.py b/stgit/commands/commit.py\nindex 2b8d7ce..5450112 100644\n--- a/stgit/commands/commit.py\n+++ b/stgit/commands/commit.py\n@@ -52,14 +52,8 @@ def func(parser, options, args):\n     if crt_series.get_protected():\n         raise CmdException, 'This branch is protected.  Commit is not permitted'\n \n-    crt_head = git.get_head()\n-\n     out.start('Committing %d patches' % len(applied))\n-\n-    crt_series.pop_patch(applied[0])\n-    git.switch(crt_head)\n-\n     for patch in applied:\n-        crt_series.delete_patch(patch)\n+        crt_series.delete_patch_data(patch)\n \n     out.done()\ndiff --git a/stgit/commands/float.py b/stgit/commands/float.py\nindex 0e32f6b..8ba76d5 100644\n--- a/stgit/commands/float.py\n+++ b/stgit/commands/float.py\n@@ -48,7 +48,7 @@ def func(parser, options, args):\n     check_head_top_equal()\n \n     unapplied = crt_series.get_unapplied()\n-    applied = crt_series.get_applied()\n+    applied = list(crt_series.get_applied()) # a copy, since we'll modify it\n     all = unapplied + applied\n \n     if options.series:\ndiff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py\nindex 0089a8b..b7a34fa 100644\n--- a/stgit/commands/imprt.py\n+++ b/stgit/commands/imprt.py\n@@ -294,7 +294,7 @@ def __create_patch(filename, message, author_name, author_email,\n         git.apply_patch(diff = diff, base = git_id(options.base))\n     else:\n         git.apply_patch(diff = diff)\n-    crt_series.refresh_patch(edit = options.edit,\n+    crt_series.refresh_patch(patch, edit = options.edit,\n                              show_patch = options.showpatch)\n     out.done()\n \ndiff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py\nindex 77dcbda..d560951 100644\n--- a/stgit/commands/refresh.py\n+++ b/stgit/commands/refresh.py\n@@ -131,7 +131,7 @@ def func(parser, options, args):\n \n         if autoresolved == 'yes':\n             resolved_all()\n-        crt_series.refresh_patch(files = args,\n+        crt_series.refresh_patch(patch, files = args,\n                                  message = options.message,\n                                  edit = options.edit,\n                                  show_patch = options.showpatch,\ndiff --git a/stgit/commands/sync.py b/stgit/commands/sync.py\nindex 8359061..a42eeac 100644\n--- a/stgit/commands/sync.py\n+++ b/stgit/commands/sync.py\n@@ -161,7 +161,7 @@ def func(parser, options, args):\n         if git.local_changes(verbose = False):\n             # index (cache) already updated by the git merge. The\n             # backup information was already reset above\n-            crt_series.refresh_patch(cache_update = False, backup = False,\n+            crt_series.refresh_patch(p, cache_update = False, backup = False,\n                                      log = 'sync')\n             out.done('updated')\n         else:\ndiff --git a/stgit/git.py b/stgit/git.py\nindex 845c712..703425b 100644\n--- a/stgit/git.py\n+++ b/stgit/git.py\n@@ -189,8 +189,11 @@ def _output_one_line(cmd, file_desc = None):\n                                                 p.childerr.read().strip())\n     return output\n \n-def _output_lines(cmd):\n+def _output_lines(cmd, input = []):\n     p=popen2.Popen3(cmd, True)\n+    for line in input:\n+        p.tochild.write(line)\n+    p.tochild.close()\n     lines = p.fromchild.readlines()\n     if p.wait():\n         raise GitException, '%s failed (%s)' % (str(cmd),\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex 0a486bd..e622f68 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -18,12 +18,13 @@ along with this program; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n \"\"\"\n \n-import sys, os, re\n+import sys, os, popen2, re\n \n from stgit.utils import *\n from stgit import git, basedir, templates\n from stgit.config import config\n from shutil import copyfile\n+from sets import Set\n \n \n # stack exception class\n@@ -274,7 +275,7 @@ class Patch(StgitObject):\n         self.__update_log_ref(value)\n \n # The current StGIT metadata format version.\n-FORMAT_VERSION = 2\n+FORMAT_VERSION = 3\n \n def format_version_key(branch):\n     return 'branch.%s.stgitformatversion' % branch\n@@ -343,11 +344,173 @@ def update_to_current_format_version(branch, git_dir):\n         rm(os.path.join(git_dir, 'refs', 'bases', branch))\n         set_format_version(2)\n \n+    # Update 2 -> 3.\n+    if get_format_version() == 2:\n+        patchorder = file(os.path.join(branch_dir, 'patchorder'), 'w')\n+        for pf in ['applied', 'unapplied']:\n+            pfn = os.path.join(branch_dir, pf)\n+            if not os.path.isfile(pfn):\n+                continue\n+            for line in file(pfn):\n+                line = line.strip()\n+                if line:\n+                    patchorder.write(line + '\\n')\n+            rm(pfn)\n+        patchorder.close()\n+        set_format_version(3)\n+\n     # Make sure we're at the latest version.\n     if not get_format_version() in [None, FORMAT_VERSION]:\n         raise StackException('Branch %s is at format version %d, expected %d'\n                              % (branch, get_format_version(), FORMAT_VERSION))\n \n+class PatchorderCache:\n+    \"\"\"An object that keeps track of the patch order for a series, as\n+    saved in its patchorder file.\"\"\"\n+    def __init__(self, series):\n+        self.__series = series\n+        self.__invalidate()\n+    def __invalidate(self):\n+        self.__patchnames = None\n+        self.__position = None\n+    def __cache(self):\n+        if self.__patchnames != None:\n+            return # already cached\n+\n+        self.__patchnames = []\n+        self.__position = {}\n+        pof = os.path.join(self.__series._dir(), 'patchorder')\n+        if os.path.isfile(pof):\n+            for line in file(pof):\n+                name = line.strip()\n+                assert not name in self.__position\n+                self.__position[name] = len(self.__patchnames)\n+                self.__patchnames.append(name)\n+    def set_patchorder(self, new_order):\n+        self.__invalidate()\n+        f = file(os.path.join(self.__series._dir(), 'patchorder'), 'w')\n+        for name in new_order:\n+            f.write('%s\\n' % name)\n+        f.close()\n+    def cmp(self, name1, name2):\n+        \"\"\"Compare two patch names to see which patch comes first. If\n+        both patches are listed in the patchorder file, sort them by\n+        the order they appear there; if one is listed and the other\n+        not, the listed patch goes first; and if neither is listed,\n+        sort them by their names.\"\"\"\n+        self.__cache()\n+        largepos = len(self.__patchnames)\n+        pos1 = self.__position.get(name1, largepos)\n+        pos2 = self.__position.get(name2, largepos)\n+        return cmp((pos1, name1), (pos2, name2))\n+\n+def read_refs(branch):\n+    \"\"\"Return a mapping from patches and branch head to hashes for a\n+    given branch. The patches are listed by name; the branch head is\n+    None.\"\"\"\n+    refs = {}\n+    patchpat = re.compile(r'^refs/patches/%s/([^\\.]+)$' % branch)\n+    for line in git._output_lines('git-show-ref'):\n+        sha1, ref = line.split()\n+        m = re.match(patchpat, ref)\n+        if m:\n+            refs[m.group(1)] = sha1\n+        elif ref == 'refs/heads/%s' % branch:\n+            refs[None] = sha1\n+    return refs\n+\n+def unapplied_patches(ref2hash):\n+    \"\"\"Given a map of patch names (and the branch head, keyed by None)\n+    to hashes, return the set of unapplied patches.\"\"\"\n+    hash2refs = {}\n+    for r, h in ref2hash.iteritems():\n+        hash2refs.setdefault(h, Set()).add(r)\n+\n+    unapplied = Set()\n+    for line in git._output_lines(\n+        'git-rev-list --stdin',\n+        ('%s%s\\n' % (['', '^'][ref == None], sha1)\n+         for ref, sha1 in ref2hash.iteritems())):\n+        for ref in hash2refs.get(line.strip(), []):\n+            unapplied.add(ref)\n+    return unapplied\n+\n+def sort_applied_patches(ref2hash):\n+    \"\"\"Given a map of patch names (and the branch head, keyed by None)\n+    to hashes, return a list with the applied patches in stack order.\n+    All patches in the map must be applied.\"\"\"\n+    hash2refs = {}\n+    for r, h in ref2hash.iteritems():\n+        if r != None:\n+            hash2refs.setdefault(h, Set()).add(r)\n+\n+    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)\n+    if not missing:\n+        return []\n+    applied = []\n+    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)\n+    for line in grl.fromchild:\n+        for ref in hash2refs.get(line.strip(), []):\n+            applied.append(ref)\n+            missing.remove(ref)\n+        if not missing:\n+            applied.reverse()\n+            return applied\n+\n+    raise StackException, 'Could not find patches: %s' % ', '.join(missing)\n+\n+class AppliedCache:\n+    \"\"\"An object that keeps track of the appliedness and order of the\n+    patches in a patch series.\"\"\"\n+    def __init__(self, series):\n+        self.__series = series\n+        self.__order = PatchorderCache(series)\n+        self.__invalidate()\n+    def get_applied(self):\n+        self.__cache()\n+        return self.__applied\n+    def get_unapplied(self):\n+        self.__cache()\n+        return self.__unapplied\n+    def rename(self, oldname, newname):\n+        \"\"\"Rename a patch.\"\"\"\n+        self.__cache()\n+        for lst in (self.__applied, self.__unapplied):\n+            try:\n+                lst[lst.index(oldname)] = newname\n+            except ValueError:\n+                pass # lst.index() couldn't find the index\n+            else:\n+                self.__write_patchorder()\n+                return\n+        raise StackException, 'Unknown patch \"%s\"' % oldname\n+    def __write_patchorder(self):\n+        self.__order.set_patchorder(self.get_applied() + self.get_unapplied())\n+    def set_patchorder(self, new_order):\n+        self.__order.set_patchorder(new_order)\n+        self.refresh()\n+    def refresh(self):\n+        \"\"\"Re-read patch appliedness info, and write patch order to\n+        disk.\"\"\"\n+        self.__invalidate()\n+        self.__write_patchorder()\n+    def __invalidate(self):\n+        self.__applied = None\n+        self.__unapplied = None\n+    def __cached(self):\n+        return (self.__applied != None)\n+    def __cache(self):\n+        if self.__cached():\n+            return\n+        patches = read_refs(self.__series.get_branch())\n+        unapplied = unapplied_patches(patches)\n+        for patch in unapplied:\n+            del patches[patch]\n+        self.__applied = sort_applied_patches(patches)\n+        self.__unapplied = list(unapplied)\n+        self.__unapplied.sort(self.__order.cmp)\n+\n+\n class Series(StgitObject):\n     \"\"\"Class including the operations on series\n     \"\"\"\n@@ -372,8 +535,6 @@ class Series(StgitObject):\n         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',\n                                        self.__name)\n \n-        self.__applied_file = os.path.join(self._dir(), 'applied')\n-        self.__unapplied_file = os.path.join(self._dir(), 'unapplied')\n         self.__hidden_file = os.path.join(self._dir(), 'hidden')\n \n         # where this series keeps its patches\n@@ -382,6 +543,8 @@ class Series(StgitObject):\n         # trash directory\n         self.__trash_dir = os.path.join(self._dir(), 'trash')\n \n+        self.__applied_cache = AppliedCache(self)\n+\n     def __patch_name_valid(self, name):\n         \"\"\"Raise an exception if the patch name is not valid.\n         \"\"\"\n@@ -409,11 +572,7 @@ class Series(StgitObject):\n     def get_current(self):\n         \"\"\"Return the name of the topmost patch, or None if there is\n         no such patch.\"\"\"\n-        try:\n-            applied = self.get_applied()\n-        except StackException:\n-            # No \"applied\" file: branch is not initialized.\n-            return None\n+        applied = self.get_applied()\n         try:\n             return applied[-1]\n         except IndexError:\n@@ -421,20 +580,10 @@ class Series(StgitObject):\n             return None\n \n     def get_applied(self):\n-        if not os.path.isfile(self.__applied_file):\n-            raise StackException, 'Branch \"%s\" not initialised' % self.__name\n-        f = file(self.__applied_file)\n-        names = [line.strip() for line in f.readlines()]\n-        f.close()\n-        return names\n+        return self.__applied_cache.get_applied()\n \n     def get_unapplied(self):\n-        if not os.path.isfile(self.__unapplied_file):\n-            raise StackException, 'Branch \"%s\" not initialised' % self.__name\n-        f = file(self.__unapplied_file)\n-        names = [line.strip() for line in f.readlines()]\n-        f.close()\n-        return names\n+        return self.__applied_cache.get_unapplied()\n \n     def get_hidden(self):\n         if not os.path.isfile(self.__hidden_file):\n@@ -446,12 +595,12 @@ class Series(StgitObject):\n \n     def get_base(self):\n         # Return the parent of the bottommost patch, if there is one.\n-        if os.path.isfile(self.__applied_file):\n-            bottommost = file(self.__applied_file).readline().strip()\n-            if bottommost:\n-                return self.get_patch(bottommost).get_bottom()\n-        # No bottommost patch, so just return HEAD\n-        return git.get_head()\n+        applied = self.get_applied()\n+        if applied:\n+            return self.get_patch(applied[0]).get_bottom()\n+        else:\n+            # No bottommost patch, so just return HEAD\n+            return git.get_head()\n \n     def get_head(self):\n         \"\"\"Return the head of the branch\n@@ -585,8 +734,6 @@ class Series(StgitObject):\n \n         self.set_parent(parent_remote, parent_branch)\n \n-        self.create_empty_field('applied')\n-        self.create_empty_field('unapplied')\n         os.makedirs(self.__refs_dir)\n         self._set_field('orig-base', git.get_head())\n \n@@ -687,10 +834,6 @@ class Series(StgitObject):\n \n             # FIXME: find a way to get rid of those manual removals\n             # (move functionality to StgitObject ?)\n-            if os.path.exists(self.__applied_file):\n-                os.remove(self.__applied_file)\n-            if os.path.exists(self.__unapplied_file):\n-                os.remove(self.__unapplied_file)\n             if os.path.exists(self.__hidden_file):\n                 os.remove(self.__hidden_file)\n             if os.path.exists(self._dir()+'/orig-base'):\n@@ -719,7 +862,7 @@ class Series(StgitObject):\n         config.unset('branch.%s.merge' % self.__name)\n         config.unset('branch.%s.stgit.parentbranch' % self.__name)\n \n-    def refresh_patch(self, files = None, message = None, edit = False,\n+    def refresh_patch(self, name, files = None, message = None, edit = False,\n                       show_patch = False,\n                       cache_update = True,\n                       author_name = None, author_email = None,\n@@ -728,10 +871,6 @@ class Series(StgitObject):\n                       backup = False, sign_str = None, log = 'refresh'):\n         \"\"\"Generates a new commit for the given patch\n         \"\"\"\n-        name = self.get_current()\n-        if not name:\n-            raise StackException, 'No patches applied'\n-\n         patch = self.get_patch(name)\n \n         descr = patch.get_description()\n@@ -821,9 +960,10 @@ class Series(StgitObject):\n         \"\"\"Creates a new patch\n         \"\"\"\n \n+        appl, unappl = self.get_applied(), self.get_unapplied()\n         if name != None:\n             self.__patch_name_valid(name)\n-            if self.patch_applied(name) or self.patch_unapplied(name):\n+            if name in appl or name in unappl:\n                 raise StackException, 'Patch \"%s\" already exists' % name\n \n         if not message and can_edit:\n@@ -860,20 +1000,29 @@ class Series(StgitObject):\n \n         if unapplied:\n             self.log_patch(patch, 'new')\n-\n-            patches = [patch.get_name()] + self.get_unapplied()\n-\n-            f = file(self.__unapplied_file, 'w+')\n-            f.writelines([line + '\\n' for line in patches])\n-            f.close()\n+            order = appl + [patch.get_name()] + unappl\n         elif before_existing:\n             self.log_patch(patch, 'new')\n-\n-            insert_string(self.__applied_file, patch.get_name())\n+            order = [patch.get_name()] + appl + unappl\n         else:\n-            append_string(self.__applied_file, patch.get_name())\n+            order = appl + [patch.get_name()] + unappl\n             if refresh:\n-                self.refresh_patch(cache_update = False, log = 'new')\n+                self.refresh_patch(name, cache_update = False, log = 'new')\n+        self.__applied_cache.set_patchorder(order)\n+\n+        return patch\n+\n+\n+    def delete_patch_data(self, name):\n+        \"\"\"Deletes the stgit data for a patch.\"\"\"\n+        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+\n+        # save the commit id to a trash file\n+        write_string(os.path.join(self.__trash_dir, name), patch.get_top())\n+\n+        patch.delete()\n+        if self.patch_hidden(name):\n+            self.unhide_patch(name)\n \n         return patch\n \n@@ -881,9 +1030,8 @@ class Series(StgitObject):\n         \"\"\"Deletes a patch\n         \"\"\"\n         self.__patch_name_valid(name)\n-        patch = Patch(name, self.__patch_dir, self.__refs_dir)\n \n-        if self.__patch_is_current(patch):\n+        if self.get_current() == name:\n             self.pop_patch(name)\n         elif self.patch_applied(name):\n             raise StackException, 'Cannot remove an applied patch, \"%s\", ' \\\n@@ -891,19 +1039,8 @@ class Series(StgitObject):\n         elif not name in self.get_unapplied():\n             raise StackException, 'Unknown patch \"%s\"' % name\n \n-        # save the commit id to a trash file\n-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())\n-\n-        patch.delete()\n-\n-        unapplied = self.get_unapplied()\n-        unapplied.remove(name)\n-        f = file(self.__unapplied_file, 'w+')\n-        f.writelines([line + '\\n' for line in unapplied])\n-        f.close()\n-\n-        if self.patch_hidden(name):\n-            self.unhide_patch(name)\n+        self.delete_patch_data(name)\n+        self.__applied_cache.refresh()\n \n     def forward_patches(self, names):\n         \"\"\"Try to fast-forward an array of patches.\n@@ -967,19 +1104,12 @@ class Series(StgitObject):\n                     break\n \n             forwarded+=1\n-            unapplied.remove(name)\n \n         if forwarded == 0:\n             return 0\n \n         git.switch(top)\n-\n-        append_strings(self.__applied_file, names[0:forwarded])\n-\n-        f = file(self.__unapplied_file, 'w+')\n-        f.writelines([line + '\\n' for line in unapplied])\n-        f.close()\n-\n+        self.__applied_cache.refresh()\n         return forwarded\n \n     def merged_patches(self, names):\n@@ -1052,13 +1182,6 @@ class Series(StgitObject):\n                               'Use \"refresh\" after fixing the conflicts or'\n                               ' revert the operation with \"push --undo\".')\n \n-        append_string(self.__applied_file, name)\n-\n-        unapplied.remove(name)\n-        f = file(self.__unapplied_file, 'w+')\n-        f.writelines([line + '\\n' for line in unapplied])\n-        f.close()\n-\n         # head == bottom case doesn't need to refresh the patch\n         if empty or head != bottom:\n             if not ex:\n@@ -1068,15 +1191,17 @@ class Series(StgitObject):\n                     log = 'push(m)'\n                 else:\n                     log = 'push'\n-                self.refresh_patch(cache_update = False, log = log)\n+                self.refresh_patch(name, cache_update = False, log = log)\n             else:\n                 # we store the correctly merged files only for\n                 # tracking the conflict history. Note that the\n                 # git.merge() operations should always leave the index\n                 # in a valid state (i.e. only stage 0 files)\n-                self.refresh_patch(cache_update = False, log = 'push(c)')\n+                self.refresh_patch(name, cache_update = False, log = 'push(c)')\n                 raise StackException, str(ex)\n \n+        self.__applied_cache.refresh()\n+\n         return modified\n \n     def undo_push(self):\n@@ -1105,10 +1230,7 @@ class Series(StgitObject):\n     def pop_patch(self, name, keep = False):\n         \"\"\"Pops the top patch from the stack\n         \"\"\"\n-        applied = self.get_applied()\n-        applied.reverse()\n-        assert(name in applied)\n-\n+        assert(name in self.get_applied())\n         patch = self.get_patch(name)\n \n         if git.get_head_file() == self.get_branch():\n@@ -1118,24 +1240,7 @@ class Series(StgitObject):\n             git.switch(patch.get_bottom(), keep)\n         else:\n             git.set_branch(self.get_branch(), patch.get_bottom())\n-\n-        # save the new applied list\n-        idx = applied.index(name) + 1\n-\n-        popped = applied[:idx]\n-        popped.reverse()\n-        unapplied = popped + self.get_unapplied()\n-\n-        f = file(self.__unapplied_file, 'w+')\n-        f.writelines([line + '\\n' for line in unapplied])\n-        f.close()\n-\n-        del applied[:idx]\n-        applied.reverse()\n-\n-        f = file(self.__applied_file, 'w+')\n-        f.writelines([line + '\\n' for line in applied])\n-        f.close()\n+        self.__applied_cache.refresh()\n \n     def empty_patch(self, name):\n         \"\"\"Returns True if the patch is empty\n@@ -1161,7 +1266,8 @@ class Series(StgitObject):\n \n         if oldname == newname:\n             raise StackException, '\"To\" name and \"from\" name are the same'\n-\n+        if oldname in applied or oldname in unapplied:\n+            raise StackException, 'Unknown patch \"%s\"' % oldname\n         if newname in applied or newname in unapplied:\n             raise StackException, 'Patch \"%s\" already exists' % newname\n \n@@ -1169,23 +1275,8 @@ class Series(StgitObject):\n             self.unhide_patch(oldname)\n             self.hide_patch(newname)\n \n-        if oldname in unapplied:\n-            self.get_patch(oldname).rename(newname)\n-            unapplied[unapplied.index(oldname)] = newname\n-\n-            f = file(self.__unapplied_file, 'w+')\n-            f.writelines([line + '\\n' for line in unapplied])\n-            f.close()\n-        elif oldname in applied:\n-            self.get_patch(oldname).rename(newname)\n-\n-            applied[applied.index(oldname)] = newname\n-\n-            f = file(self.__applied_file, 'w+')\n-            f.writelines([line + '\\n' for line in applied])\n-            f.close()\n-        else:\n-            raise StackException, 'Unknown patch \"%s\"' % oldname\n+        self.get_patch(oldname).rename(newname)\n+        self.__applied_cache.rename(oldname, newname)\n \n     def log_patch(self, patch, message):\n         \"\"\"Generate a log commit for a patch\ndiff --git a/t/t4000-upgrade.sh b/t/t4000-upgrade.sh\nindex 8a308fb..01be50d 100755\n--- a/t/t4000-upgrade.sh\n+++ b/t/t4000-upgrade.sh\n@@ -34,6 +34,12 @@ for ver in 0.12 0.8; do\n         ! git show-ref --verify --quiet refs/bases/master\n     '\n \n+    test_expect_success \\\n+        \"v$ver: Make sure the applied and unapplied files are gone\" '\n+        [ ! -e .git/patches/master/applied ] &&\n+        [ ! -e .git/patches/master/unapplied ]\n+'\n+\n     cd ..\n done\n \n"},{"id":"44576","messageId":"20070610095516.12000.1553.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 4/6] Test the new DAG appliedness machinery","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:55:16Z","receivedAt":"2007-06-10T09:55:16Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n t/t3000-git-interop.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 60 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t3000-git-interop.sh b/t/t3000-git-interop.sh\nnew file mode 100755\nindex 0000000..44414b9\n--- /dev/null\n+++ b/t/t3000-git-interop.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+# Copyright (c) 2007 Karl Hasselström\n+test_description='Test git/StGIT interoperability'\n+. ./test-lib.sh\n+\n+test_expect_success \\\n+    'Create some git-only history' '\n+    echo foo > foo.txt &&\n+    git add foo.txt &&\n+    git commit -a -m foo &&\n+    git tag foo-tag &&\n+    for i in 0 1 2 3 4; do\n+        echo foo$i >> foo.txt &&\n+        git commit -a -m foo$i;\n+    done\n+'\n+\n+test_expect_success \\\n+    'Initialize the StGIT repository' '\n+    stg init\n+'\n+\n+test_expect_success \\\n+    'Create five patches' '\n+    for i in 0 1 2 3 4; do\n+        stg new p$i -m p$i;\n+    done &&\n+    [ \"$(echo $(stg applied))\" = \"p0 p1 p2 p3 p4\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"\" ]\n+'\n+\n+test_expect_success \\\n+    'Pop two patches with git-reset' '\n+    git reset --hard HEAD~2 &&\n+    [ \"$(echo $(stg applied))\" = \"p0 p1 p2\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"p3 p4\" ]\n+'\n+\n+test_expect_success \\\n+    'Create a new patch' '\n+    stg new q0 -m q0 &&\n+    [ \"$(echo $(stg applied))\" = \"p0 p1 p2 q0\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"p3 p4\" ]\n+'\n+\n+test_expect_success \\\n+    'Go to an unapplied patch with with git-reset' '\n+    git reset --hard $(stg id p3) &&\n+    [ \"$(echo $(stg applied))\" = \"p0 p1 p2 p3\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"q0 p4\" ]\n+'\n+\n+test_expect_success \\\n+    'Go back to below the stack base with git-reset' '\n+    git reset --hard foo-tag &&\n+    [ \"$(echo $(stg applied))\" = \"\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"p0 p1 p2 q0 p3 p4\" ]\n+'\n+\n+test_done\n"},{"id":"44577","messageId":"20070610095523.12000.95017.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:55:23Z","receivedAt":"2007-06-10T09:55:23Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"The bash tab completion used the \"applied\", \"unapplied\" and \"current\"\nfiles to generate completions. Since these don't exist anymore, use\nstg applied/unapplied/series to obtain the same info. It's a bit\nslower, but not terribly much so.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n contrib/stgit-completion.bash |   15 ++++-----------\n 1 files changed, 4 insertions(+), 11 deletions(-)\n\ndiff --git a/contrib/stgit-completion.bash b/contrib/stgit-completion.bash\nindex d497098..2b9722a 100644\n--- a/contrib/stgit-completion.bash\n+++ b/contrib/stgit-completion.bash\n@@ -70,32 +70,25 @@ _current_branch ()\n # List of all applied patches.\n _applied_patches ()\n {\n-    local g=$(_gitdir)\n-    [ \"$g\" ] && cat \"$g/patches/$(_current_branch)/applied\"\n+    stg applied 2> /dev/null\n }\n \n # List of all unapplied patches.\n _unapplied_patches ()\n {\n-    local g=$(_gitdir)\n-    [ \"$g\" ] && cat \"$g/patches/$(_current_branch)/unapplied\"\n+    stg unapplied 2> /dev/null\n }\n \n # List of all patches.\n _all_patches ()\n {\n-    local b=$(_current_branch)\n-    local g=$(_gitdir)\n-    [ \"$g\" ] && cat \"$g/patches/$b/applied\" \"$g/patches/$b/unapplied\"\n+    stg series --noprefix 2> /dev/null\n }\n \n # List of all patches except the current patch.\n _all_other_patches ()\n {\n-    local b=$(_current_branch)\n-    local g=$(_gitdir)\n-    [ \"$g\" ] && cat \"$g/patches/$b/applied\" \"$g/patches/$b/unapplied\" \\\n-        | grep -v \"^$(cat $g/patches/$b/current 2> /dev/null)$\"\n+    stg series 2> /dev/null | grep -v '^>' | cut -f 2 -d ' '\n }\n \n _all_branches ()\n"},{"id":"44578","messageId":"20070610095530.12000.17789.stgit@bill","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"[StGIT PATCH 6/6] Speed up the appliedness test","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-06-10T09:55:30Z","receivedAt":"2007-06-10T09:55:30Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"The appliedness test was too slow if at least one patch, applied or\nunapplied, was too far away from HEAD, since we had to visit the whole\nintervening part of the commit DAG.\n\nThis patch fixes that problem by maintaining a cache of uninteresting\ncommits that are known to not reach any patches in the commit DAG.\n(Specifically, this is at all times the set of commits that are\nparents to patch commits and do not have a patch commit as their\nancestor.) By exlcuding these commits when walking the graph, we only\nhave to visit the interesting places.\n\nAs a nice side effect, the cache of uninteresting commits makes it\npossible to use just one git-rev-list call instead of two, since we\ncan list the applied patches without first computing the unapplied\npatches; the unapplied patches are then simply all patches except\nthose that are applied.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n---\n\n stgit/stack.py |  278 +++++++++++++++++++++++++++++++++++++++++++++-----------\n 1 files changed, 223 insertions(+), 55 deletions(-)\n\ndiff --git a/stgit/stack.py b/stgit/stack.py\nindex e622f68..6fd1a2a 100644\n--- a/stgit/stack.py\n+++ b/stgit/stack.py\n@@ -156,7 +156,7 @@ class Patch(StgitObject):\n         os.mkdir(self._dir())\n         self.create_empty_field('bottom')\n         self.create_empty_field('top')\n-\n+ \n     def delete(self):\n         for f in os.listdir(self._dir()):\n             os.remove(os.path.join(self._dir(), f))\n@@ -369,7 +369,11 @@ class PatchorderCache:\n     saved in its patchorder file.\"\"\"\n     def __init__(self, series):\n         self.__series = series\n+        self.__file = os.path.join(self.__series._dir(), 'patchorder')\n         self.__invalidate()\n+    def delete_file(self):\n+        if os.path.isfile(self.__file):\n+            os.remove(self.__file)\n     def __invalidate(self):\n         self.__patchnames = None\n         self.__position = None\n@@ -379,9 +383,8 @@ class PatchorderCache:\n \n         self.__patchnames = []\n         self.__position = {}\n-        pof = os.path.join(self.__series._dir(), 'patchorder')\n-        if os.path.isfile(pof):\n-            for line in file(pof):\n+        if os.path.isfile(self.__file):\n+            for line in file(self.__file):\n                 name = line.strip()\n                 assert not name in self.__position\n                 self.__position[name] = len(self.__patchnames)\n@@ -404,60 +407,200 @@ class PatchorderCache:\n         pos2 = self.__position.get(name2, largepos)\n         return cmp((pos1, name1), (pos2, name2))\n \n+class UninterestingCache:\n+    \"\"\"Keeps track of a set of commits that do not reach any patches.\n+    These are used to speed up the detection of unapplied patches.\n+\n+    Specifically, this is at all times the set of commits c that\n+    fulfill the following two criteria:\n+\n+      * c does not reach any patch\n+\n+      * c is the parent of a patch\n+\n+    \"\"\"\n+    def __init__(self, series):\n+        self.__series = series\n+        self.__uninteresting = None\n+        self.__filename = os.path.join(self.__series._dir(), 'uninteresting')\n+    def __invalidate(self):\n+        self.__uninteresting = None\n+        self.delete_file()\n+    def delete_file(self):\n+        if os.path.isfile(self.__filename):\n+            os.remove(self.__filename)\n+    def __other_patches(self, patchname):\n+        \"\"\"All patches except the named one.\"\"\"\n+        ref2hash = read_refs(self.__series.get_branch())\n+        return [self.__series.get_patch(ref)\n+                for ref in ref2hash.iterkeys()\n+                if ref and ref != patchname]\n+    def __write_file(self):\n+        \"\"\"Write the uninteresting commits to file.\"\"\"\n+        try:\n+            f = file(self.__filename, 'w')\n+            for u in self.__uninteresting:\n+                f.write('%s\\n' % u)\n+            f.close()\n+        except IOError:\n+            pass # this isn't fatal -- the directory is probably missing\n+    def __read_file(self):\n+        \"\"\"Read the uninteresting commits from file. Return true on\n+        success, false on failure.\"\"\"\n+        if not os.path.isfile(self.__filename):\n+            return False\n+        self.__uninteresting = Set()\n+        for line in file(self.__filename):\n+            self.__uninteresting.add(line.strip())\n+        return True\n+    def __cache_file(self):\n+        \"\"\"Try to cache the uninteresting commits using only the cache\n+        file. Return true on success, false on failure.\"\"\"\n+        if self.__uninteresting != None:\n+            return True # already cached\n+        return self.__read_file()\n+    def __cache(self):\n+        \"\"\"Cache the uninteresting commits, recomputing them if\n+        necessary.\"\"\"\n+        if self.__cache_file():\n+            return\n+        self.__compute_uninteresting()\n+        self.__write_file()\n+    def __compute_uninteresting(self):\n+        \"\"\"Compute a reasonable set of uninteresting commits from\n+        scratch. This is expensive.\"\"\"\n+        out.start('Finding uninteresting commits')\n+        ref2hash = read_refs(self.__series.get_branch())\n+        patches = Set([sha1 for ref, sha1 in ref2hash.iteritems() if ref])\n+        interesting, uninteresting = Set(), Set()\n+\n+        # Iterate over all commits. We are guaranteed to see each\n+        # commit before any of its children.\n+        for line in git._output_lines(\n+            'git-rev-list --topo-order --reverse --parents --all'):\n+            commits = line.split()\n+            commit, parents = commits[0], Set(commits[1:])\n+\n+            # Patches are interesting.\n+            if commit in patches:\n+                interesting.add(commit)\n+\n+                # The parents of a patch are uninteresting unless they\n+                # are interesting.\n+                for p in parents:\n+                    if not p in interesting:\n+                        uninteresting.add(p)\n+                continue\n+\n+            # Commits with interesting parents are interesting.\n+            if interesting.intersection(parents):\n+                interesting.add(commit)\n+        self.__uninteresting = uninteresting\n+        out.done()\n+    def create_patch(self, name, top, bottom):\n+        \"\"\"The given patch has been created. Update the uninterested\n+        state to maintain the invariant.\"\"\"\n+        if not self.__cache_file():\n+            return # not cached\n+\n+        # New patch inserted just below an existing bottommost patch:\n+        # need to move the uninteresting commit down one step.\n+        if top in self.__uninteresting:\n+            self.__uninteresting.remove(top)\n+            self.__uninteresting.add(bottom)\n+            self.__write_file()\n+            return\n+\n+        # New patch inserted next to an existing non-bottommost patch:\n+        # don't need to do anything.\n+        existing_patches = self.__other_patches(name)\n+        tops = Set([p.get_top() for p in existing_patches])\n+        bottoms = Set([p.get_bottom() for p in existing_patches])\n+        if bottom in bottoms or bottom in tops or top in bottoms:\n+            return\n+\n+        # The new patch is not adjacent to an existing patch. We'd\n+        # need to first get rid of any uninteresting commit that\n+        # reaches this patch, and then mark the patch's bottom\n+        # uninteresting if it doesn't reach any other patch. This is a\n+        # lot of work, so we chicken out and blow the whole cache\n+        # instead.\n+        self.__invalidate()\n+    def delete_patch(self, name, top, bottom):\n+        \"\"\"The given patch has been deleted. Update the uninterested\n+        state to maintain the invariant.\"\"\"\n+        if not self.__cache_file():\n+            return # not cached\n+\n+        # If this patch reaches another patch, there's nothing to do.\n+        if not bottom in self.__uninteresting:\n+            return\n+\n+        # If another patch has the same bottom, it's still\n+        # uninteresting and there's nothing more to do.\n+        other_patches = self.__other_patches(name)\n+        for p in other_patches:\n+            if p.get_bottom() == bottom:\n+                return\n+\n+        # If there are other patches on top of this one, their bottoms\n+        # (this patch's top) become uninteresting in place of this\n+        # patch's bottom.\n+        for p in other_patches:\n+            if p.get_bottom() == top:\n+                self.__uninteresting.remove(bottom)\n+                self.__uninteresting.add(top)\n+                self.__write_file()\n+                return\n+\n+        # The bottom of this patch is no longer uninteresting. But\n+        # there might be other patches that reach it, whose bottoms\n+        # would need to be marked uninteresting. That would require an\n+        # expensive reachability analysis.\n+        self.__invalidate()\n+    def get(self):\n+        self.__cache()\n+        return self.__uninteresting\n+\n def read_refs(branch):\n     \"\"\"Return a mapping from patches and branch head to hashes for a\n     given branch. The patches are listed by name; the branch head is\n     None.\"\"\"\n     refs = {}\n     patchpat = re.compile(r'^refs/patches/%s/([^\\.]+)$' % branch)\n+    head = 'refs/heads/%s' % branch\n     for line in git._output_lines('git-show-ref'):\n         sha1, ref = line.split()\n         m = re.match(patchpat, ref)\n         if m:\n             refs[m.group(1)] = sha1\n-        elif ref == 'refs/heads/%s' % branch:\n+        elif ref == head:\n             refs[None] = sha1\n+    if not None in refs:\n+        raise StackException, 'Could not find %s' % head\n     return refs\n \n-def unapplied_patches(ref2hash):\n+def get_patches(ref2hash, uninteresting):\n     \"\"\"Given a map of patch names (and the branch head, keyed by None)\n-    to hashes, return the set of unapplied patches.\"\"\"\n-    hash2refs = {}\n-    for r, h in ref2hash.iteritems():\n-        hash2refs.setdefault(h, Set()).add(r)\n-\n+    to hashes, return the list of applied patches and the set of\n+    unapplied patches. The second parameter is a set of commit objects\n+    that do not reach any patch.\"\"\"\n+    applied = []\n     unapplied = Set()\n-    for line in git._output_lines(\n-        'git-rev-list --stdin',\n-        ('%s%s\\n' % (['', '^'][ref == None], sha1)\n-         for ref, sha1 in ref2hash.iteritems())):\n-        for ref in hash2refs.get(line.strip(), []):\n-            unapplied.add(ref)\n-    return unapplied\n-\n-def sort_applied_patches(ref2hash):\n-    \"\"\"Given a map of patch names (and the branch head, keyed by None)\n-    to hashes, return a list with the applied patches in stack order.\n-    All patches in the map must be applied.\"\"\"\n-    hash2refs = {}\n+    hash2patches = {}\n     for r, h in ref2hash.iteritems():\n-        if r != None:\n-            hash2refs.setdefault(h, Set()).add(r)\n+        if r:\n+            hash2patches.setdefault(h, Set()).add(r)\n+            unapplied.add(r)\n \n-    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)\n-    if not missing:\n-        return []\n-    applied = []\n-    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)\n-    for line in grl.fromchild:\n-        for ref in hash2refs.get(line.strip(), []):\n+    for line in git._output_lines(\n+        'git-rev-list --topo-order --stdin', ['%s\\n' % ref2hash[None]]\n+        + ['^%s\\n' % u for u in uninteresting]):\n+        for ref in hash2patches.get(line.strip(), []):\n             applied.append(ref)\n-            missing.remove(ref)\n-        if not missing:\n-            applied.reverse()\n-            return applied\n-\n-    raise StackException, 'Could not find patches: %s' % ', '.join(missing)\n+            unapplied.remove(ref)\n+    applied.reverse()\n+    return applied, unapplied\n \n class AppliedCache:\n     \"\"\"An object that keeps track of the appliedness and order of the\n@@ -465,7 +608,11 @@ class AppliedCache:\n     def __init__(self, series):\n         self.__series = series\n         self.__order = PatchorderCache(series)\n+        self.__uninteresting = UninterestingCache(series)\n         self.__invalidate()\n+    def delete_files(self):\n+        for sub in [self.__uninteresting, self.__order]:\n+            sub.delete_file()\n     def get_applied(self):\n         self.__cache()\n         return self.__applied\n@@ -484,6 +631,17 @@ class AppliedCache:\n                 self.__write_patchorder()\n                 return\n         raise StackException, 'Unknown patch \"%s\"' % oldname\n+    def new(self, name, top, bottom):\n+        \"\"\"Create new patch.\"\"\"\n+        self.__uninteresting.create_patch(name, top, bottom)\n+    def delete(self, name, top, bottom):\n+        \"\"\"Delete a patch.\"\"\"\n+        self.__uninteresting.delete_patch(name, top, bottom)\n+    def change(self, name, old_top, old_bottom, new_top, new_bottom):\n+        \"\"\"Change a patch.\"\"\"\n+        if (new_top, new_bottom) != (old_top, old_bottom):\n+            self.new(name, new_top, new_bottom)\n+            self.delete(name, old_top, old_bottom)\n     def __write_patchorder(self):\n         self.__order.set_patchorder(self.get_applied() + self.get_unapplied())\n     def set_patchorder(self, new_order):\n@@ -502,11 +660,8 @@ class AppliedCache:\n     def __cache(self):\n         if self.__cached():\n             return\n-        patches = read_refs(self.__series.get_branch())\n-        unapplied = unapplied_patches(patches)\n-        for patch in unapplied:\n-            del patches[patch]\n-        self.__applied = sort_applied_patches(patches)\n+        self.__applied, unapplied = get_patches(\n+            read_refs(self.__series.get_branch()), self.__uninteresting.get())\n         self.__unapplied = list(unapplied)\n         self.__unapplied.sort(self.__order.cmp)\n \n@@ -838,6 +993,7 @@ class Series(StgitObject):\n                 os.remove(self.__hidden_file)\n             if os.path.exists(self._dir()+'/orig-base'):\n                 os.remove(self._dir()+'/orig-base')\n+            self.__applied_cache.delete_files()\n \n             if not os.listdir(self.__patch_dir):\n                 os.rmdir(self.__patch_dir)\n@@ -940,16 +1096,20 @@ class Series(StgitObject):\n         patch = self.get_patch(name)\n         old_bottom = patch.get_old_bottom()\n         old_top = patch.get_old_top()\n+        curr_bottom = patch.get_bottom()\n+        curr_top = patch.get_top()\n \n         # the bottom of the patch is not changed by refresh. If the\n         # old_bottom is different, there wasn't any previous 'refresh'\n         # command (probably only a 'push')\n-        if old_bottom != patch.get_bottom() or old_top == patch.get_top():\n+        if old_bottom != curr_bottom or old_top == curr_top:\n             raise StackException, 'No undo information available'\n \n         git.reset(tree_id = old_top, check_out = False)\n         if patch.restore_old_boundaries():\n             self.log_patch(patch, 'undo')\n+        self.__applied_cache.change(name, curr_top, curr_bottom,\n+                                    old_top, old_bottom)\n \n     def new_patch(self, name, message = None, can_edit = True,\n                   unapplied = False, show_patch = False,\n@@ -982,14 +1142,11 @@ class Series(StgitObject):\n         patch = self.get_patch(name)\n         patch.create()\n \n-        if bottom:\n-            patch.set_bottom(bottom)\n-        else:\n-            patch.set_bottom(head)\n-        if top:\n-            patch.set_top(top)\n-        else:\n-            patch.set_top(head)\n+        bottom = bottom or head\n+        top = top or head\n+        patch.set_bottom(bottom)\n+        patch.set_top(top)\n+        self.__applied_cache.new(name, top, bottom)\n \n         patch.set_description(descr)\n         patch.set_authname(author_name)\n@@ -1016,15 +1173,16 @@ class Series(StgitObject):\n     def delete_patch_data(self, name):\n         \"\"\"Deletes the stgit data for a patch.\"\"\"\n         patch = Patch(name, self.__patch_dir, self.__refs_dir)\n+        top, bottom = patch.get_top(), patch.get_bottom()\n \n         # save the commit id to a trash file\n-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())\n+        write_string(os.path.join(self.__trash_dir, name), top)\n \n         patch.delete()\n         if self.patch_hidden(name):\n             self.unhide_patch(name)\n \n-        return patch\n+        self.__applied_cache.delete(name, top, bottom)\n \n     def delete_patch(self, name):\n         \"\"\"Deletes a patch\n@@ -1084,6 +1242,7 @@ class Series(StgitObject):\n \n                     top_tree = git.get_commit(top).get_tree()\n \n+                    old_top = top\n                     top = git.commit(message = descr, parents = [head],\n                                      cache_update = False,\n                                      tree_id = top_tree,\n@@ -1097,6 +1256,9 @@ class Series(StgitObject):\n                     patch.set_bottom(head, backup = True)\n                     patch.set_top(top, backup = True)\n \n+                    self.__applied_cache.change(\n+                        name, old_top = old_top, old_bottom = bottom,\n+                        new_top = top, new_bottom = head)\n                     self.log_patch(patch, 'push(f)')\n                 else:\n                     top = head\n@@ -1154,6 +1316,7 @@ class Series(StgitObject):\n             # need an empty commit\n             patch.set_bottom(head, backup = True)\n             patch.set_top(head, backup = True)\n+            self.__applied_cache.change(name, top, bottom, head, head)\n             modified = True\n         elif head == bottom:\n             # reset the backup information. No need for logging\n@@ -1166,6 +1329,7 @@ class Series(StgitObject):\n             # The current patch is empty after merge.\n             patch.set_bottom(head, backup = True)\n             patch.set_top(head, backup = True)\n+            self.__applied_cache.change(name, top, bottom, head, head)\n \n             # Try the fast applying first. If this fails, fall back to the\n             # three-way merge\n@@ -1211,6 +1375,8 @@ class Series(StgitObject):\n         patch = self.get_patch(name)\n         old_bottom = patch.get_old_bottom()\n         old_top = patch.get_old_top()\n+        curr_bottom = patch.get_bottom()\n+        curr_top = patch.get_top()\n \n         # the top of the patch is changed by a push operation only\n         # together with the bottom (otherwise the top was probably\n@@ -1222,6 +1388,8 @@ class Series(StgitObject):\n         git.reset()\n         self.pop_patch(name)\n         ret = patch.restore_old_boundaries()\n+        self.__applied_cache.change(name, curr_top, curr_bottom,\n+                                    old_top, old_bottom)\n         if ret:\n             self.log_patch(patch, 'undo')\n \n"},{"id":"46121","messageId":"20070630195451.GL7730@nan92-1-81-57-214-146.fbx.proxad.net","threadId":"7997","inReplyTo":"20070610094322.12000.56284.stgit@bill","subject":"Re: [StGIT PATCH 0/6] New and improved DAG appliedness series","fromName":"Yann Dirson","fromEmail":"ydirson@altern.org","sentAt":"2007-06-30T19:54:51Z","receivedAt":"2007-06-30T19:54:51Z","isPatch":true,"sender":{"key":"ydirson@altern.org","avatar":"https://avatars.githubusercontent.com/u/1190950?v=4"},"body":"Hi Karl,\n\nOn Sun, Jun 10, 2007 at 02:54:47AM -0700, Karl Hasselström wrote:\n> This took a while, but here it is. (Actually, I finished this about a\n> week ago, but had no Internet connection so I couldn't send it.)\n\nIs this the latest version of the DAG patches, or is there maybe a\npublic repo where you push your work ?\n\nIt happens that my refactorings touches virtually everything, so there\nwill be conflicts, and the best thing to do is probably that I rebase\nmy work on yours.\n\nBest regards,\n-- \nYann\n"},{"id":"46172","messageId":"20070701143550.GA23196@diana.vm.bytemark.co.uk","threadId":"7997","inReplyTo":"20070630195451.GL7730@nan92-1-81-57-214-146.fbx.proxad.net","subject":"Re: [StGIT PATCH 0/6] New and improved DAG appliedness series","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-07-01T14:35:50Z","receivedAt":"2007-07-01T14:35:50Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2007-06-30 21:54:51 +0200, Yann Dirson wrote:\n\n> On Sun, Jun 10, 2007 at 02:54:47AM -0700, Karl Hasselström wrote:\n>\n> > This took a while, but here it is. (Actually, I finished this\n> > about a week ago, but had no Internet connection so I couldn't\n> > send it.)\n>\n> Is this the latest version of the DAG patches, or is there maybe a\n> public repo where you push your work ?\n\nThis is the latest version, and no, I don't yet have a public repo\nthat I push this stuff to.\n\nI've been travelling a lot the last few weeks, and I'm not quite done\nyet, so I haven't had time to even follow the mailing list properly,\nbut after that I plan to start a pu-ish (rebasing) integration branch\nfor the patches that I, you, and others post to the list.\n\n> It happens that my refactorings touches virtually everything,\n\nI noticed. :-)\n\n> so there will be conflicts,\n\n:-)\n\n> and the best thing to do is probably that I rebase my work on yours.\n\nThanks for the vote of confidence. Please go ahead; as I said, you\nalready have the latest version of my series, and it may be a while\nyet before I have much time to burn on StGIT.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"}]}