* [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 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/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
* 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
* [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 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 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
* 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: [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/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
* 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
* 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 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: [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: [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
* [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
* [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
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).