git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [StGIT PATCH] Don't use patches/<branch>/current
@ 2007-05-06 15:13 Karl Hasselström
  2007-05-15 15:56 ` Catalin Marinas
  2007-05-20 20:03 ` [StGit PATCH 0/2] Bash prompt updates Robin Rosenberg
  0 siblings, 2 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-06 15:13 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

The name of the current patch, if any, is always the last line of
patches/<branch>/applied (and there is no current patch if and only if
the "applied" file is empty). So use that instead, and stop having to
worry about keeping the redundant "current" file up-to-date.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

This is another remove-redundant-metadata cleanup patch. Not only does
it remove more code than it adds, the removed code (mostly calls to
__set_current) is the kind that one easily forgets to insert in the
proper places when writing new code.

 stgit/stack.py |   35 +++++++++--------------------------
 1 files changed, 9 insertions(+), 26 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index 2477ac6..3e9fc4f 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -295,7 +295,6 @@ class Series(StgitObject):
         self.__applied_file = os.path.join(self._dir(), 'applied')
         self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
         self.__hidden_file = os.path.join(self._dir(), 'hidden')
-        self.__current_file = os.path.join(self._dir(), 'current')
         self.__descr_file = os.path.join(self._dir(), 'description')
 
         # where this series keeps its patches
@@ -325,11 +324,6 @@ class Series(StgitObject):
         """
         return self.__name
 
-    def __set_current(self, name):
-        """Sets the topmost patch
-        """
-        self._set_field('current', name)
-
     def get_patch(self, name):
         """Return a Patch object for the given name
         """
@@ -346,11 +340,16 @@ class Series(StgitObject):
     def get_current(self):
         """Return the name of the topmost patch, or None if there is
         no such patch."""
-        name = self._get_field('current')
-        if name == '':
+        try:
+            applied = self.get_applied()
+        except StackException:
+            # No "applied" file: branch is not initialized.
+            return None
+        try:
+            return applied[-1]
+        except IndexError:
+            # No patches applied.
             return None
-        else:
-            return name
 
     def get_applied(self):
         if not os.path.isfile(self.__applied_file):
@@ -650,8 +649,6 @@ class Series(StgitObject):
                 os.remove(self.__unapplied_file)
             if os.path.exists(self.__hidden_file):
                 os.remove(self.__hidden_file)
-            if os.path.exists(self.__current_file):
-                os.remove(self.__current_file)
             if os.path.exists(self.__descr_file):
                 os.remove(self.__descr_file)
             if os.path.exists(self._dir()+'/orig-base'):
@@ -825,11 +822,8 @@ class Series(StgitObject):
             self.log_patch(patch, 'new')
 
             insert_string(self.__applied_file, patch.get_name())
-            if not self.get_current():
-                self.__set_current(name)
         else:
             append_string(self.__applied_file, patch.get_name())
-            self.__set_current(name)
             if refresh:
                 self.refresh_patch(cache_update = False, log = 'new')
 
@@ -936,8 +930,6 @@ class Series(StgitObject):
         f.writelines([line + '\n' for line in unapplied])
         f.close()
 
-        self.__set_current(name)
-
         return forwarded
 
     def merged_patches(self, names):
@@ -1019,8 +1011,6 @@ class Series(StgitObject):
         f.writelines([line + '\n' for line in unapplied])
         f.close()
 
-        self.__set_current(name)
-
         # head == bottom case doesn't need to refresh the patch
         if empty or head != bottom:
             if not ex:
@@ -1098,11 +1088,6 @@ class Series(StgitObject):
         f.writelines([line + '\n' for line in applied])
         f.close()
 
-        if applied == []:
-            self.__set_current(None)
-        else:
-            self.__set_current(applied[-1])
-
     def empty_patch(self, name):
         """Returns True if the patch is empty
         """
@@ -1144,8 +1129,6 @@ class Series(StgitObject):
             f.close()
         elif oldname in applied:
             Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
-            if oldname == self.get_current():
-                self.__set_current(newname)
 
             applied[applied.index(oldname)] = newname
 

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-06 15:13 [StGIT PATCH] Don't use patches/<branch>/current Karl Hasselström
@ 2007-05-15 15:56 ` Catalin Marinas
  2007-05-15 16:21   ` Peter Oberndorfer
                     ` (2 more replies)
  2007-05-20 20:03 ` [StGit PATCH 0/2] Bash prompt updates Robin Rosenberg
  1 sibling, 3 replies; 51+ messages in thread
From: Catalin Marinas @ 2007-05-15 15:56 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:
> The name of the current patch, if any, is always the last line of
> patches/<branch>/applied (and there is no current patch if and only if
> the "applied" file is empty). So use that instead, and stop having to
> worry about keeping the redundant "current" file up-to-date.

I applied this patch. Could you also send me a patch for the
bash-completion script as it uses this file?

I think the self.__current_file (same for the base file removed in a
different patch) should still be available in the Series object and
removed when deleting a branch, otherwise you get a "Series directory
... is not empty" exception.

Thanks.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 15:56 ` Catalin Marinas
@ 2007-05-15 16:21   ` Peter Oberndorfer
  2007-05-15 16:50     ` Catalin Marinas
  2007-05-15 18:25   ` Karl Hasselström
  2007-05-15 21:08   ` [StGIT PATCH] Don't use patches/<branch>/current Yann Dirson
  2 siblings, 1 reply; 51+ messages in thread
From: Peter Oberndorfer @ 2007-05-15 16:21 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Karl Hasselström, git

On Tuesday 15 May 2007 17:56, Catalin Marinas wrote:
> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:
> > The name of the current patch, if any, is always the last line of
> > patches/<branch>/applied (and there is no current patch if and only if
> > the "applied" file is empty). So use that instead, and stop having to
> > worry about keeping the redundant "current" file up-to-date.
> 
> I applied this patch. Could you also send me a patch for the
> bash-completion script as it uses this file?
> 
> I think the self.__current_file (same for the base file removed in a
> different patch) should still be available in the Series object and
> removed when deleting a branch, otherwise you get a "Series directory
> ... is not empty" exception.
> 
> Thanks.
> 
Hi,
this is a bit OT,
but when i wanted to try out this changes i found that 2 unrelated patches in you repo[1] are empty.
* Store branch description in the config file 
* Make the "name" argument to "stg new" optional

Is that a problem on my side, or are they really empty?

Greetings Peter

[1] http://homepage.ntlworld.com/cmarinas/stgit.git
which is mirrored at
http://repo.or.cz/w/stgit.git

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 16:21   ` Peter Oberndorfer
@ 2007-05-15 16:50     ` Catalin Marinas
  0 siblings, 0 replies; 51+ messages in thread
From: Catalin Marinas @ 2007-05-15 16:50 UTC (permalink / raw)
  To: Peter Oberndorfer; +Cc: Karl Hasselström, git

On 15/05/07, Peter Oberndorfer <kumbayo84@arcor.de> wrote:
> this is a bit OT,
> but when i wanted to try out this changes i found that 2 unrelated patches in you repo[1] are empty.
> * Store branch description in the config file
> * Make the "name" argument to "stg new" optional

Thanks for pointing out. They failed to apply cleanly last night and
forgot to delete the empty patches created. I fixed the conflicts and
added them today (I'll push them tonight).

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 15:56 ` Catalin Marinas
  2007-05-15 16:21   ` Peter Oberndorfer
@ 2007-05-15 18:25   ` Karl Hasselström
  2007-05-15 19:38     ` [StGIT PATCH] Remove obsolete files when deleting a branch Karl Hasselström
  2007-05-15 20:01     ` [StGIT PATCH] Don't use patches/<branch>/current Catalin Marinas
  2007-05-15 21:08   ` [StGIT PATCH] Don't use patches/<branch>/current Yann Dirson
  2 siblings, 2 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-15 18:25 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-15 16:56:33 +0100, Catalin Marinas wrote:

> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:
>
> > The name of the current patch, if any, is always the last line of
> > patches/<branch>/applied (and there is no current patch if and
> > only if the "applied" file is empty). So use that instead, and
> > stop having to worry about keeping the redundant "current" file
> > up-to-date.
>
> I applied this patch. Could you also send me a patch for the
> bash-completion script as it uses this file?

I realized this myself yesterday or so, and patched it to not need the
current, applied, and unapplied files. Are you OK with that patch, or
would you like one that keeps using {,un}applied?

> I think the self.__current_file (same for the base file removed in a
> different patch) should still be available in the Series object and
> removed when deleting a branch, otherwise you get a "Series
> directory ... is not empty" exception.

Ah, very true. I'll whip up a fix.

Same question there: are you OK with a single fix for base, current,
applied, and unapplied, or do you want them separate?

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGIT PATCH] Remove obsolete files when deleting a branch
  2007-05-15 18:25   ` Karl Hasselström
@ 2007-05-15 19:38     ` Karl Hasselström
  2007-05-15 20:01     ` [StGIT PATCH] Don't use patches/<branch>/current Catalin Marinas
  1 sibling, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-15 19:38 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/stack.py |   10 ++++++++++
 1 files changed, 10 insertions(+), 0 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index 52f39a6..4ed3a73 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -660,6 +660,16 @@ class Series(StgitObject):
             if os.path.exists(self._dir()+'/orig-base'):
                 os.remove(self._dir()+'/orig-base')
 
+            # Remove obsolete files that StGIT no longer uses, but
+            # that might still be around if this is an old repository.
+            for obsolete in ([os.path.join(self._dir(), fn)
+                              for fn in ['current', 'description',
+                                         'applied', 'unapplied']]
+                             + [os.path.join(self.__base_dir,
+                                             'refs', 'bases', self.__name)]):
+                if os.path.exists(obsolete):
+                    os.remove(obsolete)
+
             if not os.listdir(self.__patch_dir):
                 os.rmdir(self.__patch_dir)
             else:

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 18:25   ` Karl Hasselström
  2007-05-15 19:38     ` [StGIT PATCH] Remove obsolete files when deleting a branch Karl Hasselström
@ 2007-05-15 20:01     ` Catalin Marinas
  2007-05-16  7:11       ` Karl Hasselström
  1 sibling, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-15 20:01 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

On 15/05/07, Karl Hasselström <kha@treskal.com> wrote:
> On 2007-05-15 16:56:33 +0100, Catalin Marinas wrote:
>
> > On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:
> >
> > > The name of the current patch, if any, is always the last line of
> > > patches/<branch>/applied (and there is no current patch if and
> > > only if the "applied" file is empty). So use that instead, and
> > > stop having to worry about keeping the redundant "current" file
> > > up-to-date.
> >
> > I applied this patch. Could you also send me a patch for the
> > bash-completion script as it uses this file?
>
> I realized this myself yesterday or so, and patched it to not need the
> current, applied, and unapplied files. Are you OK with that patch, or
> would you like one that keeps using {,un}applied?

What is the impact on the bash completion for calling StGIT rather
than reading those files? Is it visible? If I integrate the DAG
patches, there probably isn't other way anyway.

> > I think the self.__current_file (same for the base file removed in a
> > different patch) should still be available in the Series object and
> > removed when deleting a branch, otherwise you get a "Series
> > directory ... is not empty" exception.
>
> Ah, very true. I'll whip up a fix.
>
> Same question there: are you OK with a single fix for base, current,
> applied, and unapplied, or do you want them separate?

Whatever is easier for you :-), I don't have any preference.

I'll push the patches I integrated in the next hour or so and you can
base your changes on them.

Thanks.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 15:56 ` Catalin Marinas
  2007-05-15 16:21   ` Peter Oberndorfer
  2007-05-15 18:25   ` Karl Hasselström
@ 2007-05-15 21:08   ` Yann Dirson
  2007-05-15 21:36     ` Catalin Marinas
  2 siblings, 1 reply; 51+ messages in thread
From: Yann Dirson @ 2007-05-15 21:08 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Karl Hasselström, git

On Tue, May 15, 2007 at 04:56:33PM +0100, Catalin Marinas wrote:
> On 06/05/07, Karl Hasselström <kha@treskal.com> wrote:
> >The name of the current patch, if any, is always the last line of
> >patches/<branch>/applied (and there is no current patch if and only if
> >the "applied" file is empty). So use that instead, and stop having to
> >worry about keeping the redundant "current" file up-to-date.
> 
> I applied this patch. Could you also send me a patch for the
> bash-completion script as it uses this file?
> 
> I think the self.__current_file (same for the base file removed in a
> different patch) should still be available in the Series object and
> removed when deleting a branch, otherwise you get a "Series directory
> .. is not empty" exception.

Shouldn't we also migrate to new format as soon as we need to touch a
data - in this case, whenever we push/pop ?

Or maybe declare a new "stgit stack format version" ?  Currently we
have "stg branch --convert", which switches between a "new" and an
"old" format which noone probably uses any more.  What about
versionning the on-disk format, and possibly provide the "convert"
functionnality back and forth between one format and the next, with
formal documentation about which version works with which stack
format ?

Best regards,
-- 
Yann.

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 21:08   ` [StGIT PATCH] Don't use patches/<branch>/current Yann Dirson
@ 2007-05-15 21:36     ` Catalin Marinas
  2007-05-15 21:49       ` Yann Dirson
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-15 21:36 UTC (permalink / raw)
  To: Yann Dirson; +Cc: Karl Hasselström, git

On 15/05/07, Yann Dirson <ydirson@altern.org> wrote:
> On Tue, May 15, 2007 at 04:56:33PM +0100, Catalin Marinas wrote:
> > I think the self.__current_file (same for the base file removed in a
> > different patch) should still be available in the Series object and
> > removed when deleting a branch, otherwise you get a "Series directory
> > .. is not empty" exception.
>
> Shouldn't we also migrate to new format as soon as we need to touch a
> data - in this case, whenever we push/pop ?
>
> Or maybe declare a new "stgit stack format version" ?  Currently we
> have "stg branch --convert", which switches between a "new" and an
> "old" format which noone probably uses any more.  What about
> versionning the on-disk format, and possibly provide the "convert"
> functionnality back and forth between one format and the next, with
> formal documentation about which version works with which stack
> format ?

I think it would be useful to have a version file (probably per
branch) and just upgrade when a mismatch is detected (in the __init__
function). The other option is to keep ignoring the unused files until
the branch is deleted but we might make a change at some point that
would break things.

We should write successive convert() functions and keep all of them in
case one skips an intermediate version.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 21:36     ` Catalin Marinas
@ 2007-05-15 21:49       ` Yann Dirson
  2007-05-16  6:27         ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Yann Dirson @ 2007-05-15 21:49 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Karl Hasselström, git

On Tue, May 15, 2007 at 10:36:05PM +0100, Catalin Marinas wrote:
> I think it would be useful to have a version file (probably per
> branch) and just upgrade when a mismatch is detected (in the __init__
> function).

Sounds reasonable, but I'd rather keep that in the config file
(eg. branch.<name>.stgit.formatversion).

Best regards,
-- 
Yann.

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 21:49       ` Yann Dirson
@ 2007-05-16  6:27         ` Karl Hasselström
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-16  6:27 UTC (permalink / raw)
  To: Yann Dirson; +Cc: Catalin Marinas, git

On 2007-05-15 23:49:52 +0200, Yann Dirson wrote:

> On Tue, May 15, 2007 at 10:36:05PM +0100, Catalin Marinas wrote:
>
> > I think it would be useful to have a version file (probably per
> > branch) and just upgrade when a mismatch is detected (in the
> > __init__ function).
>
> Sounds reasonable, but I'd rather keep that in the config file (eg.
> branch.<name>.stgit.formatversion).

I agree that explicit versioning would be a good idea -- doing
explicit upgrades at well-defined points is good headache prevention.

And I agree that the config file is a good place to put it.

I'll probably have time to whip up a patch later today. I think I'll
call the old "old" format 0, the old "new" format 1, and then use
successive integers from then on. I'll make a single version bump for
the format changes you've alreay applied, and re-do the
format-changing patches you haven't applied yet so that they have
version bumping integrated.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-15 20:01     ` [StGIT PATCH] Don't use patches/<branch>/current Catalin Marinas
@ 2007-05-16  7:11       ` Karl Hasselström
  2007-05-16 12:07         ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-16  7:11 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-15 21:01:43 +0100, Catalin Marinas wrote:

> What is the impact on the bash completion for calling StGIT rather
> than reading those files? Is it visible?

Yes, it's visible, but not annoying (to me anyway). The overhead is
akin to the overhead we used to have when "stg help" generated the
command names -- on the order of 100-200 ms, when StGIT is in the
cache. The expensive part is to start stgit; the git calls are cheap.
So theoretically the completion script could duplicate the logic in
StGIT and avoid most of the overhead, if someone wanted it badly
enough.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-16  7:11       ` Karl Hasselström
@ 2007-05-16 12:07         ` Catalin Marinas
  2007-05-16 19:40           ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-16 12:07 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1182 bytes --]

On 16/05/07, Karl Hasselström <kha@treskal.com> wrote:
> On 2007-05-15 21:01:43 +0100, Catalin Marinas wrote:
>
> > What is the impact on the bash completion for calling StGIT rather
> > than reading those files? Is it visible?
>
> Yes, it's visible, but not annoying (to me anyway). The overhead is
> akin to the overhead we used to have when "stg help" generated the
> command names -- on the order of 100-200 ms, when StGIT is in the
> cache. The expensive part is to start stgit; the git calls are cheap.
> So theoretically the completion script could duplicate the logic in
> StGIT and avoid most of the overhead, if someone wanted it badly
> enough.

I did a quick test of 'stg series' with the DAG patches applied, on a
Linux kernel repository ('du -sh .git' is 285M) with 42 patches (only
25 applied). It constantly takes over 2 seconds to complete (compared
to < 200ms without the DAG patches). The problem is that this delay
will happen for bash completion as well.

It seems that most of the time is spent in git._output_lines() called
from stack.read_refs() (for git-show-ref). I attach the profiling
output generated by stg-prof.

-- 
Catalin

[-- Attachment #2: prof.txt --]
[-- Type: text/plain, Size: 38609 bytes --]

+ cross-arm
+ init-err
+ ll-printk
+ compressed-head-stack
+ armv7-uncompress
+ armv7-tlb
+ armv7-xenon
+ armv7-vfpv3
+ armv7-neon
+ armv7-thumb2-user
+ pb926-pci-io
+ mpcore-smsc911x
+ smsc911x-driver
+ realview-eb-pci-mem
+ realview-eb-pci-io
+ nommu-consistent-api
+ nommu-fault
+ nommu-page-tlb
+ nommu-context-id
+ nommu-integrator
+ nommu-realview-eb
+ nommu-kconfig
+ nommu-ptrace
+ nommu-armv6
> nommu-armv7
- per-cpu-asids
- arch-dsb-mmiowb
- t2-arch-arm-clone
- t2-function-type
- t2-exception-handling
- t2-unified-syntax
- t2-exception-handling-alternative
- t2-srs-variant
- thumb2-kernel-port
- errata-arm1136-364296
- errata-arm1136-411920
- pipt-icache-flush
- smp-writealloc
         3150 function calls (3112 primitive calls) in 2.229 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.229    2.229 <string>:1(?)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:105(Error)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:117(NoSectionError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:124(DuplicateSectionError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:131(NoOptionError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:140(InterpolationError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:148(InterpolationMissingOptionError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:161(InterpolationSyntaxError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:165(InterpolationDepthError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:176(ParsingError)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:188(MissingSectionHeaderError)
        1    0.000    0.000    0.009    0.009 ConfigParser.py:202(RawConfigParser)
        1    0.000    0.000    0.000    0.000 ConfigParser.py:487(ConfigParser)
        1    0.000    0.000    0.002    0.002 ConfigParser.py:570(SafeConfigParser)
        1    0.000    0.000    0.011    0.011 ConfigParser.py:88(?)
        1    0.000    0.000    0.000    0.000 UserDict.py:19(__getitem__)
        1    0.000    0.000    0.000    0.000 UserDict.py:62(__contains__)
        1    0.000    0.000    0.000    0.000 basedir.py:2(?)
        1    0.007    0.007    0.007    0.007 basedir.py:23(__output)
        1    0.000    0.000    0.007    0.007 basedir.py:33(get)
        1    0.003    0.003    0.011    0.011 common.py:2(?)
        1    0.000    0.000    0.000    0.000 common.py:32(CmdException)
        1    0.000    0.000    0.000    0.000 common.py:37(RevParseException)
        1    0.000    0.000    0.006    0.006 config.py:125(config_setup)
        1    0.000    0.000    0.000    0.000 config.py:134(ConfigOption)
        2    0.000    0.000    0.000    0.000 config.py:137(__init__)
        1    0.000    0.000    0.000    0.000 config.py:2(?)
        1    0.000    0.000    0.000    0.000 config.py:24(GitConfigException)
        1    0.000    0.000    0.000    0.000 config.py:27(GitConfig)
        1    0.006    0.006    0.006    0.006 config.py:65(get)
        1    0.005    0.005    0.023    0.023 git.py:180(_output_one_line)
        2    2.029    1.015    2.067    1.034 git.py:192(_output_lines)
        1    0.002    0.002    0.003    0.003 git.py:2(?)
        1    0.000    0.000    0.023    0.023 git.py:288(get_head_file)
        1    0.000    0.000    0.000    0.000 git.py:30(GitException)
        1    0.000    0.000    0.000    0.000 git.py:39(Person)
        1    0.000    0.000    0.000    0.000 git.py:71(Commit)
        1    0.000    0.000    0.000    0.000 gitmergeonefile.py:2(?)
        1    0.000    0.000    0.000    0.000 gitmergeonefile.py:27(GitMergeException)
        1    0.001    0.001    2.228    2.228 main.py:197(main)
        1    0.000    0.000    0.000    0.000 main.py:32(canonical_cmd)
        1    0.000    0.000    0.015    0.015 main.py:51(__getitem__)
        1    0.000    0.000    0.000    0.000 optparse.py:1007(_create_option_list)
        1    0.000    0.000    0.001    0.001 optparse.py:1012(_populate_option_list)
        1    0.000    0.000    0.000    0.000 optparse.py:1022(_init_parsing_state)
        1    0.000    0.000    0.000    0.000 optparse.py:1036(set_usage)
        1    0.000    0.000    0.000    0.000 optparse.py:1059(get_default_values)
        1    0.000    0.000    0.000    0.000 optparse.py:1091(_get_args)
        1    0.000    0.000    0.000    0.000 optparse.py:1097(parse_args)
        1    0.000    0.000    0.000    0.000 optparse.py:1136(check_values)
        1    0.000    0.000    0.000    0.000 optparse.py:1149(_process_args)
        1    0.000    0.000    0.000    0.000 optparse.py:140(__init__)
        1    0.000    0.000    0.000    0.000 optparse.py:247(__init__)
       12    0.001    0.000    0.002    0.000 optparse.py:413(__init__)
       12    0.000    0.000    0.000    0.000 optparse.py:432(_check_opt_strings)
       12    0.000    0.000    0.000    0.000 optparse.py:441(_set_opt_strings)
       12    0.000    0.000    0.000    0.000 optparse.py:462(_set_attrs)
       12    0.000    0.000    0.000    0.000 optparse.py:480(_check_action)
       12    0.000    0.000    0.000    0.000 optparse.py:486(_check_type)
       12    0.000    0.000    0.000    0.000 optparse.py:504(_check_choice)
       12    0.000    0.000    0.000    0.000 optparse.py:517(_check_dest)
       12    0.000    0.000    0.000    0.000 optparse.py:528(_check_const)
       12    0.000    0.000    0.000    0.000 optparse.py:534(_check_nargs)
       12    0.000    0.000    0.000    0.000 optparse.py:543(_check_callback)
        1    0.000    0.000    0.000    0.000 optparse.py:662(__init__)
        1    0.000    0.000    0.000    0.000 optparse.py:748(__init__)
        1    0.000    0.000    0.000    0.000 optparse.py:759(_create_option_mappings)
        1    0.000    0.000    0.000    0.000 optparse.py:775(set_conflict_handler)
        1    0.000    0.000    0.000    0.000 optparse.py:780(set_description)
       13    0.000    0.000    0.000    0.000 optparse.py:786(_check_conflict)
       13    0.000    0.000    0.001    0.000 optparse.py:815(add_option)
        1    0.000    0.000    0.001    0.001 optparse.py:845(add_options)
        1    0.000    0.000    0.001    0.001 optparse.py:975(__init__)
        1    0.000    0.000    0.000    0.000 os.py:444(__setitem__)
        4    0.000    0.000    0.000    0.000 popen2.py:21(_cleanup)
        1    0.000    0.000    0.000    0.000 popen2.py:25(Popen3)
        4    0.064    0.016    0.064    0.016 popen2.py:31(__init__)
        1    0.000    0.000    0.000    0.000 popen2.py:7(?)
        3    0.000    0.000    0.000    0.000 popen2.py:87(wait)
        1    0.000    0.000    0.000    0.000 popen2.py:97(Popen4)
        1    0.000    0.000    0.000    0.000 posixpath.py:110(basename)
        5    0.000    0.000    0.000    0.000 posixpath.py:184(isdir)
        4    0.000    0.000    0.000    0.000 posixpath.py:197(isfile)
        8    0.000    0.000    0.000    0.000 posixpath.py:56(join)
        1    0.000    0.000    0.000    0.000 posixpath.py:74(split)
        1    0.000    0.000    2.229    2.229 profile:0(main())
        0    0.000             0.000          profile:0(profiler)
        1    0.001    0.001    2.163    2.163 series.py:111(func)
        1    0.001    0.001    0.015    0.015 series.py:2(?)
       38    0.001    0.000    0.001    0.000 series.py:88(__print_patch)
       46    0.000    0.000    0.000    0.000 sets.py:119(__iter__)
        1    0.000    0.000    0.000    0.000 sets.py:356(_update)
        1    0.000    0.000    0.000    0.000 sets.py:393(ImmutableSet)
        1    0.001    0.001    0.001    0.001 sets.py:41(?)
        1    0.000    0.000    0.000    0.000 sets.py:418(Set)
       72    0.001    0.000    0.001    0.000 sets.py:425(__init__)
       89    0.001    0.000    0.001    0.000 sets.py:515(add)
       25    0.000    0.000    0.000    0.000 sets.py:528(remove)
        1    0.000    0.000    0.000    0.000 sets.py:564(_TemporarilyImmutableSet)
        1    0.000    0.000    0.000    0.000 sets.py:83(BaseSet)
       26    0.000    0.000    0.000    0.000 sets.py:99(__len__)
        1    0.000    0.000    0.000    0.000 shutil.py:16(Error)
        1    0.000    0.000    0.000    0.000 shutil.py:5(?)
      896    0.017    0.000    0.028    0.000 sre.py:129(match)
        4    0.000    0.000    0.013    0.003 sre.py:177(compile)
      900    0.012    0.000    0.025    0.000 sre.py:216(_compile)
        5    0.000    0.000    0.001    0.000 sre_compile.py:151(_compile_charset)
        5    0.000    0.000    0.000    0.000 sre_compile.py:180(_optimize_charset)
     17/4    0.001    0.000    0.002    0.001 sre_compile.py:24(_compile)
        7    0.000    0.000    0.000    0.000 sre_compile.py:324(_simple)
        4    0.000    0.000    0.001    0.000 sre_compile.py:331(_compile_info)
       12    0.000    0.000    0.000    0.000 sre_compile.py:42(<lambda>)
        8    0.000    0.000    0.000    0.000 sre_compile.py:440(isstring)
        4    0.000    0.000    0.004    0.001 sre_compile.py:446(_code)
        4    0.000    0.000    0.013    0.003 sre_compile.py:461(compile)
       32    0.000    0.000    0.000    0.000 sre_parse.py:133(__len__)
       51    0.000    0.000    0.000    0.000 sre_parse.py:137(__getitem__)
        7    0.000    0.000    0.000    0.000 sre_parse.py:139(__setitem__)
        7    0.000    0.000    0.000    0.000 sre_parse.py:141(__getslice__)
       44    0.000    0.000    0.000    0.000 sre_parse.py:145(append)
    24/11    0.001    0.000    0.001    0.000 sre_parse.py:147(getwidth)
        4    0.000    0.000    0.000    0.000 sre_parse.py:183(__init__)
      125    0.001    0.000    0.001    0.000 sre_parse.py:187(__next)
       59    0.001    0.000    0.001    0.000 sre_parse.py:200(match)
       98    0.002    0.000    0.003    0.000 sre_parse.py:206(get)
       23    0.000    0.000    0.000    0.000 sre_parse.py:215(isident)
        4    0.000    0.000    0.001    0.000 sre_parse.py:221(isname)
        2    0.000    0.000    0.000    0.000 sre_parse.py:240(_class_escape)
        6    0.000    0.000    0.000    0.000 sre_parse.py:269(_escape)
     10/4    0.000    0.000    0.008    0.002 sre_parse.py:312(_parse_sub)
     10/4    0.003    0.000    0.008    0.002 sre_parse.py:367(_parse)
        4    0.000    0.000    0.009    0.002 sre_parse.py:614(parse)
        4    0.000    0.000    0.000    0.000 sre_parse.py:75(__init__)
        6    0.000    0.000    0.000    0.000 sre_parse.py:80(opengroup)
        6    0.000    0.000    0.000    0.000 sre_parse.py:91(closegroup)
       17    0.000    0.000    0.000    0.000 sre_parse.py:98(__init__)
        1    0.000    0.000    0.000    0.000 stack.py:112(StgitObject)
        1    0.000    0.000    0.000    0.000 stack.py:115(_set_dir)
        6    0.000    0.000    0.000    0.000 stack.py:117(_dir)
        1    0.000    0.000    0.000    0.000 stack.py:142(Patch)
        1    0.004    0.004    0.008    0.008 stack.py:2(?)
        1    0.000    0.000    0.000    0.000 stack.py:277(PatchorderCache)
        1    0.000    0.000    0.000    0.000 stack.py:280(__init__)
        1    0.000    0.000    0.000    0.000 stack.py:283(__invalidate)
       59    0.001    0.000    0.001    0.000 stack.py:286(__cache)
        2    0.000    0.000    0.000    0.000 stack.py:293(read_file)
        1    0.000    0.000    0.000    0.000 stack.py:30(StackException)
       59    0.005    0.000    0.007    0.000 stack.py:315(cmp)
        1    0.014    0.014    0.110    0.110 stack.py:327(read_refs)
        1    0.000    0.000    0.000    0.000 stack.py:33(FilterUntil)
        1    0.002    0.002    2.006    2.006 stack.py:342(unapplied_patches)
        1    0.026    0.026    0.037    0.037 stack.py:358(sort_applied_patches)
        1    0.000    0.000    0.000    0.000 stack.py:382(AppliedCache)
        1    0.000    0.000    0.000    0.000 stack.py:385(__init__)
        2    0.000    0.000    2.161    1.080 stack.py:389(get_applied)
        1    0.000    0.000    0.000    0.000 stack.py:392(get_unapplied)
        1    0.000    0.000    0.000    0.000 stack.py:417(__invalidate)
        3    0.000    0.000    0.000    0.000 stack.py:420(__cached)
        3    0.001    0.000    2.161    0.720 stack.py:422(__cache)
        1    0.000    0.000    0.000    0.000 stack.py:434(Series)
        1    0.000    0.000    0.031    0.031 stack.py:437(__init__)
        1    0.000    0.000    0.000    0.000 stack.py:479(get_branch)
        1    0.000    0.000    0.000    0.000 stack.py:497(get_current)
        2    0.000    0.000    2.161    1.080 stack.py:507(get_applied)
        1    0.000    0.000    0.000    0.000 stack.py:510(get_unapplied)
        1    0.000    0.000    0.000    0.000 stack.py:513(get_hidden)
        2    0.000    0.000    0.000    0.000 stack.py:646(is_initialised)
        8    0.000    0.000    0.000    0.000 stat.py:29(S_IFMT)
        5    0.000    0.000    0.000    0.000 stat.py:45(S_ISDIR)
        3    0.000    0.000    0.000    0.000 stat.py:54(S_ISREG)
        1    0.000    0.000    0.000    0.000 templates.py:2(?)
        1    0.000    0.000    0.000    0.000 utils.py:107(strip_prefix)
        1    0.000    0.000    0.000    0.000 utils.py:153(EditorException)
        1    0.001    0.001    0.001    0.001 utils.py:2(?)


   Ordered by: standard name

Function                                              called...
<string>:1(?)                                          main.py:197(main)(1)    2.228
ConfigParser.py:105(Error)                             --
ConfigParser.py:117(NoSectionError)                    --
ConfigParser.py:124(DuplicateSectionError)             --
ConfigParser.py:131(NoOptionError)                     --
ConfigParser.py:140(InterpolationError)                --
ConfigParser.py:148(InterpolationMissingOptionError)   --
ConfigParser.py:161(InterpolationSyntaxError)          --
ConfigParser.py:165(InterpolationDepthError)           --
ConfigParser.py:176(ParsingError)                      --
ConfigParser.py:188(MissingSectionHeaderError)         --
ConfigParser.py:202(RawConfigParser)                   sre.py:177(compile)(2)    0.013
ConfigParser.py:487(ConfigParser)                      --
ConfigParser.py:570(SafeConfigParser)                  sre.py:177(compile)(1)    0.013
ConfigParser.py:88(?)                                  ConfigParser.py:105(Error)(1)    0.000
                                                       ConfigParser.py:117(NoSectionError)(1)    0.000
                                                       ConfigParser.py:124(DuplicateSectionError)(1)    0.000
                                                       ConfigParser.py:131(NoOptionError)(1)    0.000
                                                       ConfigParser.py:140(InterpolationError)(1)    0.000
                                                       ConfigParser.py:148(InterpolationMissingOptionError)(1)    0.000
                                                       ConfigParser.py:161(InterpolationSyntaxError)(1)    0.000
                                                       ConfigParser.py:165(InterpolationDepthError)(1)    0.000
                                                       ConfigParser.py:176(ParsingError)(1)    0.000
                                                       ConfigParser.py:188(MissingSectionHeaderError)(1)    0.000
                                                       ConfigParser.py:202(RawConfigParser)(1)    0.009
                                                       ConfigParser.py:487(ConfigParser)(1)    0.000
                                                       ConfigParser.py:570(SafeConfigParser)(1)    0.002
UserDict.py:19(__getitem__)                            --
UserDict.py:62(__contains__)                           --
basedir.py:2(?)                                        --
basedir.py:23(__output)                                --
basedir.py:33(get)                                     UserDict.py:62(__contains__)(1)    0.000
                                                       basedir.py:23(__output)(1)    0.007
common.py:2(?)                                         common.py:32(CmdException)(1)    0.000
                                                       common.py:37(RevParseException)(1)    0.000
                                                       stack.py:2(?)(1)    0.008
                                                       utils.py:2(?)(1)    0.001
common.py:32(CmdException)                             --
common.py:37(RevParseException)                        --
config.py:125(config_setup)                            config.py:65(get)(1)    0.006
                                                       os.py:444(__setitem__)(1)    0.000
config.py:134(ConfigOption)                            --
config.py:137(__init__)                                --
config.py:2(?)                                         basedir.py:2(?)(1)    0.000
                                                       config.py:24(GitConfigException)(1)    0.000
                                                       config.py:27(GitConfig)(1)    0.000
                                                       config.py:134(ConfigOption)(1)    0.000
config.py:24(GitConfigException)                       --
config.py:27(GitConfig)                                --
config.py:65(get)                                      --
git.py:180(_output_one_line)                           popen2.py:31(__init__)(1)    0.064
                                                       popen2.py:87(wait)(1)    0.000
git.py:192(_output_lines)                              popen2.py:31(__init__)(2)    0.064
                                                       popen2.py:87(wait)(2)    0.000
git.py:2(?)                                            git.py:30(GitException)(1)    0.000
                                                       git.py:39(Person)(1)    0.000
                                                       git.py:71(Commit)(1)    0.000
                                                       gitmergeonefile.py:2(?)(1)    0.000
                                                       sets.py:41(?)(1)    0.001
                                                       shutil.py:5(?)(1)    0.000
git.py:288(get_head_file)                              git.py:180(_output_one_line)(1)    0.023
                                                       utils.py:107(strip_prefix)(1)    0.000
git.py:30(GitException)                                --
git.py:39(Person)                                      --
git.py:71(Commit)                                      --
gitmergeonefile.py:2(?)                                config.py:137(__init__)(2)    0.000
                                                       gitmergeonefile.py:27(GitMergeException)(1)    0.000
gitmergeonefile.py:27(GitMergeException)               --
main.py:197(main)                                      ConfigParser.py:88(?)(1)    0.011
                                                       UserDict.py:19(__getitem__)(1)    0.000
                                                       config.py:125(config_setup)(1)    0.006
                                                       main.py:32(canonical_cmd)(1)    0.000
                                                       main.py:51(__getitem__)(1)    0.015
                                                       optparse.py:975(__init__)(1)    0.001
                                                       optparse.py:1097(parse_args)(1)    0.000
                                                       posixpath.py:110(basename)(1)    0.000
                                                       series.py:111(func)(1)    2.163
                                                       stack.py:437(__init__)(1)    0.031
main.py:32(canonical_cmd)                              --
main.py:51(__getitem__)                                series.py:2(?)(1)    0.015
optparse.py:1007(_create_option_list)                  optparse.py:759(_create_option_mappings)(1)    0.000
optparse.py:1012(_populate_option_list)                optparse.py:815(add_option)(1)    0.001
                                                       optparse.py:845(add_options)(1)    0.001
optparse.py:1022(_init_parsing_state)                  --
optparse.py:1036(set_usage)                            --
optparse.py:1059(get_default_values)                   optparse.py:662(__init__)(1)    0.000
optparse.py:1091(_get_args)                            --
optparse.py:1097(parse_args)                           optparse.py:1059(get_default_values)(1)    0.000
                                                       optparse.py:1091(_get_args)(1)    0.000
                                                       optparse.py:1136(check_values)(1)    0.000
                                                       optparse.py:1149(_process_args)(1)    0.000
optparse.py:1136(check_values)                         --
optparse.py:1149(_process_args)                        --
optparse.py:140(__init__)                              --
optparse.py:247(__init__)                              optparse.py:140(__init__)(1)    0.000
optparse.py:413(__init__)                              optparse.py:432(_check_opt_strings)(12)    0.000
                                                       optparse.py:441(_set_opt_strings)(12)    0.000
                                                       optparse.py:462(_set_attrs)(12)    0.000
                                                       optparse.py:480(_check_action)(12)    0.000
                                                       optparse.py:486(_check_type)(12)    0.000
                                                       optparse.py:504(_check_choice)(12)    0.000
                                                       optparse.py:517(_check_dest)(12)    0.000
                                                       optparse.py:528(_check_const)(12)    0.000
                                                       optparse.py:534(_check_nargs)(12)    0.000
                                                       optparse.py:543(_check_callback)(12)    0.000
optparse.py:432(_check_opt_strings)                    --
optparse.py:441(_set_opt_strings)                      --
optparse.py:462(_set_attrs)                            --
optparse.py:480(_check_action)                         --
optparse.py:486(_check_type)                           --
optparse.py:504(_check_choice)                         --
optparse.py:517(_check_dest)                           --
optparse.py:528(_check_const)                          --
optparse.py:534(_check_nargs)                          --
optparse.py:543(_check_callback)                       --
optparse.py:662(__init__)                              --
optparse.py:748(__init__)                              optparse.py:775(set_conflict_handler)(1)    0.000
                                                       optparse.py:780(set_description)(1)    0.000
                                                       optparse.py:1007(_create_option_list)(1)    0.000
optparse.py:759(_create_option_mappings)               --
optparse.py:775(set_conflict_handler)                  --
optparse.py:780(set_description)                       --
optparse.py:786(_check_conflict)                       --
optparse.py:815(add_option)                            optparse.py:786(_check_conflict)(13)    0.000
optparse.py:845(add_options)                           optparse.py:815(add_option)(12)    0.001
optparse.py:975(__init__)                              optparse.py:247(__init__)(1)    0.000
                                                       optparse.py:748(__init__)(1)    0.000
                                                       optparse.py:1012(_populate_option_list)(1)    0.001
                                                       optparse.py:1022(_init_parsing_state)(1)    0.000
                                                       optparse.py:1036(set_usage)(1)    0.000
os.py:444(__setitem__)                                 --
popen2.py:21(_cleanup)                                 --
popen2.py:25(Popen3)                                   --
popen2.py:31(__init__)                                 popen2.py:21(_cleanup)(4)    0.000
popen2.py:7(?)                                         popen2.py:25(Popen3)(1)    0.000
                                                       popen2.py:97(Popen4)(1)    0.000
popen2.py:87(wait)                                     --
popen2.py:97(Popen4)                                   --
posixpath.py:110(basename)                             posixpath.py:74(split)(1)    0.000
posixpath.py:184(isdir)                                stat.py:45(S_ISDIR)(5)    0.000
posixpath.py:197(isfile)                               stat.py:54(S_ISREG)(3)    0.000
posixpath.py:56(join)                                  --
posixpath.py:74(split)                                 --
profile:0(main())                                      <string>:1(?)(1)    2.229
profile:0(profiler)                                    profile:0(main())(1)    2.229
series.py:111(func)                                    series.py:88(__print_patch)(38)    0.001
                                                       stack.py:497(get_current)(1)    0.000
                                                       stack.py:507(get_applied)(1)    2.161
                                                       stack.py:510(get_unapplied)(1)    0.000
                                                       stack.py:513(get_hidden)(1)    0.000
series.py:2(?)                                         common.py:2(?)(1)    0.011
                                                       optparse.py:413(__init__)(12)    0.002
series.py:88(__print_patch)                            --
sets.py:119(__iter__)                                  --
sets.py:356(_update)                                   --
sets.py:393(ImmutableSet)                              --
sets.py:41(?)                                          sets.py:83(BaseSet)(1)    0.000
                                                       sets.py:393(ImmutableSet)(1)    0.000
                                                       sets.py:418(Set)(1)    0.000
                                                       sets.py:564(_TemporarilyImmutableSet)(1)    0.000
sets.py:418(Set)                                       --
sets.py:425(__init__)                                  sets.py:356(_update)(1)    0.000
sets.py:515(add)                                       --
sets.py:528(remove)                                    --
sets.py:564(_TemporarilyImmutableSet)                  --
sets.py:83(BaseSet)                                    --
sets.py:99(__len__)                                    --
shutil.py:16(Error)                                    --
shutil.py:5(?)                                         shutil.py:16(Error)(1)    0.000
sre.py:129(match)                                      sre.py:216(_compile)(896)    0.025
sre.py:177(compile)                                    sre.py:216(_compile)(4)    0.025
sre.py:216(_compile)                                   sre_compile.py:440(isstring)(4)    0.000
                                                       sre_compile.py:461(compile)(4)    0.013
sre_compile.py:151(_compile_charset)                   sre_compile.py:42(<lambda>)(6)    0.000
                                                       sre_compile.py:180(_optimize_charset)(5)    0.000
sre_compile.py:180(_optimize_charset)                  sre_compile.py:42(<lambda>)(6)    0.000
sre_compile.py:24(_compile)                            sre_compile.py:24(_compile)(13)    0.002
                                                       sre_compile.py:151(_compile_charset)(5)    0.001
                                                       sre_compile.py:324(_simple)(7)    0.000
                                                       sre_parse.py:137(__getitem__)(27)    0.000
sre_compile.py:324(_simple)                            sre_parse.py:137(__getitem__)(7)    0.000
                                                       sre_parse.py:147(getwidth)(7)    0.001
sre_compile.py:331(_compile_info)                      sre_parse.py:133(__len__)(4)    0.000
                                                       sre_parse.py:137(__getitem__)(3)    0.000
                                                       sre_parse.py:147(getwidth)(4)    0.001
sre_compile.py:42(<lambda>)                            --
sre_compile.py:440(isstring)                           --
sre_compile.py:446(_code)                              sre_compile.py:24(_compile)(4)    0.002
                                                       sre_compile.py:331(_compile_info)(4)    0.001
sre_compile.py:461(compile)                            sre_compile.py:440(isstring)(4)    0.000
                                                       sre_compile.py:446(_code)(4)    0.004
                                                       sre_parse.py:614(parse)(4)    0.009
sre_parse.py:133(__len__)                              --
sre_parse.py:137(__getitem__)                          --
sre_parse.py:139(__setitem__)                          --
sre_parse.py:141(__getslice__)                         sre_parse.py:98(__init__)(7)    0.000
sre_parse.py:145(append)                               --
sre_parse.py:147(getwidth)                             sre_parse.py:147(getwidth)(13)    0.001
sre_parse.py:183(__init__)                             sre_parse.py:187(__next)(4)    0.001
sre_parse.py:187(__next)                               --
sre_parse.py:200(match)                                sre_parse.py:187(__next)(23)    0.001
sre_parse.py:206(get)                                  sre_parse.py:187(__next)(98)    0.001
sre_parse.py:215(isident)                              --
sre_parse.py:221(isname)                               sre_parse.py:215(isident)(23)    0.000
sre_parse.py:240(_class_escape)                        --
sre_parse.py:269(_escape)                              --
sre_parse.py:312(_parse_sub)                           sre_parse.py:200(match)(16)    0.001
                                                       sre_parse.py:367(_parse)(10)    0.008
sre_parse.py:367(_parse)                               sre_parse.py:80(opengroup)(6)    0.000
                                                       sre_parse.py:91(closegroup)(6)    0.000
                                                       sre_parse.py:98(__init__)(10)    0.000
                                                       sre_parse.py:133(__len__)(28)    0.000
                                                       sre_parse.py:137(__getitem__)(14)    0.000
                                                       sre_parse.py:139(__setitem__)(7)    0.000
                                                       sre_parse.py:141(__getslice__)(7)    0.000
                                                       sre_parse.py:145(append)(44)    0.000
                                                       sre_parse.py:200(match)(43)    0.001
                                                       sre_parse.py:206(get)(94)    0.003
                                                       sre_parse.py:221(isname)(4)    0.001
                                                       sre_parse.py:240(_class_escape)(2)    0.000
                                                       sre_parse.py:269(_escape)(6)    0.000
                                                       sre_parse.py:312(_parse_sub)(6)    0.008
sre_parse.py:614(parse)                                sre_parse.py:75(__init__)(4)    0.000
                                                       sre_parse.py:183(__init__)(4)    0.000
                                                       sre_parse.py:206(get)(4)    0.003
                                                       sre_parse.py:312(_parse_sub)(4)    0.008
sre_parse.py:75(__init__)                              --
sre_parse.py:80(opengroup)                             --
sre_parse.py:91(closegroup)                            --
sre_parse.py:98(__init__)                              --
stack.py:112(StgitObject)                              --
stack.py:115(_set_dir)                                 --
stack.py:117(_dir)                                     --
stack.py:142(Patch)                                    --
stack.py:2(?)                                          git.py:2(?)(1)    0.003
                                                       popen2.py:7(?)(1)    0.000
                                                       stack.py:30(StackException)(1)    0.000
                                                       stack.py:33(FilterUntil)(1)    0.000
                                                       stack.py:112(StgitObject)(1)    0.000
                                                       stack.py:142(Patch)(1)    0.000
                                                       stack.py:277(PatchorderCache)(1)    0.000
                                                       stack.py:382(AppliedCache)(1)    0.000
                                                       stack.py:434(Series)(1)    0.000
                                                       templates.py:2(?)(1)    0.000
stack.py:277(PatchorderCache)                          --
stack.py:280(__init__)                                 stack.py:283(__invalidate)(1)    0.000
stack.py:283(__invalidate)                             --
stack.py:286(__cache)                                  posixpath.py:56(join)(3)    0.000
                                                       posixpath.py:197(isfile)(3)    0.000
                                                       stack.py:117(_dir)(3)    0.000
                                                       stack.py:293(read_file)(2)    0.000
stack.py:293(read_file)                                --
stack.py:30(StackException)                            --
stack.py:315(cmp)                                      stack.py:286(__cache)(59)    0.001
stack.py:327(read_refs)                                git.py:192(_output_lines)(1)    2.067
                                                       sre.py:129(match)(896)    0.028
                                                       sre.py:177(compile)(1)    0.013
stack.py:33(FilterUntil)                               --
stack.py:342(unapplied_patches)                        git.py:192(_output_lines)(1)    2.067
                                                       sets.py:119(__iter__)(19)    0.000
                                                       sets.py:425(__init__)(46)    0.001
                                                       sets.py:515(add)(64)    0.001
stack.py:358(sort_applied_patches)                     popen2.py:31(__init__)(1)    0.064
                                                       sets.py:99(__len__)(26)    0.000
                                                       sets.py:119(__iter__)(25)    0.000
                                                       sets.py:425(__init__)(26)    0.001
                                                       sets.py:515(add)(25)    0.001
                                                       sets.py:528(remove)(25)    0.000
stack.py:382(AppliedCache)                             --
stack.py:385(__init__)                                 stack.py:280(__init__)(1)    0.000
                                                       stack.py:417(__invalidate)(1)    0.000
stack.py:389(get_applied)                              stack.py:422(__cache)(2)    2.161
stack.py:392(get_unapplied)                            stack.py:422(__cache)(1)    2.161
stack.py:417(__invalidate)                             --
stack.py:420(__cached)                                 --
stack.py:422(__cache)                                  sets.py:119(__iter__)(2)    0.000
                                                       stack.py:315(cmp)(59)    0.007
                                                       stack.py:327(read_refs)(1)    0.110
                                                       stack.py:342(unapplied_patches)(1)    2.006
                                                       stack.py:358(sort_applied_patches)(1)    0.037
                                                       stack.py:420(__cached)(3)    0.000
                                                       stack.py:479(get_branch)(1)    0.000
stack.py:434(Series)                                   --
stack.py:437(__init__)                                 basedir.py:33(get)(1)    0.007
                                                       git.py:288(get_head_file)(1)    0.023
                                                       posixpath.py:56(join)(5)    0.000
                                                       posixpath.py:184(isdir)(3)    0.000
                                                       stack.py:115(_set_dir)(1)    0.000
                                                       stack.py:117(_dir)(3)    0.000
                                                       stack.py:385(__init__)(1)    0.000
                                                       stack.py:646(is_initialised)(2)    0.000
stack.py:479(get_branch)                               --
stack.py:497(get_current)                              stack.py:507(get_applied)(1)    2.161
stack.py:507(get_applied)                              stack.py:389(get_applied)(2)    2.161
stack.py:510(get_unapplied)                            stack.py:392(get_unapplied)(1)    0.000
stack.py:513(get_hidden)                               posixpath.py:197(isfile)(1)    0.000
stack.py:646(is_initialised)                           posixpath.py:184(isdir)(2)    0.000
stat.py:29(S_IFMT)                                     --
stat.py:45(S_ISDIR)                                    stat.py:29(S_IFMT)(5)    0.000
stat.py:54(S_ISREG)                                    stat.py:29(S_IFMT)(3)    0.000
templates.py:2(?)                                      --
utils.py:107(strip_prefix)                             --
utils.py:153(EditorException)                          --
utils.py:2(?)                                          config.py:2(?)(1)    0.000
                                                       utils.py:153(EditorException)(1)    0.000



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-16 12:07         ` Catalin Marinas
@ 2007-05-16 19:40           ` Karl Hasselström
  2007-05-16 20:40             ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-16 19:40 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-16 13:07:14 +0100, Catalin Marinas wrote:

> I did a quick test of 'stg series' with the DAG patches applied, on
> a Linux kernel repository ('du -sh .git' is 285M) with 42 patches
> (only 25 applied). It constantly takes over 2 seconds to complete
> (compared to < 200ms without the DAG patches). The problem is that
> this delay will happen for bash completion as well.

This is more than ten times as expensive as in my measurement.
Curious. And the algorithm is designed so that it shouldn't take time
proportional to the repository size, just proportional to the number
of patches.

There are three git calls involved:

  * List the references. There can't be more than a few hundred of
    them.

  * rev-list all patches, subtracting everything that's reachable from
    the branch head. This set of commits should not be much larger
    than the number of unapplied patches.

  * rev-list the branch head, but stop walking as soon as all applied
    patches have been seen. This set of commits should not be much
    larger than the number of applied patches.

None of the calls should be expensive.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-16 19:40           ` Karl Hasselström
@ 2007-05-16 20:40             ` Karl Hasselström
  2007-05-17 12:43               ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-16 20:40 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-16 21:40:02 +0200, Karl Hasselström wrote:

> On 2007-05-16 13:07:14 +0100, Catalin Marinas wrote:
>
> > I did a quick test of 'stg series' with the DAG patches applied,
> > on a Linux kernel repository ('du -sh .git' is 285M) with 42
> > patches (only 25 applied). It constantly takes over 2 seconds to
> > complete (compared to < 200ms without the DAG patches). The
> > problem is that this delay will happen for bash completion as
> > well.
>
> This is more than ten times as expensive as in my measurement.
> Curious. And the algorithm is designed so that it shouldn't take
> time proportional to the repository size, just proportional to the
> number of patches.

I set up a kernel repository with 100 applied and 100 unapplied
patches:

  $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
  $ cd linux-2.6/
  $ stg init
  $ for ((i=0;i<200;i++)); do stg new p$(printf '%03d' $i) -m "Patch $i"; done
  $ stg goto p099

Then repeatedly:

  $ time stg series >/dev/null

This gives times such as

  real    0m0.247s
  user    0m0.156s
  sys     0m0.060s

  real    0m0.147s
  user    0m0.088s
  sys     0m0.036s

  real    0m0.153s
  user    0m0.088s
  sys     0m0.032s

  real    0m0.144s
  user    0m0.104s
  sys     0m0.024s

Tab completion also feels like it takes 0.1-0.2 seconds -- which it
should, since it's implemented with series, applied, and unapplied,
allow which do the same amount of work.

But my kernel repository is _much_ smaller than yours:

  $ du -sh .git
  183M    .git

Do you perchance have a bunch of loose objects in there?

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-16 20:40             ` Karl Hasselström
@ 2007-05-17 12:43               ` Catalin Marinas
  2007-05-17 14:57                 ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-17 12:43 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1483 bytes --]

On 16/05/07, Karl Hasselström <kha@treskal.com> wrote:
> I set up a kernel repository with 100 applied and 100 unapplied
> patches:
[...]
> Then repeatedly:
>
>   $ time stg series >/dev/null

I ran 'git repack -a -d' and 'git prune'. There are no other objects
apart from the generated pack:

$ du -sh .git
211M    .git

And then repeatedly 'time stg series > /dev/null':

real    0m1.638s
user    0m1.422s
sys     0m0.088s

real    0m2.542s
user    0m1.436s
sys     0m0.078s

real    0m2.916s
user    0m1.424s
sys     0m0.083s

real    0m2.940s
user    0m1.425s
sys     0m0.081s

real    0m1.614s
user    0m1.421s
sys     0m0.081s

real    0m1.587s
user    0m1.423s
sys     0m0.081s

real    0m2.653s
user    0m1.427s
sys     0m0.075s

> But my kernel repository is _much_ smaller than yours:
>
>   $ du -sh .git
>   183M    .git
>
> Do you perchance have a bunch of loose objects in there?

It got smaller after repacking but it is still bigger than yours.
Maybe the reason is that I have 14 branches with various patches, some
of them just for historical reasons but going back to 2.6.12. There
are also several commits generated for the patch logs.

The CPU is a P4 at 2.5GHz and the 'stg series' operation seems to be
CPU bound rather than IO. I'm also using Python 2.3 on this PC and for
this reason I changed 2 generator constructs (x for x in ...) with
list comprehension (see the attached patch).

-- 
Catalin

[-- Attachment #2: list-comprehension.patch --]
[-- Type: text/x-patch, Size: 1101 bytes --]

commit f7cc1f7fa6b2ee4f42e812970760ba5db5d2cafc
Author: Catalin Marinas <catalin.marinas@gmail.com>
Date:   Wed May 16 13:08:45 2007 +0100

    refresh	b5003aa7bb5ec384e1dbe2887c07885a791384c2

diff --git a/stgit/stack.py b/stgit/stack.py
index 13eb692..0ef26b8 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -349,8 +349,8 @@ def unapplied_patches(ref2hash):
     unapplied = Set()
     for line in git._output_lines(
         'git-rev-list --stdin',
-        ('%s%s\n' % (['', '^'][ref == None], sha1)
-         for ref, sha1 in ref2hash.iteritems())):
+        ['%s%s\n' % (['', '^'][ref == None], sha1)
+         for ref, sha1 in ref2hash.iteritems()]):
         for ref in hash2refs.get(line.strip(), []):
             unapplied.add(ref)
     return unapplied
@@ -364,7 +364,7 @@ def sort_applied_patches(ref2hash):
         if r != None:
             hash2refs.setdefault(h, Set()).add(r)
 
-    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)
+    missing = Set([ref for ref in ref2hash.iterkeys() if ref != None])
     if not missing:
         return []
     applied = []

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-17 12:43               ` Catalin Marinas
@ 2007-05-17 14:57                 ` Karl Hasselström
  2007-05-17 20:51                   ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-17 14:57 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-17 13:43:35 +0100, Catalin Marinas wrote:

> I ran 'git repack -a -d' and 'git prune'. There are no other objects
> apart from the generated pack:
>
> $ du -sh .git
> 211M    .git
>
> And then repeatedly 'time stg series > /dev/null':

Hmm, it seems there is a problem then. :-(

> It got smaller after repacking but it is still bigger than yours.
> Maybe the reason is that I have 14 branches with various patches,
> some of them just for historical reasons but going back to 2.6.12.
> There are also several commits generated for the patch logs.

OK. That shouldn't matter, though, since that extra history shouldn't
be examined anyway.

> The CPU is a P4 at 2.5GHz and the 'stg series' operation seems to be
> CPU bound rather than IO. I'm also using Python 2.3 on this PC and
> for this reason I changed 2 generator constructs (x for x in ...)
> with list comprehension (see the attached patch).

I don't think that's the problem, since those lists are both small.

The only possibility I can think of that might explain this is that
some of your unapplied patches are attached to a place in the commit
DAG that's far away from the branch head (e.g. you have rebased to
some entirely different place since you last had them applied), so
that "git-rev-list patch ^branch" outputs a large part of the commit
DAG.

Could you put counters in unapplied_patches() and
sort_applied_patches() to see how many lines each of them reads from
git-rev-list? The expected number (if it had taken just a little time,
like it did for me) is a small constant times the number of patches in
both cases.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-17 14:57                 ` Karl Hasselström
@ 2007-05-17 20:51                   ` Catalin Marinas
  2007-05-18  6:30                     ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-17 20:51 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

On 17/05/07, Karl Hasselström <kha@treskal.com> wrote:
> The only possibility I can think of that might explain this is that
> some of your unapplied patches are attached to a place in the commit
> DAG that's far away from the branch head (e.g. you have rebased to
> some entirely different place since you last had them applied), so
> that "git-rev-list patch ^branch" outputs a large part of the commit
> DAG.

That's probably the case. I have patches that I haven't rebased for
months but I keep them in case they might be needed in the future.
That's the reason for the hide/unhide commands. Anyway, I'm not yet
prepared to give up my current workflow.

I haven't tried to understand your patch yet but the unapplied patches
will never be in a linear DAG similar to the applied patches. Because
of this, we need to keep their order in a file anyway and we might not
need to run git-rev-list (BTW, how do you preserve the unapplied
patches order with the DAG implementation?).

> Could you put counters in unapplied_patches() and
> sort_applied_patches() to see how many lines each of them reads from
> git-rev-list? The expected number (if it had taken just a little time,
> like it did for me) is a small constant times the number of patches in
> both cases.

I'll do this tomorrow to confirm but that's probably the cause of the slow-down.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH] Don't use patches/<branch>/current
  2007-05-17 20:51                   ` Catalin Marinas
@ 2007-05-18  6:30                     ` Karl Hasselström
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-18  6:30 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-17 21:51:19 +0100, Catalin Marinas wrote:

> On 17/05/07, Karl Hasselström <kha@treskal.com> wrote:
>
> > The only possibility I can think of that might explain this is
> > that some of your unapplied patches are attached to a place in the
> > commit DAG that's far away from the branch head (e.g. you have
> > rebased to some entirely different place since you last had them
> > applied), so that "git-rev-list patch ^branch" outputs a large
> > part of the commit DAG.
>
> That's probably the case. I have patches that I haven't rebased for
> months but I keep them in case they might be needed in the future.

That's probably it, then. I'll need to come up with a clever scheme to
make that case cheap again.

> That's the reason for the hide/unhide commands.

Ah.

> Anyway, I'm not yet prepared to give up my current workflow.

I didn't intend to make that workflow painful, so don't despair yet --
there might be a way to make it work.

> I haven't tried to understand your patch yet but the unapplied
> patches will never be in a linear DAG similar to the applied
> patches.

I know.

> Because of this, we need to keep their order in a file anyway and we
> might not need to run git-rev-list (BTW, how do you preserve the
> unapplied patches order with the DAG implementation?).

The order of _all_ patches is kept in the "patchorder" file. This file
is consulted only when the relative order of two unapplied patches
need to be established. (Applied patches are sorted by the DAG, and
applied patches always precede unapplied patches.)

I would like to avoid keeping a record of known-to-be-unapplied
patches; while this is nowhere near as important as not keeping a
record of applied patches, it would still provide an opening for the
user to confuse StGIT.

But I believe I have a cunning plan ...

> > Could you put counters in unapplied_patches() and
> > sort_applied_patches() to see how many lines each of them reads
> > from git-rev-list? The expected number (if it had taken just a
> > little time, like it did for me) is a small constant times the
> > number of patches in both cases.
>
> I'll do this tomorrow to confirm but that's probably the cause of
> the slow-down.

Thanks for hanging in there.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGIT PATCH 0/5] Metadata format versioning
  2007-05-16  6:27         ` Karl Hasselström
@ 2007-05-19  0:09           ` Karl Hasselström
  2007-05-19  0:09             ` [StGIT PATCH 1/5] Fix config caching so that get, set, get works Karl Hasselström
                               ` (4 more replies)
  0 siblings, 5 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:09 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-16 08:27:11 +0200, Karl Hasselström wrote:

> I'll probably have time to whip up a patch later today.

OK, so this turned out to be a slight miscalculation. But here it is,
with a test! The test contains two small tarballs, so I had to whip up
binary patch support as well.

> I'll make a single version bump for the format changes you've alreay
> applied, and re-do the format-changing patches you haven't applied
> yet so that they have version bumping integrated.

This contains a single version bump (1 -> 2) for the applied changes,
but i haven't updated the DAG patch to use this yet. That's for
another day.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGIT PATCH 1/5] Fix config caching so that get, set, get works
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
@ 2007-05-19  0:09             ` Karl Hasselström
  2007-05-19  0:09             ` [StGIT PATCH 2/5] Have only a single command in each test_expect_failure Karl Hasselström
                               ` (3 subsequent siblings)
  4 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:09 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

The config caching was never invalidated or updated, which caused the
two gets to always return the same value regardless of the value
passed to set.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/config.py |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)

diff --git a/stgit/config.py b/stgit/config.py
index 79cd12f..2fd1273 100644
--- a/stgit/config.py
+++ b/stgit/config.py
@@ -99,12 +99,15 @@ class GitConfig:
 
     def rename_section(self, from_name, to_name):
         self.__run('git-repo-config --rename-section', [from_name, to_name])
+        self.__cache.clear()
 
     def set(self, name, value):
         self.__run('git-repo-config', [name, value])
+        self.__cache[name] = value
 
     def unset(self, name):
         self.__run('git-repo-config --unset', [name])
+        self.__cache[name] = None
 
     def sections_matching(self, regexp):
         """Takes a regexp with a single group, matches it against all

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 2/5] Have only a single command in each test_expect_failure
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
  2007-05-19  0:09             ` [StGIT PATCH 1/5] Fix config caching so that get, set, get works Karl Hasselström
@ 2007-05-19  0:09             ` Karl Hasselström
  2007-05-19  0:10             ` [StGIT PATCH 3/5] Upgrade old StGIT branches to new-format metadata Karl Hasselström
                               ` (2 subsequent siblings)
  4 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:09 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Otherwise, we can't know which one failed.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t1000-branch-create.sh |   71 ++++++++++++++++++++++++++++------------------
 1 files changed, 43 insertions(+), 28 deletions(-)

diff --git a/t/t1000-branch-create.sh b/t/t1000-branch-create.sh
index 58209e7..cca5504 100755
--- a/t/t1000-branch-create.sh
+++ b/t/t1000-branch-create.sh
@@ -12,57 +12,72 @@ Exercises the "stg branch" commands.
 
 stg init
 
+test_expect_success \
+    'Create a spurious refs/patches/ entry' '
+    find .git -name foo | xargs rm -rf &&
+    touch .git/refs/patches/foo
+'
+
 test_expect_failure \
-    'Try to create an stgit branch with a spurious refs/patches/ entry' \
-    'find .git -name foo | xargs rm -rf &&
-     touch .git/refs/patches/foo &&
-     stg branch -c foo
+    'Try to create an stgit branch with a spurious refs/patches/ entry' '
+    stg branch -c foo
+'
+
+test_expect_success \
+    'Check that no part of the branch was created' '
+    test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/patches/foo" &&
+    ( grep foo .git/HEAD; test $? = 1 )
 '
 
 test_expect_success \
-    'Check no part of the branch was created' \
-    'test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/patches/foo" &&
-     ( grep foo .git/HEAD; test $? = 1 )
+    'Create a spurious patches/ entry' '
+    find .git -name foo | xargs rm -rf &&
+    touch .git/patches/foo
 '
 
 test_expect_failure \
-    'Try to create an stgit branch with a spurious patches/ entry' \
-    'find .git -name foo | xargs rm -rf &&
-     touch .git/patches/foo &&
-     stg branch -c foo
+    'Try to create an stgit branch with a spurious patches/ entry' '
+    stg branch -c foo
 '
 
 test_expect_success \
-    'Check no part of the branch was created' \
-    'test "`find .git -name foo | tee /dev/stderr`" = ".git/patches/foo" &&
-     ( grep foo .git/HEAD; test $? = 1 )
+    'Check that no part of the branch was created' '
+    test "`find .git -name foo | tee /dev/stderr`" = ".git/patches/foo" &&
+    ( grep foo .git/HEAD; test $? = 1 )
+'
+
+test_expect_success \
+    'Create a git branch' '
+    find .git -name foo | xargs rm -rf &&
+    cp .git/refs/heads/master .git/refs/heads/foo
 '
 
 test_expect_failure \
-    'Try to create an stgit branch with an existing git branch by that name' \
-    'find .git -name foo | xargs rm -rf &&
-     cp .git/refs/heads/master .git/refs/heads/foo &&
-     stg branch -c foo
+    'Try to create an stgit branch with an existing git branch by that name' '
+    stg branch -c foo
 '
 
 test_expect_success \
-    'Check no part of the branch was created' \
-    'test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/heads/foo" &&
-     ( grep foo .git/HEAD; test $? = 1 )
+    'Check that no part of the branch was created' '
+    test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/heads/foo" &&
+    ( grep foo .git/HEAD; test $? = 1 )
 '
 
+test_expect_success \
+    'Create an invalid refs/heads/ entry' '
+    find .git -name foo | xargs rm -rf &&
+    touch .git/refs/heads/foo
+'
 
 test_expect_failure \
-    'Try to create an stgit branch with an invalid refs/heads/ entry' \
-    'find .git -name foo | xargs rm -rf &&
-     touch .git/refs/heads/foo &&
-     stg branch -c foo
+    'Try to create an stgit branch with an invalid refs/heads/ entry' '
+    stg branch -c foo
 '
 
 test_expect_success \
-    'Check no part of the branch was created' \
-    'test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/heads/foo" &&
-     ( grep foo .git/HEAD; test $? = 1 )
+    'Check that no part of the branch was created' '
+    test "`find .git -name foo | tee /dev/stderr`" = ".git/refs/heads/foo" &&
+    ( grep foo .git/HEAD; test $? = 1 )
 '
 
 test_done

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 3/5] Upgrade old StGIT branches to new-format metadata
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
  2007-05-19  0:09             ` [StGIT PATCH 1/5] Fix config caching so that get, set, get works Karl Hasselström
  2007-05-19  0:09             ` [StGIT PATCH 2/5] Have only a single command in each test_expect_failure Karl Hasselström
@ 2007-05-19  0:10             ` Karl Hasselström
  2007-05-19  0:10             ` [StGIT PATCH 4/5] Test the format version upgrade code Karl Hasselström
  2007-05-19  0:10             ` [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs Karl Hasselström
  4 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:10 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

There used to be a "stg branch --convert" command that switched
between "old" and "new" format metadata. But my recent metadata
cleanup patches introduced a "new new" format, and more are hopefully
on the way, so it was time to start versioning the metadata format
explicitly in order to avoid future headaches.

This patch removes the "stg branch --convert" command, and makes StGIT
automatically upgrade older formats to the latest format. It stores
the format (as an integer) in the config file. The current metadata
format version number is 2 (the "old" format is 0, and the "new"
format is 1).

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 Documentation/stg-branch.txt |    4 -
 stgit/commands/branch.py     |   11 ---
 stgit/stack.py               |  153 ++++++++++++++++++++++++------------------
 3 files changed, 86 insertions(+), 82 deletions(-)

diff --git a/Documentation/stg-branch.txt b/Documentation/stg-branch.txt
index 25c9c19..25ca951 100644
--- a/Documentation/stg-branch.txt
+++ b/Documentation/stg-branch.txt
@@ -20,7 +20,6 @@ SYNOPSIS
 'stg' branch --protect [<branch>]
 'stg' branch --unprotect [<branch>]
 'stg' branch --delete [--force] <branch>
-'stg' branch --convert
 
 DESCRIPTION
 -----------
@@ -91,9 +90,6 @@ the "master" branch if it exists.
 Branch "master" is treated specially (see bug #8732), in that only the
 StGIT metadata are removed, the GIT branch itself is not destroyed.
 
-'stg' branch --convert::
-	Switch current stack between old and new format.
-
 OPTIONS
 -------
 
diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index b043c69..5e7b0df 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -45,9 +45,6 @@ options = [make_option('-c', '--create',
            make_option('--clone',
                        help = 'clone the contents of the current branch',
                        action = 'store_true'),
-           make_option('--convert',
-                       help = 'switch between old and new format branches',
-                       action = 'store_true'),
            make_option('--delete',
                        help = 'delete an existing development branch',
                        action = 'store_true'),
@@ -186,14 +183,6 @@ def func(parser, options, args):
 
         return
 
-    elif options.convert:
-
-        if len(args) != 0:
-            parser.error('incorrect number of arguments')
-
-        crt_series.convert()
-        return
-
     elif options.delete:
 
         if len(args) != 1:
diff --git a/stgit/stack.py b/stgit/stack.py
index d9c4b99..223f3ee 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -273,6 +273,79 @@ class Patch(StgitObject):
         self._set_field('log', value)
         self.__update_log_ref(value)
 
+# The current StGIT metadata format version.
+FORMAT_VERSION = 2
+
+def format_version_key(branch):
+    return 'branch.%s.stgitformatversion' % branch
+
+def update_to_current_format_version(branch, git_dir):
+    """Update a potentially older StGIT directory structure to the
+    latest version. Note: This function should depend as little as
+    possible on external functions that may change during a format
+    version bump, since it must remain able to process older formats."""
+
+    branch_dir = os.path.join(git_dir, 'patches', branch)
+    def get_format_version():
+        """Return the integer format version number, or None if the
+        branch doesn't have any StGIT metadata at all, of any version."""
+        fv = config.get(format_version_key(branch))
+        if fv:
+            # Great, there's an explicitly recorded format version
+            # number, which means that the branch is initialized and
+            # of that exact version.
+            return int(fv)
+        elif os.path.isdir(os.path.join(branch_dir, 'patches')):
+            # There's a .git/patches/<branch>/patches dirctory, which
+            # means this is an initialized version 1 branch.
+            return 1
+        elif os.path.isdir(branch_dir):
+            # There's a .git/patches/<branch> directory, which means
+            # this is an initialized version 0 branch.
+            return 0
+        else:
+            # The branch doesn't seem to be initialized at all.
+            return None
+    def set_format_version(v):
+        config.set(format_version_key(branch), '%d' % v)
+    def mkdir(d):
+        if not os.path.isdir(d):
+            os.makedirs(d)
+    def rm(f):
+        if os.path.exists(f):
+            os.remove(f)
+
+    # Update 0 -> 1.
+    if get_format_version() == 0:
+        mkdir(os.path.join(branch_dir, 'trash'))
+        patch_dir = os.path.join(branch_dir, 'patches')
+        mkdir(patch_dir)
+        refs_dir = os.path.join(git_dir, 'refs', 'patches', branch)
+        mkdir(refs_dir)
+        for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
+                      + file(os.path.join(branch_dir, 'applied')).readlines()):
+            patch = patch.strip()
+            os.rename(os.path.join(branch_dir, patch),
+                      os.path.join(patch_dir, patch))
+            Patch(patch, patch_dir, refs_dir).update_top_ref()
+        set_format_version(1)
+
+    # Update 1 -> 2.
+    if get_format_version() == 1:
+        desc_file = os.path.join(branch_dir, 'description')
+        if os.path.isfile(desc_file):
+            desc = read_string(desc_file)
+            if desc:
+                config.set('branch.%s.description' % branch, desc)
+            rm(desc_file)
+        rm(os.path.join(branch_dir, 'current'))
+        rm(os.path.join(git_dir, 'refs', 'bases', branch))
+        set_format_version(2)
+
+    # Make sure we're at the latest version.
+    if not get_format_version() in [None, FORMAT_VERSION]:
+        raise StackException('Branch %s is at format version %d, expected %d'
+                             % (branch, get_format_version(), FORMAT_VERSION))
 
 class Series(StgitObject):
     """Class including the operations on series
@@ -290,6 +363,11 @@ class Series(StgitObject):
             raise StackException, 'GIT tree not initialised: %s' % ex
 
         self._set_dir(os.path.join(self.__base_dir, 'patches', self.__name))
+
+        # Update the branch to the latest format version if it is
+        # initialized, but don't touch it if it isn't.
+        update_to_current_format_version(self.__name, self.__base_dir)
+
         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
                                        self.__name)
 
@@ -299,19 +377,9 @@ class Series(StgitObject):
 
         # where this series keeps its patches
         self.__patch_dir = os.path.join(self._dir(), 'patches')
-        if not os.path.isdir(self.__patch_dir):
-            self.__patch_dir = self._dir()
-
-        # if no __refs_dir, create and populate it (upgrade old repositories)
-        if self.is_initialised() and not os.path.isdir(self.__refs_dir):
-            os.makedirs(self.__refs_dir)
-            for patch in self.get_applied() + self.get_unapplied():
-                self.get_patch(patch).update_top_ref()
 
         # trash directory
         self.__trash_dir = os.path.join(self._dir(), 'trash')
-        if self.is_initialised() and not os.path.isdir(self.__trash_dir):
-            os.makedirs(self.__trash_dir)
 
     def __patch_name_valid(self, name):
         """Raise an exception if the patch name is not valid.
@@ -410,19 +478,13 @@ class Series(StgitObject):
         return 'branch.%s.description' % self.get_branch()
 
     def get_description(self):
-        # Fall back to the .git/patches/<branch>/description file if
-        # the config variable is unset.
-        return (config.get(self.__branch_descr())
-                or self._get_field('description') or '')
+        return config.get(self.__branch_descr())
 
     def set_description(self, line):
         if line:
             config.set(self.__branch_descr(), line)
         else:
             config.unset(self.__branch_descr())
-        # Delete the old .git/patches/<branch>/description file if it
-        # exists.
-        self._set_field('description', None)
 
     def get_parent_remote(self):
         value = config.get('branch.%s.remote' % self.__name)
@@ -503,15 +565,16 @@ class Series(StgitObject):
     def is_initialised(self):
         """Checks if series is already initialised
         """
-        return os.path.isdir(self.__patch_dir)
+        return bool(config.get(format_version_key(self.get_branch())))
 
     def init(self, create_at=False, parent_remote=None, parent_branch=None):
         """Initialises the stgit series
         """
-        if os.path.exists(self.__patch_dir):
-            raise StackException, self.__patch_dir + ' already exists'
-        if os.path.exists(self.__refs_dir):
-            raise StackException, self.__refs_dir + ' already exists'
+        if self.is_initialised():
+            raise StackException, '%s already initialized' % self.get_branch()
+        for d in [self._dir(), self.__refs_dir]:
+            if os.path.exists(d):
+                raise StackException, '%s already exists' % d
 
         if (create_at!=False):
             git.create_branch(self.__name, create_at)
@@ -522,45 +585,10 @@ class Series(StgitObject):
 
         self.create_empty_field('applied')
         self.create_empty_field('unapplied')
-        os.makedirs(os.path.join(self._dir(), 'patches'))
         os.makedirs(self.__refs_dir)
         self._set_field('orig-base', git.get_head())
 
-    def convert(self):
-        """Either convert to use a separate patch directory, or
-        unconvert to place the patches in the same directory with
-        series control files
-        """
-        if self.__patch_dir == self._dir():
-            print 'Converting old-style to new-style...',
-            sys.stdout.flush()
-
-            self.__patch_dir = os.path.join(self._dir(), 'patches')
-            os.makedirs(self.__patch_dir)
-
-            for p in self.get_applied() + self.get_unapplied():
-                src = os.path.join(self._dir(), p)
-                dest = os.path.join(self.__patch_dir, p)
-                os.rename(src, dest)
-
-            print 'done'
-
-        else:
-            print 'Converting new-style to old-style...',
-            sys.stdout.flush()
-
-            for p in self.get_applied() + self.get_unapplied():
-                src = os.path.join(self.__patch_dir, p)
-                dest = os.path.join(self._dir(), p)
-                os.rename(src, dest)
-
-            if not os.listdir(self.__patch_dir):
-                os.rmdir(self.__patch_dir)
-                print 'done'
-            else:
-                print 'Patch directory %s is not empty.' % self.__patch_dir
-
-            self.__patch_dir = self._dir()
+        config.set(format_version_key(self.get_branch()), str(FORMAT_VERSION))
 
     def rename(self, to_name):
         """Renames a series
@@ -666,15 +694,6 @@ class Series(StgitObject):
             if os.path.exists(self._dir()+'/orig-base'):
                 os.remove(self._dir()+'/orig-base')
 
-            # Remove obsolete files that StGIT no longer uses, but
-            # that might still be around if this is an old repository.
-            for obsolete in ([os.path.join(self._dir(), fn)
-                              for fn in ['current', 'description']]
-                             + [os.path.join(self.__base_dir,
-                                             'refs', 'bases', self.__name)]):
-                if os.path.exists(obsolete):
-                    os.remove(obsolete)
-
             if not os.listdir(self.__patch_dir):
                 os.rmdir(self.__patch_dir)
             else:

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 4/5] Test the format version upgrade code
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
                               ` (2 preceding siblings ...)
  2007-05-19  0:10             ` [StGIT PATCH 3/5] Upgrade old StGIT branches to new-format metadata Karl Hasselström
@ 2007-05-19  0:10             ` Karl Hasselström
  2007-05-19  0:10             ` [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs Karl Hasselström
  4 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:10 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

This test contains tarballs of repositories created with older
versions of StGIT. It also contains the script used to generate them,
but at some point we will lose the ability to easily run old versions
-- for example, if git changes incompatibly -- so tarballs will be the
only practical option for sufficiently old versions.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t4000-upgrade.sh           |   40 ++++++++++++++++++++++++
 t/t4000-upgrade/.gitignore   |    4 ++
 t/t4000-upgrade/0.12.tar.gz  |  Bin
 t/t4000-upgrade/0.8.tar.gz   |  Bin
 t/t4000-upgrade/make-repo.sh |   71 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/t/t4000-upgrade.sh b/t/t4000-upgrade.sh
new file mode 100755
index 0000000..8a308fb
--- /dev/null
+++ b/t/t4000-upgrade.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Karl Hasselström
+#
+
+test_description='Make sure that we can use old StGIT repositories'
+
+. ./test-lib.sh
+
+for ver in 0.12 0.8; do
+
+    tar zxf ../t4000-upgrade/$ver.tar.gz
+    cd $ver
+
+    test_expect_success \
+        "v$ver: Check the list of applied and unapplied patches" '
+        [ "$(echo $(stg applied))" = "p0 p1 p2" ] &&
+        [ "$(echo $(stg unapplied))" = "p3 p4" ]
+    '
+
+    test_expect_success \
+        "v$ver: Make sure the 'description' file is no longer there" '
+        [ ! -e .git/patches/master/description ] &&
+        [ "$(echo $(git config branch.master.description))" = "cool branch" ]
+    '
+
+    test_expect_success \
+        "v$ver: Make sure the 'current' file is no longer there" '
+        [ ! -e .git/patches/master/current ]
+    '
+
+    test_expect_success \
+        "v$ver: Make sure the base ref is no longer there" '
+        ! git show-ref --verify --quiet refs/bases/master
+    '
+
+    cd ..
+done
+
+test_done
diff --git a/t/t4000-upgrade/.gitignore b/t/t4000-upgrade/.gitignore
new file mode 100644
index 0000000..d412eb6
--- /dev/null
+++ b/t/t4000-upgrade/.gitignore
@@ -0,0 +1,4 @@
+/stgit-0.8
+/stgit-0.12
+/0.8
+/0.12
diff --git a/t/t4000-upgrade/0.12.tar.gz b/t/t4000-upgrade/0.12.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..b183fcb1983eaa11c1a2eba3f0bb47b5c1c845c8
GIT binary patch
literal 11077
zc${r`WmFu|(k&7^NN{%z1or@g1b3I<5InfsAVGo+1b0gyIKeFpE+IGscX#(;`X%>%
z>#c9S_4;2|*Qs5#Yu8!lG)*iz!oV9fIRxZm6V*5dMLlY|&}~9|;}EPUQhgrXYVXB|
zJ(YHca{u!7y;2|V;F*<PyV8nwW?sI!oRr*yVHN$x=61Xa6dVr(99qsC1UkAC3_3T2
z&orULp@}U8slq>UUXgs2UH0XopU#9=<Wo0eIb4qFERDtaa&7hpaCQ2=vS-KEb?m%#
zP(-XVdK0Kym8KOqx-X#1Bd{`|qTKDSr#xrIP^G`jsn#o8_=>KE%IBbUhqDa;QV1!5
zfu#HnX@-`|`-&K6vYq>yu*(Zb1__j$p=I&@h#OvBUGdLYG5{U<-tNiX(grw2I;3tv
zF|*#ngUDGpjPhR6PID7Oz{<Jg;FWiPe%hZfk9|i8c+*}++o~YPY<(^<tk@9K(dT82
z74PT;e~Cd_g~7*zHVUID_Chv5eSh*&s%v%=8hK&HH_8kD2enqh)4uy)ws!^9O8}JY
zPVdc*U^Q0{YZ|{Ez(m`=C9{H=Q*Knf4om0xOAhu*^2K?lzSY$eX?vtIY<obqqBa~<
z@Oj$g_T@SL#Tswwzo5i%&<w;hYgBzRfP$QmYY>d$3BSip<3%wGf(scrw8G6W8b6Lq
zzo(k}pmMW_sjDN)w^aR6^y9Y=`#RHN{jQlNV}MXH=OrM=CHDA>xB3`(Gy3`&%+$Bo
zW^<TzxJcc|>c}QnF<X>Z)>x)!L99nKIK{8Z^$gfdp}=c6Wd~AWYj{^s(?{U-P@`zV
zn3Fya(da}&f@RjnTzjfg8LxQW!P%Q8%jU`{z=hdl8>q+!uz7%VI27OAaPY$O+lpp}
zbEAFx?7@6fRY2B?`)tDOq*r_K{+s&izZH|*Lz@W6xcC5yZ$M4huV<h}=x>ZU!T$ts
zXpCzb70o*MA=ucFc6t!$u-Np?i+|3%t&}ut)!FYGu%^=11>Afa^38mJ>i)~{gOa>k
z*#YZsL{f8YndXdBql6RB@8#LlAElyQ4G#WG#Oyfmh6l+0hetR!tGcMkI=w_bOE}{3
z{o{VgpQ-nGj?Ev{!^azP02fx1bznft+IRu5fHx}vW+<&V^_0C+R5nK?AKs&Ws~+SS
zEr^-3Z~PeBT3+7P@SlPnfW}eaZ}3mQ6Xcd`mX5e*1-$Z5byVg&7jv5`MBeo3sA3^W
zR{ZmA1(sf6r-5(NM_Q&H)ZsA5;_H0C0Apus4ZC+s{|e!LcF=6+3AA$$_0OUPi;`Z;
zQKAez-?NXy%Hptg!5J2R6*n;g103v5Zc0wjMqAjc6dkQWw8vy8xc+ekB8l^#VN%jx
z61#q{Qzm~Mi6*#x3YJD7`63Z(<=>|u4Kb8Fe&srJ66LEo&q65tE9mg9t_PcQUsEV~
z0y|2a<SWvMHt66B?aOVKdR9hm&S7rm8Dv@|m@xHY_p4e0-Xd4L2Y!n!=6h->9DW5$
zC+D0`;nI#2jOqJ>Lq&5~>Y1^!nOH<vR3pOv!Ca0UgqYv4l@!~8f49yk(mZlVSV`?8
zctm0lJ1A&{hsX@O=_eTSQYv7?63~MfW^a7XK%yN!fYhO*H0`5dYR5&SjuiDCu@Sg>
zwY(atE1ukx9x+(*3?gY}M65B{2g(bZN?>MOcYyx8k#q1lzB`E+CTy76ZCZWAH3|n=
z&d!tnxSqDm0|)Vh58odqE{dz8-o{U7LO$-+S9Hc1N8Dv8)zPBVfWUdR+0ULp?6{qb
z&>9*nY?~d*pCW6Lu5md5!pvZg%3}%l4v_dxaB9&V_DB=#Y3mb2prfwVmBkZw48Gj|
z38AK3h2GspL58sKWv+HYoccyf(X!*Keuf)aIsO#s4#=kbKH4pZJx?X;B)QFHsTI_Q
zQ(1wRI`i7^B8<C~q6aPbqYK0=%~v=o??c*FG$oQ-RCwN5!J=(Do($B_pi*y`vvy${
zUnoyaz>a~4Lsx-P4&&O2^xh0_Vc5TKNbc&uO3SbA+a^@ir+8rLfwG8A(CZ94e)%nw
zipW{D+><$oYOZDa0*p71`NXL{fO4-sU3vjcurK(#+k|kE06e1MQE%yoV)2v}#XKp|
zrB-W&aB>XF)mPH7BbK&%@tjfQ&E)kvh_jA>-cYO!cRyavbFfLH5&&gx<A76i!BiL|
z-K`~at<aIqMdF?r<K+6V7<l6lr*do2Gg^8(Rlh`|ULfoq-+z!s+#D*OK8fONy@?}q
zs>75D<4VT+5Tjf>6eC%CjAm*O8DB9K2gs|8&7CWManpp$^VnR0cJ82+pE!?zb&F0R
z`cgLh0&n5%xf3U*6mAmIkTp2XD8kPv6&(NgL=QFQQiJaq!>xY`1jd2%mjKr#*o4pK
z9IQP1Z<OCm1LNb!C>z4qgyXxO1&2EfT}S{L^#}yP-W?eZ34m(~9mJz|;jdj4ri*o+
zpgH%c4Wos4^_Vj-)6@@HsYy!=;6=ZFVBRGgVx`0fDHKgmiE?21OGO*j5Jb8TpIPjw
zwM)Dp>oO3ls$Yw(pP>lj*l(q2DUi&%w@73er4D|%g@A&WC%y<pu8K5|euT__ep!oM
zo5@kiaKF0rbQj$;=eeNBM|$gzZ^xSW+JM}DXP9-JED7x$9O1PH;n((#FMa+z$*&!^
zdz;rf@6M+o)E!V&wLx*RuT;e^TB9xchSq2p8TP5dyKP?8;HZW>2rCIIR4aVw&%ob)
z)2D*%jq?=i1M5-`Tx1BQnY5NEcT&z%H&s9;zVU_2(@Q?TGQ8`tSf^f`VQPQ3FwE7P
zXgR<G4Vw@^3K0M8Y=K#xwKmwNffqA=F~Qt*nYg!-r%coWZ4F+enw9-Qt`l_RDqk*H
zBWO0Bt<hU&KodtO`glJ1vzbPWcD5=UVf%NxR42-9w<;Y@q0|L8^?d?O*hOqv3QI*3
ziQc%2%6F-VSlc2u4}WZNaz|Uu@Zo1#E}cYP^(YkG;u(9j?}By1M49C+%%p0>lpYIe
z%AfU331#yyz}@Oy+Cpo~TiAm*w`O8HB`hNusNY6M2lV3SN0Ggr`&&GY`u~*Mn<3qD
z4ByNIKK^<CPQuqWmU4Mf%++N!%kSePwQ2g0wPp~n_ST=OR#_gBfQr_ZhH31l@N03~
zAiSE`snp+$LKPnL@<ef0wP|`{>@#40JeQ<jFR@-}bI15vWF5#+MipA~RE#byLDrU+
zg0uphb4Oh%QD4ROu41?z*6*XsxUSy>vSjZ1=#gs00of=di%Ei=X>1FrQ(`a@9vJxq
z(=TBXpe)8FWBPuad*lktUZ@E35u%Kve<xx+UG@q)GCuI{#M8AZuNDW9f6`DQe|eHe
z<Kx4Vp=igpxjoOh_A+dp_SWmqoU5(8@&<||&)Py2ai>+VNkvJt^zsvBvA5Xa5?vn#
zw-h8iVceoFoe}L5!l1EUP_BMCl4xR|=B$6{&B@YeRhG`eLehCeuTsQuCEYPXOK1kx
zE(NiyOh^-516Pj;E$gV|A?^ATw^)B&#;H3QQFrPVS<g58Nz@QyUwL7yP5pP)A;L0{
zXlBHrpEL~g&-+2|M2lzxJL7pz2if>=x#tz$DRy?V9<HVpWJU$MvP!t0xbmZAMG<{}
z>72z_yuxiBC=v8JCN$xbsRRuq9QLtAxVs_?XV@3aQS}MWC)monpaP=~VgM5B=HxG5
zXfiC<j|!LURL5*S>Z88=sWL(98o}m)7xs%HkQ*z=Hsl_2YNEh7d`hfKl9G!=gSV2e
z1xa%!dZ)KQNVE?#>M+H9pv^anZ20kN#(La!3D10FFXC`9>`-L1EbqNvA(bUdeV53z
zXE}U%%){P~A!2@9gg1jsL=)9G;wt;CX9G#-1!TyTmZdQ6v!+GELV>=iL)YsbS#6dt
zuen1ul@_^=_ag=jHO!q0QYpLi@pMbY5uF-=^%b+gO0fmSn!7Cqont*cBoZ-8*>oS#
zW;#wbk4Zu<j|n(d1Tlld*)$^B8=??i2vMudhqK`-a(l~%U(5&{E8<1;T4gy&z7xoH
zQ)1{@Hw}J=1i@G&Z1Zw|tb!y9$VS-mqTkJ-Z2hw8_A}Xf&K`Lmphe)T#PM44hR4(E
z=LCGuxS#{gNK`o%GY+@ymxOweLk9!72BI%Yw*b_Yc{HW^i%Q5Nqb?m6Dwtx6?spYI
zwoWcK-@<^XXpjSFYEhq2akJv0C8M~2`Yrj?P&`WyUu^x&1qO8)0#q@c`+c+R#uFi;
zEizg7;HSwSQJhj+zVlIE%H*At@fYZ6Py$2W@?eCZP!5J!2%Pu^!4jVEQAPTi%i_bb
zWO4}iU)><r4F_I}8t}{i?p{VBCG5Jm)~@=YaVl)^wg7oK8G9K6V{4$m?g!UVPljb$
zq`bk)8bksKE8L)Hl!xn`SOSp6r>d^UI5}||836-&aT_-(BZAuLOk$Ds_|FF71*X1r
zk$hbdft$E>G9be(UvdRTeeQul#cdC!7ccKp?fEy|*iI0c?e9H*nG=T$ND@HED9qLA
zk&N&4bdHNH&>Mp=yT6Qc_jg`SU@ED_OU-z<@e5mu+4r@7!slZzFJ&jS^8x8r)%s}C
z3Q#7oR+P%g4%#<}&YNwE7LN2ymZ&(D^BKn|(Z@2k#7J@sU^TdfW#UTU(rh)1q-(+_
zxi>L5TG-;Xe{bS65It=U2HDM(lEVG;*!hk`_50aypAC_Ah0)c!JA`J*?fJ=DTC(f-
z<C=}*_uoY*u#>-(xqstYz|a|%eF;Llrqv=Jc3p3`x?*dfFS3Jo)J<Dy1Pc#o8J3kx
z=6gA>z>Fm)%TPEF2AQ>2`*~KZ`6RpT1kK;GnQkRpvBt$};PQD3yB8gXMA*rGS`O_-
zpM2_zj$=@eO3FbLWYT7RM|f_IqxDtwM6Aj^LQSUnhl}fg`Fq6lh+I4zmHUeZTn$-z
z7F73u;zvUB&Bi_9CJy!MR~(M~d9u^g(Ua?(No?Yl;-~Wm-F%Y-S-T~Y{bCuRR)s<r
zhkhI{SocVJ;)UW`?q_xm%WSkmGBF3&HSroSGzyi`ejxB~c@`?X*W|KvYn%Nff8RnC
zqmf*p^~kI&XgSsU5XRiYriF{LRAVkkpYq|M2RgIw7jJ`!TR)e1XMoA2@%=Mca3TNw
zcM-3TYMDK{FprR>d&qL;n*|QojwiQvt-;u?YR6km-ZyiMd-YE4Rc3Obt@L~;w2Dt&
z`$+!xCY{?^FX`!1&OE@~$zSt;mtld2UrJqhZYZNE{8To*PR~X2Xj~!vhFpg0UiK?a
zQv)SmCrO_oXo5TuJzu@5sLxCpX&q^`e36k5Z|4wF%R^XS_MJWCZY9NxJo)f^pf{ZG
ziGK43f`<P)NQS4a+&M!|!BaS0A2RxiU7v?Ok9vgQgH}-R=M-OzANL4VR>lb9)yb6Z
zB%<goD6xAh?1FOHA642YougX?i0Dg#Izfpjq5XyDZS{;o=_OZSWr;^7YvWqL@o;(%
z5K!hC2md`_wF8cK(rCn@y&<T07%xZ*<PUyDS%_kX^c<sdSJGyHd#uIy)0ijg#MeK@
zc?WVapK9si(1~6O)z;qsVi_2S>VKz2P{`AfB+^ace27gQjztwlap2#FBZDJng^G1H
z&AAo*q2#DExF$+UBtSI8O8tTmk&O}}Ln;zBbTVlH*)u?}i|9tR)OX6aBjK-G8_a-^
z5wPjT4Sf%@r2WzpQ1v?0xi8>nmTn;W$eJ~y;AbAL8w19KDKDv1u?4z_K(iZa5-M!u
z!hX*XzUw{$c0)dL-QPSPh=y%2w_Gs^?6kKNH|ePF>B=NIUp}boF&|<25wtbRuM$3o
z3Kj7MG*(*Yy%}mxawt+l;62AG@`f97XCCab_%fP5QI^$9*SoC`@P4G&+`uH0*Hue5
z3Q6lyA)eR5e%PV7lR`(O^*o6R<&W+$3qwIOMH*NLe|czL>}vB}qtl4p?yE++qs8Gb
z&TDbWR3de|Q)@_-b#VBw-nCdu^k{f>E^mO`=lAd5Z(DVD%m;QOaffnS4m?{IO7wfk
z6eIQyo)%CXL~jH_x@q*}@5AsA5JX24lD38sek>NU5psNAZ#DnXRN|CNME<6lx~S5m
zn|VZ+*g4wG(~kbibV%_}fstD&9bdb@jd=&}n)CN<XmYDAiKPR=<{YiW5CI|*XOBCF
z5)Pk>DLAk5{reF=bEEjX$~K9QGP;+daW?qahK0$gJ@e+97Df)amFfyrw!^7nN*Hg4
zsu-x+==anmu_#*TkSR5fQaWheRq~vot;k59>ojO_(4vqHkeIg;lbEu3WDk2mZ{H^S
zhm#aG*vH3X#pLEFVwXm0utreeV`OutzvEt7cL*-(No#4PiMuaKEBGj>5j))=^u`ow
zlFU5QT}p6zxK!Y#lKEn}#4VL=m;A@&oq~<1*lYw7Gq!@AZDmeR34!Ry+XG7`<@9QE
zczysJl-rojycKo@O%n)$uW1<Vz|WNSweGpz#_ICp{Ki04^iL6z$0s#JFpee&AlSzx
zNt$t+NBC(XCYQK-gU3Te%|gmG6=C~L*+&IBNxJuoCyek12WFJK2|i0TgeCleDe}!J
z15S44od~rg+DiD+nIv?;o|aX$Y;sCq)>HSrtz490-htO*WTAh<2k)2DUQ}~mv!s7F
z*=jEFkuJ|QG+8z-QPq4~NU+?$&n7GX=+)8WkeErMyN4M|26~nJt~0mI#Jht_t$yJC
zU4NfUVPN-Y^R%GM?-z>_*beA4-x^WvakkJS6XhxoBGt5WIzHhh^Trgj@QEe8QA7a-
z_gGAa=fK7nPaqW-9M^;k47f4z3&4*vrzzShOaS&ak*jBr&BL=<MWH~a%2_`p&R~di
zUnr?>a#mQJWA6$X7FoXI?i0`V-s|JrfkjmHt&(mm8er2c?Tcrw^dnxBc{h5}qq8{n
zpJ>$w9*WMQJ^=v{Oba$#EGz^qn3HgN3<VC}yYLJrwNHZ*Y#eH@Vl0S>YUa4BT?DA3
zt}!|vc6gJ8WwqgutJ%vB#`n)Oi#o75{>I-x)hEuUr?u3~{HS!!r)j!WLp;v~GpefJ
zfTq$x9n`3$Kq9#8+h3DJ_P9(c=hKKH*5Q?)v1$Y#XpGL}c8-F0hQz;RTc~S%uTVMt
z8TA0=MwWu};Z4JpAL-NwXurEu&}!t-ZQoJh<{dP~_!oPk8xw5&BHz-032uh6vt>4P
z&iVfDCE4sp0~xF)eGP!(`!9@!xvRr%G){rS_=@@?2tWxSQ*vwpukE~HJUs2j|9(I=
z-#_5Wa1DZq8q8rAG_4=~UJkrS6R4IOO&ev6_!d#fHpA<r{VlR=zG=$=n<FUiC9Ty3
zY)y;#55Ns4=NS662AE(3b%5`Jd>?r31D>0~j|*qdi!QKfq0=(Z!x(O`&Qo&!Q}5Cp
zaN{zRK4=E(R)79OGkXoDfIr%TpPvDEl$jqa9k}d;KC0b^!vCznVM4${%iT{f1pEgM
zX#@p45;Ssk!t!pP!0<cpV>SrBa0cr?>%1ETX{Up)WlDkEMuI)inFMToWN{1#ynO~9
zXMOU6?$Dx6!l6&63!uC>c=}Ji(Ae_Br~9t&=+PmYVFn49s9~OW0fXFFwRn9f8U%B1
z@5W@~REfI-XJ&Zk^gV={<(V4YKPY1J^@U1GT5IF!5H@Rh(Cui><h_j(CoVE39KYt>
zTiSb}%C|?k3kKV^?)i+Z0BX>42ll%a!Ci64v8c}nrC*WB7$q{!WP9<R0S2NT_*A0b
z2Tq(kb=^1ue3pa=(m;=wNAbhm?CArIO{*~7cun~VsQ?GZyxb#kw8lqR;AJ<;x7`uc
z+e0pz^}4q~7cDvgfgU69Vk{%b+Ya~J{SGs(QKe;@ktHM7m4UAwy86j{RJn3zt4=;o
zKOMi-<1*uvlZ9UoRH=J;b4YYLIJ+h>cX|%aBr6Ai?!C`9lJ_wFT<%r{c(Zvse|wUt
zHc)GCdyZ^OeYh=b=?y*9MuTEKiHI;_gHqB~SiE<`J9y37rRTU-TX~Aketj`HutI8o
z59R6c4Q{?k&S@T9a<hnZ%KGdAko&owwJHmCb_dOWbg>(P^>ZRRC3;1*+|bm6wt3}|
z{6z^%iCHNxO_YeN?G>|rYIm0ORb?ppXp5#=KCmbmKW>u{5iPJ=>CiRNbe58niug3y
zvt&})<oeDH6}e7Lt*E&4|F%+0L4MfpQvaEJ3(Iu7j+j_}NQRy|@UPL0{HdHHH8jss
zbZT5KkNuF%Ci8oQMz4nSYnG!CF%b_xt8<W&^K#{I{kQ|peXfWOp~$>U@QeozPZXpl
zoxo#L-&mzh;oU=n+1Qr&sGHv{<`XNY(ka(qvGIpyVk6Tk>`{E0Gvc%NelaAv+e@a?
z1gArL!ZAsH+DTIZg(s`n*(M1(X%<8*Acr4))@x!P{O$r!f^{(mgVJrz#7>HhCfrm=
z-1G0kMive{Y;m#@+s#dDeE0HyqW;!1eLZhKHGR_%K3x6ji?%a`>hjbvj=0*}wWraj
z4{NriIqcP0mY<m`>wwCeU_q|JW7Hojyw2XGExjaTd*W)8Ld}n`lKeR)h?t-Qn-F|U
zYj259VtG&OW+_;k<<qFvnow5Y!eE7*#PZR3-Fd~**)5A`$vaN=@{XOcI52tkwR>d>
zUG9{&hZdheW+qCRh={$48#A+9N#84`)B`3a`<Tiiuy}mD7t6v=H$6uOXmW&nhqy)s
zVkzfaEEn6$pMMr{Z*rFRRFb}t=m}gXB#sxwunVka%dc^qEH)WuUad~NaM)QGD~_R0
z5VpDh_B*~A<G1P3H<@E0dE7g9Hfj2qYU@priil)A(wf~tRiCETb@)+@-=@Ah>*&{`
z(@!bmZ)cC&yvtm-!po-LuUzLtdkST9nE8*}9lOI%oEx)<@20t+@lRF`nAtz?Y<6-!
zqI@&^sM)a0+djD1zv@y0V|Lx%kqz?K&CYq~x7CS<9Y2=;QD)!rjx$Q1TJx(s(Sa2N
z_5qzq#o~3#?#eOTXlQk$!>ZcbrFjaYzYjJNceJw7jJ{f2@=<wcpHL00m#!(?sqrRb
z{aHC}_C5X9?0k9bB5{tTV$klgws(bF7aEMAru-`IR%uoFr3z!9!dZ8V(B}ym4Qn84
zY;8xi?>3j*t>@~Q*YF4#EpF+7qxT~*Rf?BagMHB4zHBPcPA`E+<TlT^TRj<D9RdY2
z%D@!Lhf^FLQpR%CS6UzDD}f=0y_?g)@X|n<$9=L_vK!R9)L0+OqpM^U6%jqe*fD_D
z^bQq4f@{G?{Rqn}uP3S1jgzdmUbn{@eb65P^R_P92d<6#n*j%eA|5@vJ<8R0qenLW
zVtI-BC>a6oi11VMc2AleVmJp_7T&p8j@?s^7Yn8$!r#ZD=ZoA{*F`;5j_s3=E>>tb
z@t4NLm&gzU+0PoI5nY$`$pV2jUt_GJ`iWADbBjjsX_jk-EZ&b&X#C;y^VAy{d$Z+q
zVSVODLCL_3npMi!RJL$$3jK=jJQ<=;x=KFY@C9<-989)uyJITgJFs)r_aa#}05EXx
zwOo!*VyV!J%EQT%7&+{kprpBL39u|~eX{*vSH1$(a(q07<OVzbnnVkzAAcJU8+DZY
zF>*FHt=d+#N=9IOcqRchQ-wwIt;nfS@t4U36K%Dni{Ec+#cqV&Q8s@hbH4F_<f%uL
z{T^jg_h3`&r&JQy5m$NtgnMbSJn+G-y+?Kg>zUZF;p&a??vnGvd$J1V=3284G~4qc
zJqk7t5jS=1ml1r!*tX-q;O`+{OT(d3#GruX%bz9fm{BJXAI2z}AL=ZRteR0%zmb@s
z@1~1|tK^;iTC-*B9i&R=?_Ovfr<n#z7fY2mKUCGI$QXM4bi6Jqhd*Oxv71UosT5W0
zoVcg=FTOb?znxIH^}G127@$Xzf9u;sw(37QP-?3)qxlpeDF4t`#Atd-m!3_sNVqse
zAVA*3qO_4yV`#<MK0Eok&M4SxdlWM0%zros5^K8}3>STWS0`TLw~K_yeOL3E9{&XE
zV;%A~b*f<MJNj3%tG!~A!6ICyHPqs6kmFeL&Yu;Phsf+vYw?3VLG4k~35})lIBTbZ
z&sb+q7bY<FJ{4`;-}%R@HjbUU#girC?<0Q^5s{p3Ten+d|03J<65|q@+v)gmY!^_!
zEiUJF<R%?}u+Lp{+)*I#N_yl_cKF1I$eDuRzKdmVk-3O_SgDDx3fo;F&{kJ3VchlW
zX2N7`tKOhd`;r{la8X(oM_IDsQC%5IYNsIBxYBTb@=v;^#G?s(q*us#?Y$wTI)AVr
z0j~T7WTCl_zeG)tsc*g@v^Y$Na&4D@f`<RZ>_dQl1TbONv}c~pKJ0bGJ8rxiT({Hg
z<<U}a&;P@3w}nd9`#`87O6<{XIe2+QugN8)f650)>n1g9mh_X$Rp(pkvB?{45#e~V
zW_h>K>?HR)FaxHWSmsnZuu1g@&KFF}^yV(Fyc!lR7X{i}O2vQK|DZYZHoffKu0W1j
zzD)VBY}Zcx`90Sh&STurM4guaOItjVgx}Wbg3?w(Qod?O@tPa!I}racj4J~YU_r)}
z2W(icJb)rC_%ZjufO#DJOI$wVgYs8m)f^*1DF-IHO#Vcp*kK*%sa08_^u)#J;i9Cn
z6SFTL2$(ATzubTr8w0*93ekJ{N*&&(&m4%9c_0rr<<0CU9xh7yZz&!!^RVS5w+SUK
z&iE8drg+;2Y4cuO=4q^*>vZI(w#T1nUo2Gigp_5&N<8h`g*~MPVpcYft}d|2f~ad+
zd4tkrXZ9B0&rv>yMomU3L!RTfYjMz3=XIzRbaz~py)}F6MwB^0S_Sf$0Ma|1Nxz5s
z&HmUaiCJ<{%RgodrWtSw^5EggKVrbK@iW0~Fb;MeA$GF=b9ju|evWJ@UNa*kN_2IX
z$^GNlT2MN-(TPm%gd@v2pLcvg5k}2>*iOK(n=e>a6118HULrFL)DqvkJl;ZfF~jvB
z%8n5_%HIE6<C0x|eVy0t>^=zj&6!;4@-cRSe~@3>N$|^2wsG`>i&<8EZ{TS4*ibnU
ziWz8s|9O7QS2x5!UCg3U8J3bk#)*Nl<9*a|da^R>eS>##lkpYw`>l#Wottm^(MrWQ
zRe-0%fD|;(^2(>GQ>1AAxA!v}GT`K{>fih+x`O>Qs&%<6TAc27uH-YB%gwk4MWBWB
z<0@ijX3Uv0=)PLD!vL@_Jg9pEN9({JBda;G1M3O<c(z6oA04ET@HbnAGoN;aT7srm
zb^Ri2wglexyPK$2f$s+O22Rzgm^+VB8XNC!qz3}O`aTC51<Mk6iSgJ$@2UrrR@2x?
z#)g_D-gE-7^(DkhS~YwU7oIz1$CaZq;Twp9{r&?pLgFMxep{E3SV#Dm>i+i}o{Llz
z24B40l7MteamT}K!_RKb-(AWXIy+%wrjV*%RIExSqEBgqhgT11CxdsdYAOT)!=qMI
zidws^dx~edhscKv;y~-hmd;o18>Kk_<ZRLF-aX|h=`vCG&gBW>Yjz9d*bahK&yc~N
zZ$Men8;`i?c?95-d+2an0rn$^wOkF>zrb+~Mz;7~bfFw{uJdQNSNqu!vJ|NLf&VMG
z@)Qmt3>^o*kojt~|9+fPAV?nzDZ2D=j>_@e=$D-&z-oH;0Aj_s1HvpCyo>h@PP`vM
z&-sg0^-kA>Ue+nMl~KWM+Ga8{g&Q5WGl3^=FKkL)pWxI4V}@3Vo6#|fMPp-3=9%cW
zTL>MFHy=E?g!BK_eg<0EHw98Azdz7aUx9lbXz=plfn*aNRYSSrEy&j51x*}qwAXqu
zCo%BGU{pwv3_BjsY)Amo3()5QWI80CSjka(?s-EB=J8puia~NC0`?(I;o*LMpGFIS
zh5o9=@C9POORbnbpxQ#!umR8i&If0ZEr3gP*^k!-x~t`N9w8x)d)%yjYDOj(0xGD0
z=@%G#U|KtF_^TmyGo<)@Ael6x`#xM!38uR-Ik{0-qai2r4Ep@%Gf0-c7C;M|xyoN(
zFYu$61tvFM)#%A>-9Ttv0U^Avd3~riYhp=7fc{USiSuVrTLRo3Kl~Yl7wORZayL5?
zhMZIc7G(A>x_wcb&RMK^1a5o=VUL4Rd!J%9VnRM|OkCI&D9nOSu8>Td;v%mh8(vS~
zluu9K(1Q4nzS{xnd0`vBBBFZIx)KwMz$a2qHO)Xx1^5RLbp@vNWsPJF3@<L+Ta!#G
z2j-Rl?eS~i#WjE+b+;_Wv+jy+o~SShR=BVDh~)r#27mce4M;yjgTG2Bq6lJ)5*!=>
z)f;qN2Pn=RaQl~sG%sDB*J_hm;K1c(`p+C7Dr_8#9F|3A-zZt8U|J@pku!**RDh9E
zsPiI<mo-N7`xh&b7TJQ#sam*Z7hGL>5BT&13Z-R&n?L7&8Urxqz@Z4wW|8rP&*3*<
zd{Kbq8jL7)sO?$^&t}oO#{B~*?!)r`kvj%_K5Qq)6Q`2}{w{5NBrywrL_@p=lM8Cy
z;Ew&rZ2~}fu@CS&ech+21d=BnKpQX`^a;RzUSSp_4D2PtX{rGv#77V|1=}Oq5y0pP
zSHFj$(Vjanv8<yPznc*rt$kUhQ?B#cxJ!o-^4gv$p&|ucO{-SXE>FP>K->Axitk|B
zt{ZT#+~y-l5-1%5ke}DgC3zC`E&1ciNvU^O8@oQgLFh>o8)hIJ!BjGW#WStLAvQ|A
zkAv4+z^6SZl9UqcW&?P)@k~Q238Y&v$O8Ts;GSndQUimMNhzm5Q>s5{6Ut4l$)_Z(
zq2&-qz%3v>Ehf#U0gMu`J<de|1W|vL^EVTfwb_t;jK8K>fM69sMEV~tmj96&g7BRG
zkJO`n0Aml341ufn%9X=IpFkUMuD`IQ{}+}<l2$QHvn-XA={s2(v4U|IMv7LMoyzBn
zPvj(cNWElQA*#|>?;amP<^anK2ygunYQf<4Ow$1noC2s}N|JU043RQjhLIDmO_|R5
z)uxalYFQx_iYjkdU+n|VCH$3OV-Q>&^ARNVx99&5pFn@J;p&x(f!}QY<pV$r)n`!p
zWXp!5>0!xNV2P2Vw<R`O)<;Ggqm88eg|WNrze=bf0a%WK(1I)Q=3_PLJ9ULgs3|P}
z9FB`u08G~=m4nye>Md~O|LMOO7hGK!K>J^m+<_7QSLeOp>VNkP#0|e3%T)A=0kXJ$
zJfn33qEKzp&&fzNmO4nxa3QSc{87Tc>JNF&cLhlPpURO`3t#>^Z}{#hrNO|#nb;s{
z^_ofEJSR(QDkY}KH#F+eCGT(PNVPz{H;n66dIG>eV!QYs<Q6#A-!ysuE7N*C0R0*q
z9=bQfFFzq%s<%N{RfLhYgCY?Bii4h2QBJnPk5JZ+_z?PUTlp*euYYIe2~7J}?Vma;
z5GM)#J0pkxOZ<m4IY9Ev18Dt`>%ZI-fotL8EYF~vzZL(ZpzINC4`AGQSVIW?CyT$0
z*Z{t6T!BL**?7_lNu|o<{4i@8L+J?B-QvD%@FXy@rW9!sGFc=~h$VHxaeo1ZOMsOI
zU><S=M8@~sfd7gs7eIT2c8Acj#-gLkYkvy)?yI)`!blO1GJ=P&Xj_+`Q|?9K`U7+{
zW$zFq`XxT={?{LM6SDYA@MaMl11V}8jMn3^HQxVDuDrJ>`)eHPGmY075Oam+3!tID
zB6V88&MbYz#o3y5t0|2`y@qK0{aW-w48VPvhx{zoM>Al_`)KZ(?|I?~hNlc0H7Aa`
zSg|LM#-tB#>w78wq5J=yF*1$Oalv0~L)JnNHz9g;65o+{_&&ciY~k)avs)euc*;cS
z&HY1=)a`#SLw9qj1JG?qX{z$A29(6f#RCpE5CT^K7w@nSr1ggXS$3l3-pUfj6US^0
zlhQDQv6LHjvekqQ9MKFmj)Oxlz|UeJc$=Sja>~W#1x<q1yOjqTlxL{9BxBL~ix?YL
zfue1ZFdLmskeH%nG`<x|4uLPFg<>d!f!f84=2zE>Ig|5hBw2f=&pd>oY&ngY*@vs8
zt}k)05^!u}avoo!9tLag!y%d5Q?oA>D^QeVFO?eS6V6*CG5AAyQ)1RNP-S^Ab=^7S
z`OCBJlO3`Y$8t3%k<?!MnO4c6pjx~l>}HMB*Q5+jd@*L35rWrYbmKmonDaKqtpmT<
zUI*5xk)VZ@bFcS_&hLvnYZ_5X!#xGN4NU8=&l7m*trgKH3)Yy^VA(E1JFD1rW>(>Q
z5<zp#KB@D&Mxyt_1<i%==HrHSE5-`cKly9o{fCWNC>BN{E2~$%h-_r^^uew84l+9W
z01WTn8!j#KU1WK6Ubtsnl2B?<c}2ce<U-;s+Cou$ycy`Kpnn9sH0rPdQ088lDFOP7
zL*Q-DCCvi_CJ$!-;vsi%$nzvo{UNsbnn&Om81lmIC(O1U<r%D*k>#**q$|{X>2Ht~
z>%pQ=TJA|WbF)Uhg3CA9{Mg*84}8wm1~lhWZEe3OihmssP2fiO&l3XL!jX7g6o{~n
Hi12>^a3NeI

literal 0
Hc$@<O00001

diff --git a/t/t4000-upgrade/0.8.tar.gz b/t/t4000-upgrade/0.8.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7a7d3c2f8d916dc79c55f0f0b2426005224aca97
GIT binary patch
literal 8224
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
S0001J1N;{VbU3j97y$r>Aaw))

literal 0
Hc$@<O00001

diff --git a/t/t4000-upgrade/make-repo.sh b/t/t4000-upgrade/make-repo.sh
new file mode 100644
index 0000000..98b5020
--- /dev/null
+++ b/t/t4000-upgrade/make-repo.sh
@@ -0,0 +1,71 @@
+# This script makes several versions of a small test repository that
+# can be used for testing the format version upgrade code.
+
+LANG=C
+LC_ALL=C
+PAGER=cat
+TZ=UTC
+export LANG LC_ALL PAGER TZ
+unset AUTHOR_DATE
+unset AUTHOR_EMAIL
+unset AUTHOR_NAME
+unset COMMIT_AUTHOR_EMAIL
+unset COMMIT_AUTHOR_NAME
+unset GIT_ALTERNATE_OBJECT_DIRECTORIES
+unset GIT_AUTHOR_DATE
+GIT_AUTHOR_EMAIL=author@example.com
+GIT_AUTHOR_NAME='A U Thor'
+unset GIT_COMMITTER_DATE
+GIT_COMMITTER_EMAIL=committer@example.com
+GIT_COMMITTER_NAME='C O Mitter'
+unset GIT_DIFF_OPTS
+unset GIT_DIR
+unset GIT_EXTERNAL_DIFF
+unset GIT_INDEX_FILE
+unset GIT_OBJECT_DIRECTORY
+unset SHA1_FILE_DIRECTORIES
+unset SHA1_FILE_DIRECTORY
+export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
+export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
+
+for ver in 0.12 0.8; do
+    if [ -e $ver.tar.gz ]; then continue; fi
+
+    # Get the required stgit version.
+    (
+        cd ../..
+        git archive --format=tar --prefix=stgit-$ver/ v$ver
+    ) | tar xf -
+
+    # Set up a repository.
+    mkdir $ver
+    cd $ver
+    git init
+    touch foo
+    git add foo
+    git commit -m 'Initial commit'
+
+    # Use the old stgit.
+    (
+        pwd
+        PATH=../stgit-$ver:$PATH
+
+        stg --version
+        stg init
+        echo 'cool branch' > .git/patches/master/description
+
+        for i in 0 1 2 3 4; do
+            stg new p$i -m "Patch $i"
+            echo "Line $i" >> foo
+            stg refresh
+        done
+        stg pop -n 2
+    )
+
+    # Reduce the number of small files.
+    git gc
+
+    # Make a tarball.
+    cd ..
+    tar zcf $ver.tar.gz $ver
+done

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs
  2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
                               ` (3 preceding siblings ...)
  2007-05-19  0:10             ` [StGIT PATCH 4/5] Test the format version upgrade code Karl Hasselström
@ 2007-05-19  0:10             ` Karl Hasselström
  2007-05-22 12:15               ` Catalin Marinas
  4 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-19  0:10 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

This just passes the --binary option to git-diff-*, which causes the
generated diffs to contain an applyable diff even when binary files
differ. It's necessary to do this if you want to mail patches to
binary files.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/commands/diff.py   |    6 +++++-
 stgit/commands/export.py |    5 ++++-
 stgit/commands/mail.py   |    6 +++++-
 stgit/git.py             |   16 ++++++++++++----
 4 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/stgit/commands/diff.py b/stgit/commands/diff.py
index 8678a0a..d3e1190 100644
--- a/stgit/commands/diff.py
+++ b/stgit/commands/diff.py
@@ -44,6 +44,9 @@ shows the specified patch (defaulting to the current one)."""
 options = [make_option('-r', '--range',
                        metavar = 'rev1[..[rev2]]', dest = 'revs',
                        help = 'show the diff between revisions'),
+           make_option('--binary',
+                       help = 'output a diff even for binary files',
+                       action = 'store_true'),
            make_option('-s', '--stat',
                        help = 'show the stat instead of the diff',
                        action = 'store_true')]
@@ -79,6 +82,7 @@ def func(parser, options, args):
     if options.stat:
         print git.diffstat(args, git_id(rev1), git_id(rev2))
     else:
-        diff_str = git.diff(args, git_id(rev1), git_id(rev2))
+        diff_str = git.diff(args, git_id(rev1), git_id(rev2),
+                            binary = options.binary)
         if diff_str:
             pager(diff_str)
diff --git a/stgit/commands/export.py b/stgit/commands/export.py
index 79b8630..20d8f67 100644
--- a/stgit/commands/export.py
+++ b/stgit/commands/export.py
@@ -62,6 +62,9 @@ options = [make_option('-d', '--dir',
                        help = 'Use FILE as a template'),
            make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
+           make_option('--binary',
+                       help = 'output a diff even for binary files',
+                       action = 'store_true'),
            make_option('-s', '--stdout',
                        help = 'dump the patches to the standard output',
                        action = 'store_true')]
@@ -172,7 +175,7 @@ def func(parser, options, args):
         # write the diff
         git.diff(rev1 = patch.get_bottom(),
                  rev2 = patch.get_top(),
-                 out_fd = f)
+                 out_fd = f, binary = options.binary)
         if not options.stdout:
             f.close()
         patch_no += 1
diff --git a/stgit/commands/mail.py b/stgit/commands/mail.py
index 151a408..2fcaa5f 100644
--- a/stgit/commands/mail.py
+++ b/stgit/commands/mail.py
@@ -120,6 +120,9 @@ options = [make_option('-a', '--all',
                        help = 'username for SMTP authentication'),
            make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
+           make_option('--binary',
+                       help = 'output a diff even for binary files',
+                       action = 'store_true'),
            make_option('-m', '--mbox',
                        help = 'generate an mbox file instead of sending',
                        action = 'store_true')]
@@ -390,7 +393,8 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
                  # for backward template compatibility
                  'endofheaders': '',
                  'diff':         git.diff(rev1 = git_id('%s//bottom' % patch),
-                                          rev2 = git_id('%s//top' % patch)),
+                                          rev2 = git_id('%s//top' % patch),
+                                          binary = options.binary),
                  'diffstat':     git.diffstat(rev1 = git_id('%s//bottom'%patch),
                                               rev2 = git_id('%s//top' % patch)),
                  # for backward template compatibility
diff --git a/stgit/git.py b/stgit/git.py
index 837f927..86630ce 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -771,20 +771,28 @@ def status(files = None, modified = False, new = False, deleted = False,
         else:
             print '%s' % fs[1]
 
-def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None):
+def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None,
+         binary = False):
     """Show the diff between rev1 and rev2
     """
     if not files:
         files = []
 
+    args = []
+    if binary:
+        args.append('--binary')
+
     if rev1 and rev2:
-        diff_str = _output(['git-diff-tree', '-p', rev1, rev2, '--'] + files)
+        diff_str = _output(['git-diff-tree', '-p'] + args
+                           + [rev1, rev2, '--'] + files)
     elif rev1 or rev2:
         refresh_index()
         if rev2:
-            diff_str = _output(['git-diff-index', '-p', '-R', rev2, '--'] + files)
+            diff_str = _output(['git-diff-index', '-p', '-R']
+                               + args + [rev2, '--'] + files)
         else:
-            diff_str = _output(['git-diff-index', '-p', rev1, '--'] + files)
+            diff_str = _output(['git-diff-index', '-p']
+                               + args + [rev1, '--'] + files)
     else:
         diff_str = ''
 

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGit PATCH 0/2] Bash prompt updates
  2007-05-06 15:13 [StGIT PATCH] Don't use patches/<branch>/current Karl Hasselström
  2007-05-15 15:56 ` Catalin Marinas
@ 2007-05-20 20:03 ` Robin Rosenberg
  2007-05-20 20:04   ` [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current' Robin Rosenberg
  2007-05-20 20:04   ` [StGit PATCH " Robin Rosenberg
  1 sibling, 2 replies; 51+ messages in thread
From: Robin Rosenberg @ 2007-05-20 20:03 UTC (permalink / raw)
  To: 20070506150852.8985.98091.stgit; +Cc: git

Here is a fix to update the bash prompt so it does not
use the obsolete current file anymore.

Part 2 is my version which uses a different format, mostly
because '/' can be part of the branch name.

-- robin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-20 20:03 ` [StGit PATCH 0/2] Bash prompt updates Robin Rosenberg
@ 2007-05-20 20:04   ` Robin Rosenberg
  2007-05-20 20:46     ` Yann Dirson
  2007-05-20 20:04   ` [StGit PATCH " Robin Rosenberg
  1 sibling, 1 reply; 51+ messages in thread
From: Robin Rosenberg @ 2007-05-20 20:04 UTC (permalink / raw)
  To: 20070506150852.8985.98091.stgit; +Cc: git

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---

 contrib/stgbashprompt.sh |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/contrib/stgbashprompt.sh b/contrib/stgbashprompt.sh
index 16bb39b..7c5dc76 100755
--- a/contrib/stgbashprompt.sh
+++ b/contrib/stgbashprompt.sh
@@ -8,8 +8,8 @@ if [ "$PS1" ]; then
 		git_dir=$(git-rev-parse --git-dir 2> /dev/null) || return
 		ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
 		br=${ref#refs/heads/}
-		top=$(cat $git_dir/patches/$br/current 2>/dev/null) \
-			&& top="/$top"
+		top=$(tail -1 $git_dir/patches/$br/applied 2>/dev/null) \
+			&& top="/$top";
 		echo "[$br$top]"
 	}
 	PS1='\u@\h:$(__prompt_git)\W\$ '

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGit PATCH 2/2] Don't use / as separatar since it is common i branch names
  2007-05-20 20:03 ` [StGit PATCH 0/2] Bash prompt updates Robin Rosenberg
  2007-05-20 20:04   ` [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current' Robin Rosenberg
@ 2007-05-20 20:04   ` Robin Rosenberg
  1 sibling, 0 replies; 51+ messages in thread
From: Robin Rosenberg @ 2007-05-20 20:04 UTC (permalink / raw)
  To: 20070506150852.8985.98091.stgit; +Cc: git

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---

 contrib/stgbashprompt.sh |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/contrib/stgbashprompt.sh b/contrib/stgbashprompt.sh
index 7c5dc76..732966a 100755
--- a/contrib/stgbashprompt.sh
+++ b/contrib/stgbashprompt.sh
@@ -8,9 +8,9 @@ if [ "$PS1" ]; then
 		git_dir=$(git-rev-parse --git-dir 2> /dev/null) || return
 		ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
 		br=${ref#refs/heads/}
-		top=$(tail -1 $git_dir/patches/$br/applied 2>/dev/null) \
-			&& top="/$top";
-		echo "[$br$top]"
+		top=$(tail -1 $git_dir/patches/$br/applied 2>/dev/null)
+		top=${top:-(none)}
+		echo "[$top@$br]"
 	}
 	PS1='\u@\h:$(__prompt_git)\W\$ '
 fi

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of  the obsolete 'current'
  2007-05-20 20:04   ` [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current' Robin Rosenberg
@ 2007-05-20 20:46     ` Yann Dirson
  2007-05-20 21:22       ` [PATCH " Robin Rosenberg
  2007-05-20 21:24       ` [PATCH 2/2] Don't use / as separatar since it is common i branch names Robin Rosenberg
  0 siblings, 2 replies; 51+ messages in thread
From: Yann Dirson @ 2007-05-20 20:46 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: 20070506150852.8985.98091.stgit, git

Note that "tail -1" gives a warning with newer versions, "tail -n 1"
should be the proper call.

Also I'm not sure it is a good way to look at "applied" file, since
Karl IIRC has plans to change this.  Better call "stg top" and not
touch that again :)

On Sun, May 20, 2007 at 10:04:03PM +0200, Robin Rosenberg wrote:
> Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
> ---
> 
>  contrib/stgbashprompt.sh |    4 ++--
>  1 files changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/contrib/stgbashprompt.sh b/contrib/stgbashprompt.sh
> index 16bb39b..7c5dc76 100755
> --- a/contrib/stgbashprompt.sh
> +++ b/contrib/stgbashprompt.sh
> @@ -8,8 +8,8 @@ if [ "$PS1" ]; then
>  		git_dir=$(git-rev-parse --git-dir 2> /dev/null) || return
>  		ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
>  		br=${ref#refs/heads/}
> -		top=$(cat $git_dir/patches/$br/current 2>/dev/null) \
> -			&& top="/$top"
> +		top=$(tail -1 $git_dir/patches/$br/applied 2>/dev/null) \
> +			&& top="/$top";
>  		echo "[$br$top]"
>  	}
>  	PS1='\u@\h:$(__prompt_git)\W\$ '
> 
> -
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> !DSPAM:4650aff673931961316905!
> 

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-20 20:46     ` Yann Dirson
@ 2007-05-20 21:22       ` Robin Rosenberg
  2007-05-21  7:48         ` Karl Hasselström
  2007-05-20 21:24       ` [PATCH 2/2] Don't use / as separatar since it is common i branch names Robin Rosenberg
  1 sibling, 1 reply; 51+ messages in thread
From: Robin Rosenberg @ 2007-05-20 21:22 UTC (permalink / raw)
  To: catalin.marinas; +Cc: ydirson, git

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=utf-8, Size: 1520 bytes --]

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>

söndag 20 maj 2007 skrev Yann Dirson:
> Note that "tail -1" gives a warning with newer versions, "tail -n 1"
> should be the proper call.
My man page doesn't mention -N being deprecated, but ok since -n 1 seems
to work here too.
 
> Also I'm not sure it is a good way to look at "applied" file, since
> Karl IIRC has plans to change this.  Better call "stg top" and not
> touch that again :)

Calling stg is too slow to be be used here. I that command in my first draft
for this function and people complained (see the thread named "Bash snippet
to show branch and patch in bash prompt"). It takes ~ 0.15s on here which is
very noticable, barely below my pain threshold. 

We'll update the prompt when and if Karl breaks this.

It'd probably drain my battery too :/

-- robin

 contrib/stgbashprompt.sh |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/contrib/stgbashprompt.sh b/contrib/stgbashprompt.sh
index 16bb39b..f4817a1 100755
--- a/contrib/stgbashprompt.sh
+++ b/contrib/stgbashprompt.sh
@@ -8,8 +8,8 @@ if [ "$PS1" ]; then
 		git_dir=$(git-rev-parse --git-dir 2> /dev/null) || return
 		ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
 		br=${ref#refs/heads/}
-		top=$(cat $git_dir/patches/$br/current 2>/dev/null) \
-			&& top="/$top"
+		top=$(tail -n 1 $git_dir/patches/$br/applied 2>/dev/null) \
+			&& top="/$top";
 		echo "[$br$top]"
 	}
 	PS1='\u@\h:$(__prompt_git)\W\$ '

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH 2/2] Don't use / as separatar since it is common i branch names
  2007-05-20 20:46     ` Yann Dirson
  2007-05-20 21:22       ` [PATCH " Robin Rosenberg
@ 2007-05-20 21:24       ` Robin Rosenberg
  1 sibling, 0 replies; 51+ messages in thread
From: Robin Rosenberg @ 2007-05-20 21:24 UTC (permalink / raw)
  To: catalin.marinas; +Cc: ydirson, git

    Don't use / as separatar since it is common i branch names
    
    Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>

diff --git a/contrib/stgbashprompt.sh b/contrib/stgbashprompt.sh
index f4817a1..5927e67 100755
--- a/contrib/stgbashprompt.sh
+++ b/contrib/stgbashprompt.sh
@@ -9,8 +9,8 @@ if [ "$PS1" ]; then
 		ref=$(git-symbolic-ref HEAD 2> /dev/null) || return
 		br=${ref#refs/heads/}
 		top=$(tail -n 1 $git_dir/patches/$br/applied 2>/dev/null) \
-			&& top="/$top";
-		echo "[$br$top]"
+		top=${top:-(none)}
+		echo "[$top@$br]"
 	}
 	PS1='\u@\h:$(__prompt_git)\W\$ '
 fi

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-20 21:22       ` [PATCH " Robin Rosenberg
@ 2007-05-21  7:48         ` Karl Hasselström
  2007-05-21  9:31           ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-21  7:48 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: catalin.marinas, ydirson, git

On 2007-05-20 23:22:00 +0200, Robin Rosenberg wrote:

> söndag 20 maj 2007 skrev Yann Dirson:
>
> > Also I'm not sure it is a good way to look at "applied" file,
> > since Karl IIRC has plans to change this. Better call "stg top"
> > and not touch that again :)
>
> Calling stg is too slow to be be used here. I that command in my
> first draft for this function and people complained (see the thread
> named "Bash snippet to show branch and patch in bash prompt"). It
> takes ~ 0.15s on here which is very noticable, barely below my pain
> threshold.
>
> We'll update the prompt when and if Karl breaks this.

Yes, I can confirm that I'm hard at work breaking this. :-) I'm trying
out a way to get around the performance bug Catalin found, but I
didn't have time to finish it yesterday.

If that work is included, you could simply find the top patch by doing
git-show-ref and figuring out which patch has the same sha1 as HEAD.

But it sucks that stg starts so slowly. It has gotten better, I
believe (I think Catalin did some work here?), but 150 ms doesn't
really qualify as "instantaneous".

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21  7:48         ` Karl Hasselström
@ 2007-05-21  9:31           ` Catalin Marinas
  2007-05-21 10:15             ` Karl Hasselström
  2007-05-21 18:57             ` Yann Dirson
  0 siblings, 2 replies; 51+ messages in thread
From: Catalin Marinas @ 2007-05-21  9:31 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Robin Rosenberg, ydirson, git

On 21/05/07, Karl Hasselström <kha@treskal.com> wrote:
> On 2007-05-20 23:22:00 +0200, Robin Rosenberg wrote:
> > Calling stg is too slow to be be used here. I that command in my
> > first draft for this function and people complained (see the thread
> > named "Bash snippet to show branch and patch in bash prompt"). It
> > takes ~ 0.15s on here which is very noticable, barely below my pain
> > threshold.
> >
> > We'll update the prompt when and if Karl breaks this.
>
> Yes, I can confirm that I'm hard at work breaking this. :-) I'm trying
> out a way to get around the performance bug Catalin found, but I
> didn't have time to finish it yesterday.

My plan is to release a 0.13 version pretty soon but without the DAG
patches as we might have to test them a bit more. The release after
0.13 I'd like to be a 1.0-rc1 (including the DAG patches) unless we
have some other major changes pending.

> But it sucks that stg starts so slowly. It has gotten better, I
> believe (I think Catalin did some work here?), but 150 ms doesn't
> really qualify as "instantaneous".

I don't think we can get much slower than this. I modified stg to only
load the modules needed for a given command but it still takes around
150ms for a command like 'top'. I don't know any other python tricks
to make it start faster.

BTW, any of you would like to get added as a member to
gna.org/projects/stgit (there are no advantages, only e-mail updates
for filed bug reports)?

Regards.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21  9:31           ` Catalin Marinas
@ 2007-05-21 10:15             ` Karl Hasselström
  2007-05-21 11:39               ` Karl Hasselström
  2007-05-21 18:57             ` Yann Dirson
  1 sibling, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-21 10:15 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Robin Rosenberg, ydirson, git

On 2007-05-21 10:31:09 +0100, Catalin Marinas wrote:

> My plan is to release a 0.13 version pretty soon but without the DAG
> patches as we might have to test them a bit more.

That's reasonable.

> The release after 0.13 I'd like to be a 1.0-rc1 (including the DAG
> patches) unless we have some other major changes pending.

It'd be great to do away with the need to "stg init", but that
shouldn't really be a major change (but I haven't started looking at
it yet). It would be nice if 1.0 had documentation that didn't have to
mention "stg init".

> I don't think we can get much slower than this.

Oh yes we can ... :-)

> I modified stg to only load the modules needed for a given command
> but it still takes around 150ms for a command like 'top'. I don't
> know any other python tricks to make it start faster.

I don't either. We might consider having plumbing written in C or
something, and make sure that the plumbing can be called directly if
there's need, but it's going to complicate things greatly compared to
pure Python.

> BTW, any of you would like to get added as a member to
> gna.org/projects/stgit (there are no advantages, only e-mail updates
> for filed bug reports)?

Free bug reports? Sure, I'm in! (I just created a Gna! account: kha)

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21 10:15             ` Karl Hasselström
@ 2007-05-21 11:39               ` Karl Hasselström
  2007-05-21 15:17                 ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-21 11:39 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Robin Rosenberg, ydirson, git

On 2007-05-21 12:15:40 +0200, Karl Hasselström wrote:

> We might consider having plumbing written in C or something, and
> make sure that the plumbing can be called directly if there's need,
> but it's going to complicate things greatly compared to pure Python.

What I'm (foggily) envisioning here is to rewrite parts of StGIT (as
little as possible) as a C library (libstgit.so?), and call the
library both from the Python code, and from a "plumbing" C program
(stgit-helper?). We should not try to make the library API stable,
just like the current git library.

There are two kinds of things we'd want to have in the library: (1)
things that are too slow to do in Python, and (2) things that need to
be available from stgit-helper in order to avoid Python's startup
cost, such as top/applied/unapplied for the bash completion and bash
prompt.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21 11:39               ` Karl Hasselström
@ 2007-05-21 15:17                 ` Catalin Marinas
  2007-05-21 15:39                   ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-21 15:17 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Robin Rosenberg, ydirson, git

On 21/05/07, Karl Hasselström <kha@treskal.com> wrote:
> On 2007-05-21 12:15:40 +0200, Karl Hasselström wrote:
>
> > We might consider having plumbing written in C or something, and
> > make sure that the plumbing can be called directly if there's need,
> > but it's going to complicate things greatly compared to pure Python.

Yes, it will complicate things. I think StGIT would have progressed at
a much slower pace if written in C :-).

> What I'm (foggily) envisioning here is to rewrite parts of StGIT (as
> little as possible) as a C library (libstgit.so?), and call the
> library both from the Python code, and from a "plumbing" C program
> (stgit-helper?). We should not try to make the library API stable,
> just like the current git library.

Apart from the start-up time, I don't see other major slowdowns caused
by Python. It would be useful to use a git library directly without
invoking external applications (I'm not sure what's the state of a
"libgit.a" or what improvement we would get).

As for the start-up time, unless you write most of the commands in C,
we would still have to load Python modules. If you run stg-prof
instead of stg for a simple command like 'top', you can see that the
main function takes about 60-70ms, the rest to 150ms reported by the
external 'time' is Python start-up and module loading.

I had a quick try at using "freeze.py" to generate a binary (well, it
includes python bytecodes but it might save time on module look-up)
but it got confused by my optimisation to only load module commands
based on the stg arguments. Maybe we should try this first.

> There are two kinds of things we'd want to have in the library: (1)
> things that are too slow to do in Python, and (2) things that need to
> be available from stgit-helper in order to avoid Python's startup
> cost, such as top/applied/unapplied for the bash completion and bash
> prompt.

As you probably guessed, I'm not really in favour of re-writing parts
of StGIT in C, at least not in the near future, though anyone can fork
and re-implement it :-).

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21 15:17                 ` Catalin Marinas
@ 2007-05-21 15:39                   ` Karl Hasselström
  2007-05-22 12:11                     ` Catalin Marinas
  0 siblings, 1 reply; 51+ messages in thread
From: Karl Hasselström @ 2007-05-21 15:39 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Robin Rosenberg, ydirson, git

On 2007-05-21 16:17:22 +0100, Catalin Marinas wrote:

> On 21/05/07, Karl Hasselström <kha@treskal.com> wrote:
>
> > On 2007-05-21 12:15:40 +0200, Karl Hasselström wrote:
> >
> > > We might consider having plumbing written in C or something, and
> > > make sure that the plumbing can be called directly if there's
> > > need, but it's going to complicate things greatly compared to
> > > pure Python.
>
> Yes, it will complicate things. I think StGIT would have progressed at
> a much slower pace if written in C :-).

I agree. C is not a good language to write a whole application in. But
for the hot spots, C is a reasonable choice.

> > What I'm (foggily) envisioning here is to rewrite parts of StGIT
> > (as little as possible) as a C library (libstgit.so?), and call
> > the library both from the Python code, and from a "plumbing" C
> > program (stgit-helper?). We should not try to make the library API
> > stable, just like the current git library.
>
> Apart from the start-up time, I don't see other major slowdowns
> caused by Python.

I haven't, either.

> It would be useful to use a git library directly without invoking
> external applications (I'm not sure what's the state of a "libgit.a"
> or what improvement we would get).

There is no usable git library yet. But once there is, I agree we
should use it.

> As for the start-up time, unless you write most of the commands in C,
> we would still have to load Python modules. If you run stg-prof
> instead of stg for a simple command like 'top', you can see that the
> main function takes about 60-70ms, the rest to 150ms reported by the
> external 'time' is Python start-up and module loading.

My suggestion was to have a small stand-alone C program that could do
some operations that need to be really fast, such as
top/applied/unapplied. It need not have a nice user interface since
it's only going to be called by scripts (bash-completion and the
like), and it should only handle those operations that _must- avoid
the Python startup penalty. And for sanity reasons, it should share
code with stgit.

> I had a quick try at using "freeze.py" to generate a binary (well,
> it includes python bytecodes but it might save time on module
> look-up) but it got confused by my optimisation to only load module
> commands based on the stg arguments. Maybe we should try this first.

I agree that we should try pure-Python optimizations first.

> > There are two kinds of things we'd want to have in the library:
> > (1) things that are too slow to do in Python, and (2) things that
> > need to be available from stgit-helper in order to avoid Python's
> > startup cost, such as top/applied/unapplied for the bash
> > completion and bash prompt.
>
> As you probably guessed, I'm not really in favour of re-writing
> parts of StGIT in C, at least not in the near future, though anyone
> can fork and re-implement it :-).

I wouldn't do it just for fun, either. But if it's a prerequisite to
get good enough performance for something we really want, and all else
has failed, I'd be willing to argue for the introduction of a C
library and helper application.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21  9:31           ` Catalin Marinas
  2007-05-21 10:15             ` Karl Hasselström
@ 2007-05-21 18:57             ` Yann Dirson
  1 sibling, 0 replies; 51+ messages in thread
From: Yann Dirson @ 2007-05-21 18:57 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Karl Hasselström, Robin Rosenberg, git

On Mon, May 21, 2007 at 10:31:09AM +0100, Catalin Marinas wrote:
> BTW, any of you would like to get added as a member to
> gna.org/projects/stgit (there are no advantages, only e-mail updates
> for filed bug reports)?

You should not disregard the added bonus of being able to be assigned
some of the bugs and tasks reported there :)

Best regards,
-- 
Yann.

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-21 15:39                   ` Karl Hasselström
@ 2007-05-22 12:11                     ` Catalin Marinas
  2007-05-22 13:29                       ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-22 12:11 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Robin Rosenberg, ydirson, git

On 21/05/07, Karl Hasselström <kha@treskal.com> wrote:
> My suggestion was to have a small stand-alone C program that could do
> some operations that need to be really fast, such as
> top/applied/unapplied. It need not have a nice user interface since
> it's only going to be called by scripts (bash-completion and the
> like), and it should only handle those operations that _must- avoid
> the Python startup penalty. And for sanity reasons, it should share
> code with stgit.

There is one more case to consider - people using NFS-mounted
directories. The applied/unapplied commands would be even slower and
the language overhead be negligible.

Another workaround would be to always generate the applied/unapplied
files when the stack structure changes.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs
  2007-05-19  0:10             ` [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs Karl Hasselström
@ 2007-05-22 12:15               ` Catalin Marinas
  2007-05-22 13:31                 ` Karl Hasselström
  0 siblings, 1 reply; 51+ messages in thread
From: Catalin Marinas @ 2007-05-22 12:15 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

On 19/05/07, Karl Hasselström <kha@treskal.com> wrote:
> This just passes the --binary option to git-diff-*, which causes the
> generated diffs to contain an applyable diff even when binary files
> differ. It's necessary to do this if you want to mail patches to
> binary files.

I applied this patch but is there anything wrong if we have this
option on by default, at least for some commands? Maybe we don't need
it for 'show' and 'diff' but we definitely need it for 'mail' and
'export'.

There is also git.apply_diff() which calls git.diff(). This is first
tried when pushing a patch and followed by a three-way merged if it
fails. I think we should always have the --binary option in this case.

-- 
Catalin

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current'
  2007-05-22 12:11                     ` Catalin Marinas
@ 2007-05-22 13:29                       ` Karl Hasselström
  0 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-22 13:29 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Robin Rosenberg, ydirson, git

On 2007-05-22 13:11:13 +0100, Catalin Marinas wrote:

> On 21/05/07, Karl Hasselström <kha@treskal.com> wrote:
>
> > My suggestion was to have a small stand-alone C program that could
> > do some operations that need to be really fast, such as
> > top/applied/unapplied. It need not have a nice user interface
> > since it's only going to be called by scripts (bash-completion and
> > the like), and it should only handle those operations that _must-
> > avoid the Python startup penalty. And for sanity reasons, it
> > should share code with stgit.
>
> There is one more case to consider - people using NFS-mounted
> directories. The applied/unapplied commands would be even slower and
> the language overhead be negligible.
>
> Another workaround would be to always generate the applied/unapplied
> files when the stack structure changes.

Yes, we could do that. These files would only be accurate when the
stack was last modified with StGIT and not plain git, but that might
be acceptable.

Hmm. Since the only way plain git modifies the stack is by changing
HEAD (we assume the user doesn't manually mess with the patch refs),
we might also write down the value of HEAD for which the
applied/unapplied files are valid, so that the caller could call "stg
applied" if the applied file was out of date. But that's quite a
hassle to have to reimplement every time.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs
  2007-05-22 12:15               ` Catalin Marinas
@ 2007-05-22 13:31                 ` Karl Hasselström
  0 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-05-22 13:31 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2007-05-22 13:15:13 +0100, Catalin Marinas wrote:

> On 19/05/07, Karl Hasselström <kha@treskal.com> wrote:
>
> > This just passes the --binary option to git-diff-*, which causes
> > the generated diffs to contain an applyable diff even when binary
> > files differ. It's necessary to do this if you want to mail
> > patches to binary files.
>
> I applied this patch but is there anything wrong if we have this
> option on by default, at least for some commands? Maybe we don't
> need it for 'show' and 'diff' but we definitely need it for 'mail'
> and 'export'.

I'd be fine with that.

> There is also git.apply_diff() which calls git.diff(). This is first
> tried when pushing a patch and followed by a three-way merged if it
> fails. I think we should always have the --binary option in this
> case.

Yes, that sounds good.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGIT PATCH 0/6] New and improved DAG appliedness series
  2007-05-18  6:30                     ` Karl Hasselström
@ 2007-06-10  9:54                       ` Karl Hasselström
  2007-06-10  9:54                         ` [StGIT PATCH 1/6] Verify patch status during the test Karl Hasselström
                                           ` (6 more replies)
  0 siblings, 7 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:54 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

This took a while, but here it is. (Actually, I finished this about a
week ago, but had no Internet connection so I couldn't send it.)

The series starts with basically the same DAG appliedness test as
before, with the same known performance bug. Later on in the series,
the mechanism is changed to one that doesn't have the bug. I kept the
intermediate, slow state because the changesets read better that way,
and because the new mechanism is more complicated than the old so it
might be useful to be able to compare their output in case some bug
turns up further down the road.

To test the performance, I used a script (which I've unfortunately
misplaced) that (in the kernel repository) reset to one point 10000
commits in the past and one 5000 commits in the past, pushed a few
patches at each spot, and then created 100 applied and 100 unapplied
patches on top of upstream HEAD. This triggers the performance bug
with the first algorithm since we have unapplied commits very far from
HEAD.

Both algorithms are documented in the patches that introduce them.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [StGIT PATCH 1/6] Verify patch status during the test
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
@ 2007-06-10  9:54                         ` Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 2/6] Make use of the get_patch() utility function Karl Hasselström
                                           ` (5 subsequent siblings)
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:54 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git



Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t1200-push-modified.sh |   55 +++++++++++++++++++++++++++-------------------
 1 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/t/t1200-push-modified.sh b/t/t1200-push-modified.sh
index 6769667..aa4ffd0 100755
--- a/t/t1200-push-modified.sh
+++ b/t/t1200-push-modified.sh
@@ -19,23 +19,26 @@ specify --merged, then rollback and retry with the correct flag.'
 test_create_repo foo
 
 test_expect_success \
-    'Clone tree and setup changes' \
-    "stg clone foo bar &&
-     (cd bar && stg new p1 -m p1
-      printf 'a\nc\n' > file && stg add file && stg refresh &&
-      stg new p2 -m p2
-      printf 'a\nb\nc\n' > file && stg refresh
-     )
-"
+    'Clone tree and setup changes' '
+    stg clone foo bar &&
+    (
+        cd bar && stg new p1 -m p1
+        printf "a\nc\n" > file && stg add file && stg refresh &&
+        stg new p2 -m p2 &&
+        printf "a\nb\nc\n" > file && stg refresh &&
+        [ "$(echo $(stg applied))" = "p1 p2" ] &&
+        [ "$(echo $(stg unapplied))" = "" ]
+    )
+'
 
 test_expect_success \
-    'Port those patches to orig tree' \
-    '(cd foo &&
-      GIT_DIR=../bar/.git git-format-patch --stdout \
-          $(cd ../bar && stg id base@master)..HEAD |
-      git-am -3 -k
-     )
-    '
+    'Port those patches to orig tree' '
+    (
+        cd foo &&
+        GIT_DIR=../bar/.git git-format-patch --stdout \
+          $(cd ../bar && stg id base@master)..HEAD | git-am -3 -k
+    )
+'
 
 test_expect_success \
     'Pull to sync with parent, preparing for the problem' \
@@ -51,15 +54,21 @@ test_expect_failure \
 "
 
 test_expect_success \
-    'Rollback the push' \
-    "(cd bar && stg push --undo
-     )
-"
+    'Rollback the push' '
+    (
+        cd bar && stg push --undo &&
+        [ "$(echo $(stg applied))" = "" ] &&
+        [ "$(echo $(stg unapplied))" = "p1 p2" ]
+    )
+'
 
 test_expect_success \
-    'Push those patches while checking they were merged upstream' \
-    "(cd bar && stg push --merged --all
-     )
-"
+    'Push those patches while checking they were merged upstream' '
+    (
+        cd bar && stg push --merged --all
+        [ "$(echo $(stg applied))" = "p1 p2" ] &&
+        [ "$(echo $(stg unapplied))" = "" ]
+    )
+'
 
 test_done

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 2/6] Make use of the get_patch() utility function
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
  2007-06-10  9:54                         ` [StGIT PATCH 1/6] Verify patch status during the test Karl Hasselström
@ 2007-06-10  9:55                         ` Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 3/6] Compute patch appliedness from commit DAG Karl Hasselström
                                           ` (4 subsequent siblings)
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

We already had it, but no one was using it

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/stack.py |   27 +++++++++++++--------------
 1 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index 7a06458..0a486bd 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -404,7 +404,7 @@ class Series(StgitObject):
         crt = self.get_current()
         if not crt:
             return None
-        return Patch(crt, self.__patch_dir, self.__refs_dir)
+        return self.get_patch(crt)
 
     def get_current(self):
         """Return the name of the topmost patch, or None if there is
@@ -678,7 +678,7 @@ class Series(StgitObject):
                 raise StackException, \
                       'Cannot delete: the series still contains patches'
             for p in patches:
-                Patch(p, self.__patch_dir, self.__refs_dir).delete()
+                self.get_patch(p).delete()
 
             # remove the trash directory
             for fname in os.listdir(self.__trash_dir):
@@ -732,7 +732,7 @@ class Series(StgitObject):
         if not name:
             raise StackException, 'No patches applied'
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         descr = patch.get_description()
         if not (message or descr):
@@ -798,7 +798,7 @@ class Series(StgitObject):
         name = self.get_current()
         assert(name)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
 
@@ -839,7 +839,7 @@ class Series(StgitObject):
         if name == None:
             name = make_patch_name(descr, self.patch_exists)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         patch.create()
 
         if bottom:
@@ -919,7 +919,7 @@ class Series(StgitObject):
         for name in names:
             assert(name in unapplied)
 
-            patch = Patch(name, self.__patch_dir, self.__refs_dir)
+            patch = self.get_patch(name)
 
             head = top
             bottom = patch.get_bottom()
@@ -988,8 +988,7 @@ class Series(StgitObject):
         patches detected to have been applied. The state of the tree
         is restored to the original one
         """
-        patches = [Patch(name, self.__patch_dir, self.__refs_dir)
-                   for name in names]
+        patches = [self.get_patch(name) for name in names]
         patches.reverse()
 
         merged = []
@@ -1008,7 +1007,7 @@ class Series(StgitObject):
         unapplied = self.get_unapplied()
         assert(name in unapplied)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         head = git.get_head()
         bottom = patch.get_bottom()
@@ -1084,7 +1083,7 @@ class Series(StgitObject):
         name = self.get_current()
         assert(name)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
 
@@ -1110,7 +1109,7 @@ class Series(StgitObject):
         applied.reverse()
         assert(name in applied)
 
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
 
         if git.get_head_file() == self.get_branch():
             if keep and not git.apply_diff(git.get_head(), patch.get_bottom()):
@@ -1142,7 +1141,7 @@ class Series(StgitObject):
         """Returns True if the patch is empty
         """
         self.__patch_name_valid(name)
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        patch = self.get_patch(name)
         bottom = patch.get_bottom()
         top = patch.get_top()
 
@@ -1171,14 +1170,14 @@ class Series(StgitObject):
             self.hide_patch(newname)
 
         if oldname in unapplied:
-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
+            self.get_patch(oldname).rename(newname)
             unapplied[unapplied.index(oldname)] = newname
 
             f = file(self.__unapplied_file, 'w+')
             f.writelines([line + '\n' for line in unapplied])
             f.close()
         elif oldname in applied:
-            Patch(oldname, self.__patch_dir, self.__refs_dir).rename(newname)
+            self.get_patch(oldname).rename(newname)
 
             applied[applied.index(oldname)] = newname
 

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 3/6] Compute patch appliedness from commit DAG
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
  2007-06-10  9:54                         ` [StGIT PATCH 1/6] Verify patch status during the test Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 2/6] Make use of the get_patch() utility function Karl Hasselström
@ 2007-06-10  9:55                         ` Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 4/6] Test the new DAG appliedness machinery Karl Hasselström
                                           ` (3 subsequent siblings)
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Don't rely on cached metadata in the "applied" and "unapplied" files
to tell which patches are applied. Instead, consider the patches
reachable from the branch head to be applied, and the rest unapplied.

The order of the applied patches is also taken from the DAG, but we
can't do that for the unapplied patches. So the patch order is saved
to a file whenever it changes, and that file is consulted whenever we
need to compute the order of the unapplied patches.

The point of this excercise is to let users do things such as "git
reset" without confusing stgit. This gives incrased flexibility to
power users, and increased safety to other users. The advantages come
from the removal of redundant metadata: it is no longer possible for
StGIT's appliedness status to get out of sync with the underlying git
commit DAG.

This is how the appliedness and order is computed:

  * First, a single call to git-show-ref gives the hashes of all
    patches and the branch head.

  * Then, "git-rev-list patch1 patch2 patch3 ^branch" lists a small
    set of hashes that contains all the unapplied patches and none of
    the applied patches.

  * Last, "git-rev-list head" lists all commits in the branch. The
    applied patches are listed in the correct order.

This is efficient because none of the two rev-list calls need to look
at more than a small part of the DAG. The first call returns a small
set of commits, and the last call is abandoned before it has time to
look far back in the DAG.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/commands/commit.py  |    8 -
 stgit/commands/float.py   |    2 
 stgit/commands/imprt.py   |    2 
 stgit/commands/refresh.py |    2 
 stgit/commands/sync.py    |    2 
 stgit/git.py              |    5 +
 stgit/stack.py            |  333 +++++++++++++++++++++++++++++----------------
 t/t4000-upgrade.sh        |    6 +
 8 files changed, 227 insertions(+), 133 deletions(-)

diff --git a/stgit/commands/commit.py b/stgit/commands/commit.py
index 2b8d7ce..5450112 100644
--- a/stgit/commands/commit.py
+++ b/stgit/commands/commit.py
@@ -52,14 +52,8 @@ def func(parser, options, args):
     if crt_series.get_protected():
         raise CmdException, 'This branch is protected.  Commit is not permitted'
 
-    crt_head = git.get_head()
-
     out.start('Committing %d patches' % len(applied))
-
-    crt_series.pop_patch(applied[0])
-    git.switch(crt_head)
-
     for patch in applied:
-        crt_series.delete_patch(patch)
+        crt_series.delete_patch_data(patch)
 
     out.done()
diff --git a/stgit/commands/float.py b/stgit/commands/float.py
index 0e32f6b..8ba76d5 100644
--- a/stgit/commands/float.py
+++ b/stgit/commands/float.py
@@ -48,7 +48,7 @@ def func(parser, options, args):
     check_head_top_equal()
 
     unapplied = crt_series.get_unapplied()
-    applied = crt_series.get_applied()
+    applied = list(crt_series.get_applied()) # a copy, since we'll modify it
     all = unapplied + applied
 
     if options.series:
diff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py
index 0089a8b..b7a34fa 100644
--- a/stgit/commands/imprt.py
+++ b/stgit/commands/imprt.py
@@ -294,7 +294,7 @@ def __create_patch(filename, message, author_name, author_email,
         git.apply_patch(diff = diff, base = git_id(options.base))
     else:
         git.apply_patch(diff = diff)
-    crt_series.refresh_patch(edit = options.edit,
+    crt_series.refresh_patch(patch, edit = options.edit,
                              show_patch = options.showpatch)
     out.done()
 
diff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py
index 77dcbda..d560951 100644
--- a/stgit/commands/refresh.py
+++ b/stgit/commands/refresh.py
@@ -131,7 +131,7 @@ def func(parser, options, args):
 
         if autoresolved == 'yes':
             resolved_all()
-        crt_series.refresh_patch(files = args,
+        crt_series.refresh_patch(patch, files = args,
                                  message = options.message,
                                  edit = options.edit,
                                  show_patch = options.showpatch,
diff --git a/stgit/commands/sync.py b/stgit/commands/sync.py
index 8359061..a42eeac 100644
--- a/stgit/commands/sync.py
+++ b/stgit/commands/sync.py
@@ -161,7 +161,7 @@ def func(parser, options, args):
         if git.local_changes(verbose = False):
             # index (cache) already updated by the git merge. The
             # backup information was already reset above
-            crt_series.refresh_patch(cache_update = False, backup = False,
+            crt_series.refresh_patch(p, cache_update = False, backup = False,
                                      log = 'sync')
             out.done('updated')
         else:
diff --git a/stgit/git.py b/stgit/git.py
index 845c712..703425b 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -189,8 +189,11 @@ def _output_one_line(cmd, file_desc = None):
                                                 p.childerr.read().strip())
     return output
 
-def _output_lines(cmd):
+def _output_lines(cmd, input = []):
     p=popen2.Popen3(cmd, True)
+    for line in input:
+        p.tochild.write(line)
+    p.tochild.close()
     lines = p.fromchild.readlines()
     if p.wait():
         raise GitException, '%s failed (%s)' % (str(cmd),
diff --git a/stgit/stack.py b/stgit/stack.py
index 0a486bd..e622f68 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -18,12 +18,13 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re
+import sys, os, popen2, re
 
 from stgit.utils import *
 from stgit import git, basedir, templates
 from stgit.config import config
 from shutil import copyfile
+from sets import Set
 
 
 # stack exception class
@@ -274,7 +275,7 @@ class Patch(StgitObject):
         self.__update_log_ref(value)
 
 # The current StGIT metadata format version.
-FORMAT_VERSION = 2
+FORMAT_VERSION = 3
 
 def format_version_key(branch):
     return 'branch.%s.stgitformatversion' % branch
@@ -343,11 +344,173 @@ def update_to_current_format_version(branch, git_dir):
         rm(os.path.join(git_dir, 'refs', 'bases', branch))
         set_format_version(2)
 
+    # Update 2 -> 3.
+    if get_format_version() == 2:
+        patchorder = file(os.path.join(branch_dir, 'patchorder'), 'w')
+        for pf in ['applied', 'unapplied']:
+            pfn = os.path.join(branch_dir, pf)
+            if not os.path.isfile(pfn):
+                continue
+            for line in file(pfn):
+                line = line.strip()
+                if line:
+                    patchorder.write(line + '\n')
+            rm(pfn)
+        patchorder.close()
+        set_format_version(3)
+
     # Make sure we're at the latest version.
     if not get_format_version() in [None, FORMAT_VERSION]:
         raise StackException('Branch %s is at format version %d, expected %d'
                              % (branch, get_format_version(), FORMAT_VERSION))
 
+class PatchorderCache:
+    """An object that keeps track of the patch order for a series, as
+    saved in its patchorder file."""
+    def __init__(self, series):
+        self.__series = series
+        self.__invalidate()
+    def __invalidate(self):
+        self.__patchnames = None
+        self.__position = None
+    def __cache(self):
+        if self.__patchnames != None:
+            return # already cached
+
+        self.__patchnames = []
+        self.__position = {}
+        pof = os.path.join(self.__series._dir(), 'patchorder')
+        if os.path.isfile(pof):
+            for line in file(pof):
+                name = line.strip()
+                assert not name in self.__position
+                self.__position[name] = len(self.__patchnames)
+                self.__patchnames.append(name)
+    def set_patchorder(self, new_order):
+        self.__invalidate()
+        f = file(os.path.join(self.__series._dir(), 'patchorder'), 'w')
+        for name in new_order:
+            f.write('%s\n' % name)
+        f.close()
+    def cmp(self, name1, name2):
+        """Compare two patch names to see which patch comes first. If
+        both patches are listed in the patchorder file, sort them by
+        the order they appear there; if one is listed and the other
+        not, the listed patch goes first; and if neither is listed,
+        sort them by their names."""
+        self.__cache()
+        largepos = len(self.__patchnames)
+        pos1 = self.__position.get(name1, largepos)
+        pos2 = self.__position.get(name2, largepos)
+        return cmp((pos1, name1), (pos2, name2))
+
+def read_refs(branch):
+    """Return a mapping from patches and branch head to hashes for a
+    given branch. The patches are listed by name; the branch head is
+    None."""
+    refs = {}
+    patchpat = re.compile(r'^refs/patches/%s/([^\.]+)$' % branch)
+    for line in git._output_lines('git-show-ref'):
+        sha1, ref = line.split()
+        m = re.match(patchpat, ref)
+        if m:
+            refs[m.group(1)] = sha1
+        elif ref == 'refs/heads/%s' % branch:
+            refs[None] = sha1
+    return refs
+
+def unapplied_patches(ref2hash):
+    """Given a map of patch names (and the branch head, keyed by None)
+    to hashes, return the set of unapplied patches."""
+    hash2refs = {}
+    for r, h in ref2hash.iteritems():
+        hash2refs.setdefault(h, Set()).add(r)
+
+    unapplied = Set()
+    for line in git._output_lines(
+        'git-rev-list --stdin',
+        ('%s%s\n' % (['', '^'][ref == None], sha1)
+         for ref, sha1 in ref2hash.iteritems())):
+        for ref in hash2refs.get(line.strip(), []):
+            unapplied.add(ref)
+    return unapplied
+
+def sort_applied_patches(ref2hash):
+    """Given a map of patch names (and the branch head, keyed by None)
+    to hashes, return a list with the applied patches in stack order.
+    All patches in the map must be applied."""
+    hash2refs = {}
+    for r, h in ref2hash.iteritems():
+        if r != None:
+            hash2refs.setdefault(h, Set()).add(r)
+
+    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)
+    if not missing:
+        return []
+    applied = []
+    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)
+    for line in grl.fromchild:
+        for ref in hash2refs.get(line.strip(), []):
+            applied.append(ref)
+            missing.remove(ref)
+        if not missing:
+            applied.reverse()
+            return applied
+
+    raise StackException, 'Could not find patches: %s' % ', '.join(missing)
+
+class AppliedCache:
+    """An object that keeps track of the appliedness and order of the
+    patches in a patch series."""
+    def __init__(self, series):
+        self.__series = series
+        self.__order = PatchorderCache(series)
+        self.__invalidate()
+    def get_applied(self):
+        self.__cache()
+        return self.__applied
+    def get_unapplied(self):
+        self.__cache()
+        return self.__unapplied
+    def rename(self, oldname, newname):
+        """Rename a patch."""
+        self.__cache()
+        for lst in (self.__applied, self.__unapplied):
+            try:
+                lst[lst.index(oldname)] = newname
+            except ValueError:
+                pass # lst.index() couldn't find the index
+            else:
+                self.__write_patchorder()
+                return
+        raise StackException, 'Unknown patch "%s"' % oldname
+    def __write_patchorder(self):
+        self.__order.set_patchorder(self.get_applied() + self.get_unapplied())
+    def set_patchorder(self, new_order):
+        self.__order.set_patchorder(new_order)
+        self.refresh()
+    def refresh(self):
+        """Re-read patch appliedness info, and write patch order to
+        disk."""
+        self.__invalidate()
+        self.__write_patchorder()
+    def __invalidate(self):
+        self.__applied = None
+        self.__unapplied = None
+    def __cached(self):
+        return (self.__applied != None)
+    def __cache(self):
+        if self.__cached():
+            return
+        patches = read_refs(self.__series.get_branch())
+        unapplied = unapplied_patches(patches)
+        for patch in unapplied:
+            del patches[patch]
+        self.__applied = sort_applied_patches(patches)
+        self.__unapplied = list(unapplied)
+        self.__unapplied.sort(self.__order.cmp)
+
+
 class Series(StgitObject):
     """Class including the operations on series
     """
@@ -372,8 +535,6 @@ class Series(StgitObject):
         self.__refs_dir = os.path.join(self.__base_dir, 'refs', 'patches',
                                        self.__name)
 
-        self.__applied_file = os.path.join(self._dir(), 'applied')
-        self.__unapplied_file = os.path.join(self._dir(), 'unapplied')
         self.__hidden_file = os.path.join(self._dir(), 'hidden')
 
         # where this series keeps its patches
@@ -382,6 +543,8 @@ class Series(StgitObject):
         # trash directory
         self.__trash_dir = os.path.join(self._dir(), 'trash')
 
+        self.__applied_cache = AppliedCache(self)
+
     def __patch_name_valid(self, name):
         """Raise an exception if the patch name is not valid.
         """
@@ -409,11 +572,7 @@ class Series(StgitObject):
     def get_current(self):
         """Return the name of the topmost patch, or None if there is
         no such patch."""
-        try:
-            applied = self.get_applied()
-        except StackException:
-            # No "applied" file: branch is not initialized.
-            return None
+        applied = self.get_applied()
         try:
             return applied[-1]
         except IndexError:
@@ -421,20 +580,10 @@ class Series(StgitObject):
             return None
 
     def get_applied(self):
-        if not os.path.isfile(self.__applied_file):
-            raise StackException, 'Branch "%s" not initialised' % self.__name
-        f = file(self.__applied_file)
-        names = [line.strip() for line in f.readlines()]
-        f.close()
-        return names
+        return self.__applied_cache.get_applied()
 
     def get_unapplied(self):
-        if not os.path.isfile(self.__unapplied_file):
-            raise StackException, 'Branch "%s" not initialised' % self.__name
-        f = file(self.__unapplied_file)
-        names = [line.strip() for line in f.readlines()]
-        f.close()
-        return names
+        return self.__applied_cache.get_unapplied()
 
     def get_hidden(self):
         if not os.path.isfile(self.__hidden_file):
@@ -446,12 +595,12 @@ class Series(StgitObject):
 
     def get_base(self):
         # Return the parent of the bottommost patch, if there is one.
-        if os.path.isfile(self.__applied_file):
-            bottommost = file(self.__applied_file).readline().strip()
-            if bottommost:
-                return self.get_patch(bottommost).get_bottom()
-        # No bottommost patch, so just return HEAD
-        return git.get_head()
+        applied = self.get_applied()
+        if applied:
+            return self.get_patch(applied[0]).get_bottom()
+        else:
+            # No bottommost patch, so just return HEAD
+            return git.get_head()
 
     def get_head(self):
         """Return the head of the branch
@@ -585,8 +734,6 @@ class Series(StgitObject):
 
         self.set_parent(parent_remote, parent_branch)
 
-        self.create_empty_field('applied')
-        self.create_empty_field('unapplied')
         os.makedirs(self.__refs_dir)
         self._set_field('orig-base', git.get_head())
 
@@ -687,10 +834,6 @@ class Series(StgitObject):
 
             # FIXME: find a way to get rid of those manual removals
             # (move functionality to StgitObject ?)
-            if os.path.exists(self.__applied_file):
-                os.remove(self.__applied_file)
-            if os.path.exists(self.__unapplied_file):
-                os.remove(self.__unapplied_file)
             if os.path.exists(self.__hidden_file):
                 os.remove(self.__hidden_file)
             if os.path.exists(self._dir()+'/orig-base'):
@@ -719,7 +862,7 @@ class Series(StgitObject):
         config.unset('branch.%s.merge' % self.__name)
         config.unset('branch.%s.stgit.parentbranch' % self.__name)
 
-    def refresh_patch(self, files = None, message = None, edit = False,
+    def refresh_patch(self, name, files = None, message = None, edit = False,
                       show_patch = False,
                       cache_update = True,
                       author_name = None, author_email = None,
@@ -728,10 +871,6 @@ class Series(StgitObject):
                       backup = False, sign_str = None, log = 'refresh'):
         """Generates a new commit for the given patch
         """
-        name = self.get_current()
-        if not name:
-            raise StackException, 'No patches applied'
-
         patch = self.get_patch(name)
 
         descr = patch.get_description()
@@ -821,9 +960,10 @@ class Series(StgitObject):
         """Creates a new patch
         """
 
+        appl, unappl = self.get_applied(), self.get_unapplied()
         if name != None:
             self.__patch_name_valid(name)
-            if self.patch_applied(name) or self.patch_unapplied(name):
+            if name in appl or name in unappl:
                 raise StackException, 'Patch "%s" already exists' % name
 
         if not message and can_edit:
@@ -860,20 +1000,29 @@ class Series(StgitObject):
 
         if unapplied:
             self.log_patch(patch, 'new')
-
-            patches = [patch.get_name()] + self.get_unapplied()
-
-            f = file(self.__unapplied_file, 'w+')
-            f.writelines([line + '\n' for line in patches])
-            f.close()
+            order = appl + [patch.get_name()] + unappl
         elif before_existing:
             self.log_patch(patch, 'new')
-
-            insert_string(self.__applied_file, patch.get_name())
+            order = [patch.get_name()] + appl + unappl
         else:
-            append_string(self.__applied_file, patch.get_name())
+            order = appl + [patch.get_name()] + unappl
             if refresh:
-                self.refresh_patch(cache_update = False, log = 'new')
+                self.refresh_patch(name, cache_update = False, log = 'new')
+        self.__applied_cache.set_patchorder(order)
+
+        return patch
+
+
+    def delete_patch_data(self, name):
+        """Deletes the stgit data for a patch."""
+        patch = Patch(name, self.__patch_dir, self.__refs_dir)
+
+        # save the commit id to a trash file
+        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
+
+        patch.delete()
+        if self.patch_hidden(name):
+            self.unhide_patch(name)
 
         return patch
 
@@ -881,9 +1030,8 @@ class Series(StgitObject):
         """Deletes a patch
         """
         self.__patch_name_valid(name)
-        patch = Patch(name, self.__patch_dir, self.__refs_dir)
 
-        if self.__patch_is_current(patch):
+        if self.get_current() == name:
             self.pop_patch(name)
         elif self.patch_applied(name):
             raise StackException, 'Cannot remove an applied patch, "%s", ' \
@@ -891,19 +1039,8 @@ class Series(StgitObject):
         elif not name in self.get_unapplied():
             raise StackException, 'Unknown patch "%s"' % name
 
-        # save the commit id to a trash file
-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
-
-        patch.delete()
-
-        unapplied = self.get_unapplied()
-        unapplied.remove(name)
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
-        if self.patch_hidden(name):
-            self.unhide_patch(name)
+        self.delete_patch_data(name)
+        self.__applied_cache.refresh()
 
     def forward_patches(self, names):
         """Try to fast-forward an array of patches.
@@ -967,19 +1104,12 @@ class Series(StgitObject):
                     break
 
             forwarded+=1
-            unapplied.remove(name)
 
         if forwarded == 0:
             return 0
 
         git.switch(top)
-
-        append_strings(self.__applied_file, names[0:forwarded])
-
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
+        self.__applied_cache.refresh()
         return forwarded
 
     def merged_patches(self, names):
@@ -1052,13 +1182,6 @@ class Series(StgitObject):
                               'Use "refresh" after fixing the conflicts or'
                               ' revert the operation with "push --undo".')
 
-        append_string(self.__applied_file, name)
-
-        unapplied.remove(name)
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
         # head == bottom case doesn't need to refresh the patch
         if empty or head != bottom:
             if not ex:
@@ -1068,15 +1191,17 @@ class Series(StgitObject):
                     log = 'push(m)'
                 else:
                     log = 'push'
-                self.refresh_patch(cache_update = False, log = log)
+                self.refresh_patch(name, cache_update = False, log = log)
             else:
                 # we store the correctly merged files only for
                 # tracking the conflict history. Note that the
                 # git.merge() operations should always leave the index
                 # in a valid state (i.e. only stage 0 files)
-                self.refresh_patch(cache_update = False, log = 'push(c)')
+                self.refresh_patch(name, cache_update = False, log = 'push(c)')
                 raise StackException, str(ex)
 
+        self.__applied_cache.refresh()
+
         return modified
 
     def undo_push(self):
@@ -1105,10 +1230,7 @@ class Series(StgitObject):
     def pop_patch(self, name, keep = False):
         """Pops the top patch from the stack
         """
-        applied = self.get_applied()
-        applied.reverse()
-        assert(name in applied)
-
+        assert(name in self.get_applied())
         patch = self.get_patch(name)
 
         if git.get_head_file() == self.get_branch():
@@ -1118,24 +1240,7 @@ class Series(StgitObject):
             git.switch(patch.get_bottom(), keep)
         else:
             git.set_branch(self.get_branch(), patch.get_bottom())
-
-        # save the new applied list
-        idx = applied.index(name) + 1
-
-        popped = applied[:idx]
-        popped.reverse()
-        unapplied = popped + self.get_unapplied()
-
-        f = file(self.__unapplied_file, 'w+')
-        f.writelines([line + '\n' for line in unapplied])
-        f.close()
-
-        del applied[:idx]
-        applied.reverse()
-
-        f = file(self.__applied_file, 'w+')
-        f.writelines([line + '\n' for line in applied])
-        f.close()
+        self.__applied_cache.refresh()
 
     def empty_patch(self, name):
         """Returns True if the patch is empty
@@ -1161,7 +1266,8 @@ class Series(StgitObject):
 
         if oldname == newname:
             raise StackException, '"To" name and "from" name are the same'
-
+        if oldname in applied or oldname in unapplied:
+            raise StackException, 'Unknown patch "%s"' % oldname
         if newname in applied or newname in unapplied:
             raise StackException, 'Patch "%s" already exists' % newname
 
@@ -1169,23 +1275,8 @@ class Series(StgitObject):
             self.unhide_patch(oldname)
             self.hide_patch(newname)
 
-        if oldname in unapplied:
-            self.get_patch(oldname).rename(newname)
-            unapplied[unapplied.index(oldname)] = newname
-
-            f = file(self.__unapplied_file, 'w+')
-            f.writelines([line + '\n' for line in unapplied])
-            f.close()
-        elif oldname in applied:
-            self.get_patch(oldname).rename(newname)
-
-            applied[applied.index(oldname)] = newname
-
-            f = file(self.__applied_file, 'w+')
-            f.writelines([line + '\n' for line in applied])
-            f.close()
-        else:
-            raise StackException, 'Unknown patch "%s"' % oldname
+        self.get_patch(oldname).rename(newname)
+        self.__applied_cache.rename(oldname, newname)
 
     def log_patch(self, patch, message):
         """Generate a log commit for a patch
diff --git a/t/t4000-upgrade.sh b/t/t4000-upgrade.sh
index 8a308fb..01be50d 100755
--- a/t/t4000-upgrade.sh
+++ b/t/t4000-upgrade.sh
@@ -34,6 +34,12 @@ for ver in 0.12 0.8; do
         ! git show-ref --verify --quiet refs/bases/master
     '
 
+    test_expect_success \
+        "v$ver: Make sure the applied and unapplied files are gone" '
+        [ ! -e .git/patches/master/applied ] &&
+        [ ! -e .git/patches/master/unapplied ]
+'
+
     cd ..
 done
 

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 4/6] Test the new DAG appliedness machinery
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
                                           ` (2 preceding siblings ...)
  2007-06-10  9:55                         ` [StGIT PATCH 3/6] Compute patch appliedness from commit DAG Karl Hasselström
@ 2007-06-10  9:55                         ` Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch Karl Hasselström
                                           ` (2 subsequent siblings)
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git



Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 t/t3000-git-interop.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 60 insertions(+), 0 deletions(-)

diff --git a/t/t3000-git-interop.sh b/t/t3000-git-interop.sh
new file mode 100755
index 0000000..44414b9
--- /dev/null
+++ b/t/t3000-git-interop.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+# Copyright (c) 2007 Karl Hasselström
+test_description='Test git/StGIT interoperability'
+. ./test-lib.sh
+
+test_expect_success \
+    'Create some git-only history' '
+    echo foo > foo.txt &&
+    git add foo.txt &&
+    git commit -a -m foo &&
+    git tag foo-tag &&
+    for i in 0 1 2 3 4; do
+        echo foo$i >> foo.txt &&
+        git commit -a -m foo$i;
+    done
+'
+
+test_expect_success \
+    'Initialize the StGIT repository' '
+    stg init
+'
+
+test_expect_success \
+    'Create five patches' '
+    for i in 0 1 2 3 4; do
+        stg new p$i -m p$i;
+    done &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 p3 p4" ] &&
+    [ "$(echo $(stg unapplied))" = "" ]
+'
+
+test_expect_success \
+    'Pop two patches with git-reset' '
+    git reset --hard HEAD~2 &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2" ] &&
+    [ "$(echo $(stg unapplied))" = "p3 p4" ]
+'
+
+test_expect_success \
+    'Create a new patch' '
+    stg new q0 -m q0 &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 q0" ] &&
+    [ "$(echo $(stg unapplied))" = "p3 p4" ]
+'
+
+test_expect_success \
+    'Go to an unapplied patch with with git-reset' '
+    git reset --hard $(stg id p3) &&
+    [ "$(echo $(stg applied))" = "p0 p1 p2 p3" ] &&
+    [ "$(echo $(stg unapplied))" = "q0 p4" ]
+'
+
+test_expect_success \
+    'Go back to below the stack base with git-reset' '
+    git reset --hard foo-tag &&
+    [ "$(echo $(stg applied))" = "" ] &&
+    [ "$(echo $(stg unapplied))" = "p0 p1 p2 q0 p3 p4" ]
+'
+
+test_done

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
                                           ` (3 preceding siblings ...)
  2007-06-10  9:55                         ` [StGIT PATCH 4/6] Test the new DAG appliedness machinery Karl Hasselström
@ 2007-06-10  9:55                         ` Karl Hasselström
  2007-06-10  9:55                         ` [StGIT PATCH 6/6] Speed up the appliedness test Karl Hasselström
  2007-06-30 19:54                         ` [StGIT PATCH 0/6] New and improved DAG appliedness series Yann Dirson
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

The bash tab completion used the "applied", "unapplied" and "current"
files to generate completions. Since these don't exist anymore, use
stg applied/unapplied/series to obtain the same info. It's a bit
slower, but not terribly much so.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 contrib/stgit-completion.bash |   15 ++++-----------
 1 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/contrib/stgit-completion.bash b/contrib/stgit-completion.bash
index d497098..2b9722a 100644
--- a/contrib/stgit-completion.bash
+++ b/contrib/stgit-completion.bash
@@ -70,32 +70,25 @@ _current_branch ()
 # List of all applied patches.
 _applied_patches ()
 {
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/applied"
+    stg applied 2> /dev/null
 }
 
 # List of all unapplied patches.
 _unapplied_patches ()
 {
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/unapplied"
+    stg unapplied 2> /dev/null
 }
 
 # List of all patches.
 _all_patches ()
 {
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied"
+    stg series --noprefix 2> /dev/null
 }
 
 # List of all patches except the current patch.
 _all_other_patches ()
 {
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied" \
-        | grep -v "^$(cat $g/patches/$b/current 2> /dev/null)$"
+    stg series 2> /dev/null | grep -v '^>' | cut -f 2 -d ' '
 }
 
 _all_branches ()

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [StGIT PATCH 6/6] Speed up the appliedness test
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
                                           ` (4 preceding siblings ...)
  2007-06-10  9:55                         ` [StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch Karl Hasselström
@ 2007-06-10  9:55                         ` Karl Hasselström
  2007-06-30 19:54                         ` [StGIT PATCH 0/6] New and improved DAG appliedness series Yann Dirson
  6 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-06-10  9:55 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

The appliedness test was too slow if at least one patch, applied or
unapplied, was too far away from HEAD, since we had to visit the whole
intervening part of the commit DAG.

This patch fixes that problem by maintaining a cache of uninteresting
commits that are known to not reach any patches in the commit DAG.
(Specifically, this is at all times the set of commits that are
parents to patch commits and do not have a patch commit as their
ancestor.) By exlcuding these commits when walking the graph, we only
have to visit the interesting places.

As a nice side effect, the cache of uninteresting commits makes it
possible to use just one git-rev-list call instead of two, since we
can list the applied patches without first computing the unapplied
patches; the unapplied patches are then simply all patches except
those that are applied.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---

 stgit/stack.py |  278 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 223 insertions(+), 55 deletions(-)

diff --git a/stgit/stack.py b/stgit/stack.py
index e622f68..6fd1a2a 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -156,7 +156,7 @@ class Patch(StgitObject):
         os.mkdir(self._dir())
         self.create_empty_field('bottom')
         self.create_empty_field('top')
-
+ 
     def delete(self):
         for f in os.listdir(self._dir()):
             os.remove(os.path.join(self._dir(), f))
@@ -369,7 +369,11 @@ class PatchorderCache:
     saved in its patchorder file."""
     def __init__(self, series):
         self.__series = series
+        self.__file = os.path.join(self.__series._dir(), 'patchorder')
         self.__invalidate()
+    def delete_file(self):
+        if os.path.isfile(self.__file):
+            os.remove(self.__file)
     def __invalidate(self):
         self.__patchnames = None
         self.__position = None
@@ -379,9 +383,8 @@ class PatchorderCache:
 
         self.__patchnames = []
         self.__position = {}
-        pof = os.path.join(self.__series._dir(), 'patchorder')
-        if os.path.isfile(pof):
-            for line in file(pof):
+        if os.path.isfile(self.__file):
+            for line in file(self.__file):
                 name = line.strip()
                 assert not name in self.__position
                 self.__position[name] = len(self.__patchnames)
@@ -404,60 +407,200 @@ class PatchorderCache:
         pos2 = self.__position.get(name2, largepos)
         return cmp((pos1, name1), (pos2, name2))
 
+class UninterestingCache:
+    """Keeps track of a set of commits that do not reach any patches.
+    These are used to speed up the detection of unapplied patches.
+
+    Specifically, this is at all times the set of commits c that
+    fulfill the following two criteria:
+
+      * c does not reach any patch
+
+      * c is the parent of a patch
+
+    """
+    def __init__(self, series):
+        self.__series = series
+        self.__uninteresting = None
+        self.__filename = os.path.join(self.__series._dir(), 'uninteresting')
+    def __invalidate(self):
+        self.__uninteresting = None
+        self.delete_file()
+    def delete_file(self):
+        if os.path.isfile(self.__filename):
+            os.remove(self.__filename)
+    def __other_patches(self, patchname):
+        """All patches except the named one."""
+        ref2hash = read_refs(self.__series.get_branch())
+        return [self.__series.get_patch(ref)
+                for ref in ref2hash.iterkeys()
+                if ref and ref != patchname]
+    def __write_file(self):
+        """Write the uninteresting commits to file."""
+        try:
+            f = file(self.__filename, 'w')
+            for u in self.__uninteresting:
+                f.write('%s\n' % u)
+            f.close()
+        except IOError:
+            pass # this isn't fatal -- the directory is probably missing
+    def __read_file(self):
+        """Read the uninteresting commits from file. Return true on
+        success, false on failure."""
+        if not os.path.isfile(self.__filename):
+            return False
+        self.__uninteresting = Set()
+        for line in file(self.__filename):
+            self.__uninteresting.add(line.strip())
+        return True
+    def __cache_file(self):
+        """Try to cache the uninteresting commits using only the cache
+        file. Return true on success, false on failure."""
+        if self.__uninteresting != None:
+            return True # already cached
+        return self.__read_file()
+    def __cache(self):
+        """Cache the uninteresting commits, recomputing them if
+        necessary."""
+        if self.__cache_file():
+            return
+        self.__compute_uninteresting()
+        self.__write_file()
+    def __compute_uninteresting(self):
+        """Compute a reasonable set of uninteresting commits from
+        scratch. This is expensive."""
+        out.start('Finding uninteresting commits')
+        ref2hash = read_refs(self.__series.get_branch())
+        patches = Set([sha1 for ref, sha1 in ref2hash.iteritems() if ref])
+        interesting, uninteresting = Set(), Set()
+
+        # Iterate over all commits. We are guaranteed to see each
+        # commit before any of its children.
+        for line in git._output_lines(
+            'git-rev-list --topo-order --reverse --parents --all'):
+            commits = line.split()
+            commit, parents = commits[0], Set(commits[1:])
+
+            # Patches are interesting.
+            if commit in patches:
+                interesting.add(commit)
+
+                # The parents of a patch are uninteresting unless they
+                # are interesting.
+                for p in parents:
+                    if not p in interesting:
+                        uninteresting.add(p)
+                continue
+
+            # Commits with interesting parents are interesting.
+            if interesting.intersection(parents):
+                interesting.add(commit)
+        self.__uninteresting = uninteresting
+        out.done()
+    def create_patch(self, name, top, bottom):
+        """The given patch has been created. Update the uninterested
+        state to maintain the invariant."""
+        if not self.__cache_file():
+            return # not cached
+
+        # New patch inserted just below an existing bottommost patch:
+        # need to move the uninteresting commit down one step.
+        if top in self.__uninteresting:
+            self.__uninteresting.remove(top)
+            self.__uninteresting.add(bottom)
+            self.__write_file()
+            return
+
+        # New patch inserted next to an existing non-bottommost patch:
+        # don't need to do anything.
+        existing_patches = self.__other_patches(name)
+        tops = Set([p.get_top() for p in existing_patches])
+        bottoms = Set([p.get_bottom() for p in existing_patches])
+        if bottom in bottoms or bottom in tops or top in bottoms:
+            return
+
+        # The new patch is not adjacent to an existing patch. We'd
+        # need to first get rid of any uninteresting commit that
+        # reaches this patch, and then mark the patch's bottom
+        # uninteresting if it doesn't reach any other patch. This is a
+        # lot of work, so we chicken out and blow the whole cache
+        # instead.
+        self.__invalidate()
+    def delete_patch(self, name, top, bottom):
+        """The given patch has been deleted. Update the uninterested
+        state to maintain the invariant."""
+        if not self.__cache_file():
+            return # not cached
+
+        # If this patch reaches another patch, there's nothing to do.
+        if not bottom in self.__uninteresting:
+            return
+
+        # If another patch has the same bottom, it's still
+        # uninteresting and there's nothing more to do.
+        other_patches = self.__other_patches(name)
+        for p in other_patches:
+            if p.get_bottom() == bottom:
+                return
+
+        # If there are other patches on top of this one, their bottoms
+        # (this patch's top) become uninteresting in place of this
+        # patch's bottom.
+        for p in other_patches:
+            if p.get_bottom() == top:
+                self.__uninteresting.remove(bottom)
+                self.__uninteresting.add(top)
+                self.__write_file()
+                return
+
+        # The bottom of this patch is no longer uninteresting. But
+        # there might be other patches that reach it, whose bottoms
+        # would need to be marked uninteresting. That would require an
+        # expensive reachability analysis.
+        self.__invalidate()
+    def get(self):
+        self.__cache()
+        return self.__uninteresting
+
 def read_refs(branch):
     """Return a mapping from patches and branch head to hashes for a
     given branch. The patches are listed by name; the branch head is
     None."""
     refs = {}
     patchpat = re.compile(r'^refs/patches/%s/([^\.]+)$' % branch)
+    head = 'refs/heads/%s' % branch
     for line in git._output_lines('git-show-ref'):
         sha1, ref = line.split()
         m = re.match(patchpat, ref)
         if m:
             refs[m.group(1)] = sha1
-        elif ref == 'refs/heads/%s' % branch:
+        elif ref == head:
             refs[None] = sha1
+    if not None in refs:
+        raise StackException, 'Could not find %s' % head
     return refs
 
-def unapplied_patches(ref2hash):
+def get_patches(ref2hash, uninteresting):
     """Given a map of patch names (and the branch head, keyed by None)
-    to hashes, return the set of unapplied patches."""
-    hash2refs = {}
-    for r, h in ref2hash.iteritems():
-        hash2refs.setdefault(h, Set()).add(r)
-
+    to hashes, return the list of applied patches and the set of
+    unapplied patches. The second parameter is a set of commit objects
+    that do not reach any patch."""
+    applied = []
     unapplied = Set()
-    for line in git._output_lines(
-        'git-rev-list --stdin',
-        ('%s%s\n' % (['', '^'][ref == None], sha1)
-         for ref, sha1 in ref2hash.iteritems())):
-        for ref in hash2refs.get(line.strip(), []):
-            unapplied.add(ref)
-    return unapplied
-
-def sort_applied_patches(ref2hash):
-    """Given a map of patch names (and the branch head, keyed by None)
-    to hashes, return a list with the applied patches in stack order.
-    All patches in the map must be applied."""
-    hash2refs = {}
+    hash2patches = {}
     for r, h in ref2hash.iteritems():
-        if r != None:
-            hash2refs.setdefault(h, Set()).add(r)
+        if r:
+            hash2patches.setdefault(h, Set()).add(r)
+            unapplied.add(r)
 
-    missing = Set(ref for ref in ref2hash.iterkeys() if ref != None)
-    if not missing:
-        return []
-    applied = []
-    grl = popen2.Popen3('git-rev-list %s' % ref2hash[None], True)
-    for line in grl.fromchild:
-        for ref in hash2refs.get(line.strip(), []):
+    for line in git._output_lines(
+        'git-rev-list --topo-order --stdin', ['%s\n' % ref2hash[None]]
+        + ['^%s\n' % u for u in uninteresting]):
+        for ref in hash2patches.get(line.strip(), []):
             applied.append(ref)
-            missing.remove(ref)
-        if not missing:
-            applied.reverse()
-            return applied
-
-    raise StackException, 'Could not find patches: %s' % ', '.join(missing)
+            unapplied.remove(ref)
+    applied.reverse()
+    return applied, unapplied
 
 class AppliedCache:
     """An object that keeps track of the appliedness and order of the
@@ -465,7 +608,11 @@ class AppliedCache:
     def __init__(self, series):
         self.__series = series
         self.__order = PatchorderCache(series)
+        self.__uninteresting = UninterestingCache(series)
         self.__invalidate()
+    def delete_files(self):
+        for sub in [self.__uninteresting, self.__order]:
+            sub.delete_file()
     def get_applied(self):
         self.__cache()
         return self.__applied
@@ -484,6 +631,17 @@ class AppliedCache:
                 self.__write_patchorder()
                 return
         raise StackException, 'Unknown patch "%s"' % oldname
+    def new(self, name, top, bottom):
+        """Create new patch."""
+        self.__uninteresting.create_patch(name, top, bottom)
+    def delete(self, name, top, bottom):
+        """Delete a patch."""
+        self.__uninteresting.delete_patch(name, top, bottom)
+    def change(self, name, old_top, old_bottom, new_top, new_bottom):
+        """Change a patch."""
+        if (new_top, new_bottom) != (old_top, old_bottom):
+            self.new(name, new_top, new_bottom)
+            self.delete(name, old_top, old_bottom)
     def __write_patchorder(self):
         self.__order.set_patchorder(self.get_applied() + self.get_unapplied())
     def set_patchorder(self, new_order):
@@ -502,11 +660,8 @@ class AppliedCache:
     def __cache(self):
         if self.__cached():
             return
-        patches = read_refs(self.__series.get_branch())
-        unapplied = unapplied_patches(patches)
-        for patch in unapplied:
-            del patches[patch]
-        self.__applied = sort_applied_patches(patches)
+        self.__applied, unapplied = get_patches(
+            read_refs(self.__series.get_branch()), self.__uninteresting.get())
         self.__unapplied = list(unapplied)
         self.__unapplied.sort(self.__order.cmp)
 
@@ -838,6 +993,7 @@ class Series(StgitObject):
                 os.remove(self.__hidden_file)
             if os.path.exists(self._dir()+'/orig-base'):
                 os.remove(self._dir()+'/orig-base')
+            self.__applied_cache.delete_files()
 
             if not os.listdir(self.__patch_dir):
                 os.rmdir(self.__patch_dir)
@@ -940,16 +1096,20 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
+        curr_bottom = patch.get_bottom()
+        curr_top = patch.get_top()
 
         # the bottom of the patch is not changed by refresh. If the
         # old_bottom is different, there wasn't any previous 'refresh'
         # command (probably only a 'push')
-        if old_bottom != patch.get_bottom() or old_top == patch.get_top():
+        if old_bottom != curr_bottom or old_top == curr_top:
             raise StackException, 'No undo information available'
 
         git.reset(tree_id = old_top, check_out = False)
         if patch.restore_old_boundaries():
             self.log_patch(patch, 'undo')
+        self.__applied_cache.change(name, curr_top, curr_bottom,
+                                    old_top, old_bottom)
 
     def new_patch(self, name, message = None, can_edit = True,
                   unapplied = False, show_patch = False,
@@ -982,14 +1142,11 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         patch.create()
 
-        if bottom:
-            patch.set_bottom(bottom)
-        else:
-            patch.set_bottom(head)
-        if top:
-            patch.set_top(top)
-        else:
-            patch.set_top(head)
+        bottom = bottom or head
+        top = top or head
+        patch.set_bottom(bottom)
+        patch.set_top(top)
+        self.__applied_cache.new(name, top, bottom)
 
         patch.set_description(descr)
         patch.set_authname(author_name)
@@ -1016,15 +1173,16 @@ class Series(StgitObject):
     def delete_patch_data(self, name):
         """Deletes the stgit data for a patch."""
         patch = Patch(name, self.__patch_dir, self.__refs_dir)
+        top, bottom = patch.get_top(), patch.get_bottom()
 
         # save the commit id to a trash file
-        write_string(os.path.join(self.__trash_dir, name), patch.get_top())
+        write_string(os.path.join(self.__trash_dir, name), top)
 
         patch.delete()
         if self.patch_hidden(name):
             self.unhide_patch(name)
 
-        return patch
+        self.__applied_cache.delete(name, top, bottom)
 
     def delete_patch(self, name):
         """Deletes a patch
@@ -1084,6 +1242,7 @@ class Series(StgitObject):
 
                     top_tree = git.get_commit(top).get_tree()
 
+                    old_top = top
                     top = git.commit(message = descr, parents = [head],
                                      cache_update = False,
                                      tree_id = top_tree,
@@ -1097,6 +1256,9 @@ class Series(StgitObject):
                     patch.set_bottom(head, backup = True)
                     patch.set_top(top, backup = True)
 
+                    self.__applied_cache.change(
+                        name, old_top = old_top, old_bottom = bottom,
+                        new_top = top, new_bottom = head)
                     self.log_patch(patch, 'push(f)')
                 else:
                     top = head
@@ -1154,6 +1316,7 @@ class Series(StgitObject):
             # need an empty commit
             patch.set_bottom(head, backup = True)
             patch.set_top(head, backup = True)
+            self.__applied_cache.change(name, top, bottom, head, head)
             modified = True
         elif head == bottom:
             # reset the backup information. No need for logging
@@ -1166,6 +1329,7 @@ class Series(StgitObject):
             # The current patch is empty after merge.
             patch.set_bottom(head, backup = True)
             patch.set_top(head, backup = True)
+            self.__applied_cache.change(name, top, bottom, head, head)
 
             # Try the fast applying first. If this fails, fall back to the
             # three-way merge
@@ -1211,6 +1375,8 @@ class Series(StgitObject):
         patch = self.get_patch(name)
         old_bottom = patch.get_old_bottom()
         old_top = patch.get_old_top()
+        curr_bottom = patch.get_bottom()
+        curr_top = patch.get_top()
 
         # the top of the patch is changed by a push operation only
         # together with the bottom (otherwise the top was probably
@@ -1222,6 +1388,8 @@ class Series(StgitObject):
         git.reset()
         self.pop_patch(name)
         ret = patch.restore_old_boundaries()
+        self.__applied_cache.change(name, curr_top, curr_bottom,
+                                    old_top, old_bottom)
         if ret:
             self.log_patch(patch, 'undo')
 

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH 0/6] New and improved DAG appliedness series
  2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
                                           ` (5 preceding siblings ...)
  2007-06-10  9:55                         ` [StGIT PATCH 6/6] Speed up the appliedness test Karl Hasselström
@ 2007-06-30 19:54                         ` Yann Dirson
  2007-07-01 14:35                           ` Karl Hasselström
  6 siblings, 1 reply; 51+ messages in thread
From: Yann Dirson @ 2007-06-30 19:54 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Catalin Marinas, git

Hi Karl,

On Sun, Jun 10, 2007 at 02:54:47AM -0700, Karl Hasselström wrote:
> This took a while, but here it is. (Actually, I finished this about a
> week ago, but had no Internet connection so I couldn't send it.)

Is this the latest version of the DAG patches, or is there maybe a
public repo where you push your work ?

It happens that my refactorings touches virtually everything, so there
will be conflicts, and the best thing to do is probably that I rebase
my work on yours.

Best regards,
-- 
Yann

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [StGIT PATCH 0/6] New and improved DAG appliedness series
  2007-06-30 19:54                         ` [StGIT PATCH 0/6] New and improved DAG appliedness series Yann Dirson
@ 2007-07-01 14:35                           ` Karl Hasselström
  0 siblings, 0 replies; 51+ messages in thread
From: Karl Hasselström @ 2007-07-01 14:35 UTC (permalink / raw)
  To: Yann Dirson; +Cc: Catalin Marinas, git

On 2007-06-30 21:54:51 +0200, Yann Dirson wrote:

> On Sun, Jun 10, 2007 at 02:54:47AM -0700, Karl Hasselström wrote:
>
> > This took a while, but here it is. (Actually, I finished this
> > about a week ago, but had no Internet connection so I couldn't
> > send it.)
>
> Is this the latest version of the DAG patches, or is there maybe a
> public repo where you push your work ?

This is the latest version, and no, I don't yet have a public repo
that I push this stuff to.

I've been travelling a lot the last few weeks, and I'm not quite done
yet, so I haven't had time to even follow the mailing list properly,
but after that I plan to start a pu-ish (rebasing) integration branch
for the patches that I, you, and others post to the list.

> It happens that my refactorings touches virtually everything,

I noticed. :-)

> so there will be conflicts,

:-)

> and the best thing to do is probably that I rebase my work on yours.

Thanks for the vote of confidence. Please go ahead; as I said, you
already have the latest version of my series, and it may be a while
yet before I have much time to burn on StGIT.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply	[flat|nested] 51+ messages in thread

end of thread, other threads:[~2007-07-01 14:36 UTC | newest]

Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-05-06 15:13 [StGIT PATCH] Don't use patches/<branch>/current Karl Hasselström
2007-05-15 15:56 ` Catalin Marinas
2007-05-15 16:21   ` Peter Oberndorfer
2007-05-15 16:50     ` Catalin Marinas
2007-05-15 18:25   ` Karl Hasselström
2007-05-15 19:38     ` [StGIT PATCH] Remove obsolete files when deleting a branch Karl Hasselström
2007-05-15 20:01     ` [StGIT PATCH] Don't use patches/<branch>/current Catalin Marinas
2007-05-16  7:11       ` Karl Hasselström
2007-05-16 12:07         ` Catalin Marinas
2007-05-16 19:40           ` Karl Hasselström
2007-05-16 20:40             ` Karl Hasselström
2007-05-17 12:43               ` Catalin Marinas
2007-05-17 14:57                 ` Karl Hasselström
2007-05-17 20:51                   ` Catalin Marinas
2007-05-18  6:30                     ` Karl Hasselström
2007-06-10  9:54                       ` [StGIT PATCH 0/6] New and improved DAG appliedness series Karl Hasselström
2007-06-10  9:54                         ` [StGIT PATCH 1/6] Verify patch status during the test Karl Hasselström
2007-06-10  9:55                         ` [StGIT PATCH 2/6] Make use of the get_patch() utility function Karl Hasselström
2007-06-10  9:55                         ` [StGIT PATCH 3/6] Compute patch appliedness from commit DAG Karl Hasselström
2007-06-10  9:55                         ` [StGIT PATCH 4/6] Test the new DAG appliedness machinery Karl Hasselström
2007-06-10  9:55                         ` [StGIT PATCH 5/6] Fix bash completion after the DAG appliedness patch Karl Hasselström
2007-06-10  9:55                         ` [StGIT PATCH 6/6] Speed up the appliedness test Karl Hasselström
2007-06-30 19:54                         ` [StGIT PATCH 0/6] New and improved DAG appliedness series Yann Dirson
2007-07-01 14:35                           ` Karl Hasselström
2007-05-15 21:08   ` [StGIT PATCH] Don't use patches/<branch>/current Yann Dirson
2007-05-15 21:36     ` Catalin Marinas
2007-05-15 21:49       ` Yann Dirson
2007-05-16  6:27         ` Karl Hasselström
2007-05-19  0:09           ` [StGIT PATCH 0/5] Metadata format versioning Karl Hasselström
2007-05-19  0:09             ` [StGIT PATCH 1/5] Fix config caching so that get, set, get works Karl Hasselström
2007-05-19  0:09             ` [StGIT PATCH 2/5] Have only a single command in each test_expect_failure Karl Hasselström
2007-05-19  0:10             ` [StGIT PATCH 3/5] Upgrade old StGIT branches to new-format metadata Karl Hasselström
2007-05-19  0:10             ` [StGIT PATCH 4/5] Test the format version upgrade code Karl Hasselström
2007-05-19  0:10             ` [StGIT PATCH 5/5] Add --binary flag to commands that generate diffs Karl Hasselström
2007-05-22 12:15               ` Catalin Marinas
2007-05-22 13:31                 ` Karl Hasselström
2007-05-20 20:03 ` [StGit PATCH 0/2] Bash prompt updates Robin Rosenberg
2007-05-20 20:04   ` [StGit PATCH 1/2] Update the bash prompt from 'applied' instead of the obsolete 'current' Robin Rosenberg
2007-05-20 20:46     ` Yann Dirson
2007-05-20 21:22       ` [PATCH " Robin Rosenberg
2007-05-21  7:48         ` Karl Hasselström
2007-05-21  9:31           ` Catalin Marinas
2007-05-21 10:15             ` Karl Hasselström
2007-05-21 11:39               ` Karl Hasselström
2007-05-21 15:17                 ` Catalin Marinas
2007-05-21 15:39                   ` Karl Hasselström
2007-05-22 12:11                     ` Catalin Marinas
2007-05-22 13:29                       ` Karl Hasselström
2007-05-21 18:57             ` Yann Dirson
2007-05-20 21:24       ` [PATCH 2/2] Don't use / as separatar since it is common i branch names Robin Rosenberg
2007-05-20 20:04   ` [StGit PATCH " Robin Rosenberg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).