Git development
 help / color / mirror / Atom feed
* bug: stgit doesn't handle branch names with / in them
@ 2006-02-14 17:35 Karl Hasselström
  2006-02-17  1:41 ` [PATCH] Handle branch names with slashes Karl  Hasselström
  0 siblings, 1 reply; 14+ messages in thread
From: Karl Hasselström @ 2006-02-14 17:35 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

"stg branch -l" only shows branches directly under refs/heads, and
completely ignores branches in subdirectories. But it does print the
name of the subdirectories . . .

"stg branch" prints only the part of the branch name following the
final slash.

    $ git-branch
      kha/abc123
      kha/dab
    * kha/patches
      kha/powerup
      kha/dare
      kha/dare-build
      kha/boo123
      kha/lisp106
      local/test0
      local/test3
      local/test4
      SVN-import
      test5

    $ stg branch -l
    Available branches:
            SVN-import  |
            kha         |
            local       |
            test5       |

    $ stg branch
    patches

No patch (yet), sorry.

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

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

* [PATCH] Handle branch names with slashes
  2006-02-14 17:35 bug: stgit doesn't handle branch names with / in them Karl Hasselström
@ 2006-02-17  1:41 ` Karl  Hasselström
  2006-02-17  3:01   ` Sam Vilain
  2006-02-17  9:47   ` Catalin Marinas
  0 siblings, 2 replies; 14+ messages in thread
From: Karl  Hasselström @ 2006-02-17  1:41 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

Let StGIT grok branch names with slashes in them. It used to fall flat
on its face when confronted with them.

I think I've covered all, or at least most cases, but there are
probably some bugs left if you look hard enough.

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

---

 stgit/commands/branch.py |    6 +++-
 stgit/git.py             |   12 +++++--
 stgit/stack.py           |   42 ++++++++++---------------
 stgit/utils.py           |   77 ++++++++++++++++++++++++++++++++++++++++------
 4 files changed, 97 insertions(+), 40 deletions(-)

diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index ef44349..d3e8a3c 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -173,7 +173,11 @@ def func(parser, options, args):
         if len(args) != 0:
             parser.error('incorrect number of arguments')
 
-        branches = os.listdir(os.path.join(git.get_base_dir(), 'refs', 'heads'))
+        branches = []
+        basepath = os.path.join(git.get_base_dir(), 'refs', 'heads')
+        for path, dirs, files in os.walk(basepath):
+            branches += [remove_leading_dir(basepath, os.path.join(path, f))
+                         for f in files]
         branches.sort()
         max_len = max([len(i) for i in branches])
 
diff --git a/stgit/git.py b/stgit/git.py
index 582e803..724b6fd 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -232,7 +232,8 @@ def get_head():
 def get_head_file():
     """Returns the name of the file pointed to by the HEAD link
     """
-    return os.path.basename(_output_one_line('git-symbolic-ref HEAD'))
+    return remove_leading_dir(os.path.join('refs', 'heads'),
+                              _output_one_line('git-symbolic-ref HEAD'))
 
 def set_head_file(ref):
     """Resets HEAD to point to a new ref
@@ -325,7 +326,9 @@ def delete_branch(name):
     branch_head = os.path.join('refs', 'heads', name)
     if not branch_exists(branch_head):
         raise GitException, 'Branch "%s" does not exist' % name
-    os.remove(os.path.join(get_base_dir(), branch_head))
+    base = get_base_dir()
+    rm_file_and_dirs(os.path.join(base, branch_head),
+                     os.path.join(base, 'refs', 'heads'))
 
 def rename_branch(from_name, to_name):
     """Rename a git branch
@@ -339,8 +342,9 @@ def rename_branch(from_name, to_name):
 
     if get_head_file() == from_name:
         set_head_file(to_head)
-    os.rename(os.path.join(get_base_dir(), from_head), \
-              os.path.join(get_base_dir(), to_head))
+    base = os.path.join(get_base_dir())
+    rename_dirs(os.path.join(base, from_head), os.path.join(base, to_head),
+                os.path.join(base, 'refs', 'heads'))
 
 def add(names):
     """Add the files or recursively add the directory contents
diff --git a/stgit/stack.py b/stgit/stack.py
index 556c40e..68a2936 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -406,7 +406,7 @@ class Series:
         """
         if len(self.get_applied()) == 0:
             head = git.get_head()
-            write_string(self.__base_file, head)
+            write_string(self.__base_file, head, mkdir = True)
 
     def __end_stack_check(self):
         """Remove .git/refs/heads/base if the stack is empty.
@@ -499,9 +499,11 @@ class Series:
         git.rename_branch(self.__name, to_name)
 
         if os.path.isdir(self.__series_dir):
-            os.rename(self.__series_dir, to_stack.__series_dir)
+            rename_dirs(self.__series_dir, to_stack.__series_dir,
+                        os.path.join(self.__base_dir, 'patches'))
         if os.path.exists(self.__base_file):
-            os.rename(self.__base_file, to_stack.__base_file)
+            rename_dirs(self.__base_file, to_stack.__base_file,
+                        os.path.join(self.__base_dir, 'refs', 'bases'))
 
         self.__init__(to_name)
 
@@ -543,29 +545,19 @@ class Series:
             for p in patches:
                 Patch(p, self.__patch_dir, self.__refs_dir).delete()
 
-            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.__current_file):
-                os.remove(self.__current_file)
-            if os.path.exists(self.__descr_file):
-                os.remove(self.__descr_file)
-            if not os.listdir(self.__patch_dir):
-                os.rmdir(self.__patch_dir)
-            else:
-                print 'Patch directory %s is not empty.' % self.__name
-            if not os.listdir(self.__series_dir):
-                os.rmdir(self.__series_dir)
-            else:
-                print 'Series directory %s is not empty.' % self.__name
-            if not os.listdir(self.__refs_dir):
-                os.rmdir(self.__refs_dir)
-            else:
-                print 'Refs directory %s is not empty.' % self.__refs_dir
+            for f in [self.__applied_file, self.__unapplied_file,
+                      self.__current_file, self.__descr_file]:
+                rm_if_exists(f)
+
+            for (d, n) in [(self.__patch_dir, 'Patch'),
+                           (self.__series_dir, 'Series'),
+                           (self.__refs_dir, 'Refs')]:
+                if os.path.isdir(d) and os.listdir(d):
+                    print '%s directory %s is not empty.' % (n, self.__name)
+                rmdir_while_empty(d, self.__base_dir)
 
-        if os.path.exists(self.__base_file):
-            os.remove(self.__base_file)
+        rm_if_exists(self.__base_file)
+        rmdir_while_empty(os.path.dirname(self.__base_file), self.__base_dir)
 
     def refresh_patch(self, files = None, message = None, edit = False,
                       show_patch = False,
diff --git a/stgit/utils.py b/stgit/utils.py
index 5749b3b..33c62be 100644
--- a/stgit/utils.py
+++ b/stgit/utils.py
@@ -18,6 +18,18 @@ along with this program; if not, write t
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
+import os.path
+
+def mkdir_file(filename, mode, mkdir):
+    """Opens filename with the given mode, creating the directory it's
+    in if it doesn't already exist and mkdir is true
+    """
+    if mkdir:
+        d = os.path.dirname(filename)
+        if not os.path.isdir(d):
+            os.makedirs(d)
+    return file(filename, mode)
+
 def read_string(filename, multiline = False):
     """Reads the first line from a file
     """
@@ -29,42 +41,87 @@ def read_string(filename, multiline = Fa
     f.close()
     return result
 
-def write_string(filename, line, multiline = False):
+def write_string(filename, line, multiline = False, mkdir = False):
     """Writes 'line' to file and truncates it
     """
-    f = file(filename, 'w+')
+    f = mkdir_file(filename, 'w+', mkdir)
     if multiline:
         f.write(line)
     else:
         print >> f, line
     f.close()
 
-def append_strings(filename, lines):
+def append_strings(filename, lines, mkdir = False):
     """Appends 'lines' sequence to file
     """
-    f = file(filename, 'a+')
+    f = mkdir_file(filename, 'a+', mkdir)
     for line in lines:
         print >> f, line
     f.close()
 
-def append_string(filename, line):
+def append_string(filename, line, mkdir = False):
     """Appends 'line' to file
     """
-    f = file(filename, 'a+')
+    f = mkdir_file(filename, 'a+', mkdir)
     print >> f, line
     f.close()
 
-def insert_string(filename, line):
+def insert_string(filename, line, mkdir = False):
     """Inserts 'line' at the beginning of the file
     """
-    f = file(filename, 'r+')
+    f = mkdir_file(filename, 'r+', mkdir)
     lines = f.readlines()
     f.seek(0); f.truncate()
     print >> f, line
     f.writelines(lines)
     f.close()
 
-def create_empty_file(name):
+def create_empty_file(name, mkdir = False):
     """Creates an empty file
     """
-    file(name, 'w+').close()
+    mkdir_file(name, 'w+', mkdir).close()
+
+def remove_leading_dir(leading, path):
+    """Remove leading directories from a pathname
+    """
+    if not path.startswith(leading):
+        raise Exception('"%s" does not begin with "%s"' % (path, leading))
+    path = path[len(leading):]
+    if len(path) > 0 and path[0] in [os.path.sep, os.path.altsep]:
+        path = path[1:]
+    return path
+
+def rmdir_while_empty(path, stop):
+    """Delete dirs until we reach a directory that isn't empty, or
+    until we reach the path stop
+    """
+    while path.startswith(stop) and len(path) > len(stop):
+        parent = os.path.dirname(path)
+        try:
+            os.rmdir(path)
+        except OSError:
+            return # directory not empty
+        path = parent
+
+def rm_file_and_dirs(path, stop):
+    """Delete the file, and keep deleting dirs until we reach a
+    directory that isn't empty, or until we reach the path stop
+    """
+    os.remove(path)
+    rmdir_while_empty(os.path.dirname(path), stop)
+
+def rm_if_exists(f):
+    """Delete file if it exists
+    """
+    if os.path.exists(f):
+        os.remove(f)
+
+def rename_dirs(from_path, to_path, stop):
+    """Rename file or directory, creating new directories at to_path
+    as necessary, and removing leftover empty directories at from_path
+    until we reach stop
+    """
+    if not os.path.isdir(os.path.dirname(to_path)):
+        os.makedirs(os.path.dirname(to_path))
+    os.rename(from_path, to_path)
+    rmdir_while_empty(os.path.dirname(from_path), stop)

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  1:41 ` [PATCH] Handle branch names with slashes Karl  Hasselström
@ 2006-02-17  3:01   ` Sam Vilain
  2006-02-17  4:21     ` Karl Hasselström
  2006-02-17  9:47   ` Catalin Marinas
  1 sibling, 1 reply; 14+ messages in thread
From: Sam Vilain @ 2006-02-17  3:01 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Catalin Marinas, git

Karl Hasselström wrote:
> Let StGIT grok branch names with slashes in them. It used to fall flat
> on its face when confronted with them.
> 
> I think I've covered all, or at least most cases, but there are
> probably some bugs left if you look hard enough.

Does `stgit -r patchname/bottom` still work?

Sam.

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  3:01   ` Sam Vilain
@ 2006-02-17  4:21     ` Karl Hasselström
  2006-02-27 12:11       ` Karl Hasselström
  0 siblings, 1 reply; 14+ messages in thread
From: Karl Hasselström @ 2006-02-17  4:21 UTC (permalink / raw)
  To: Sam Vilain; +Cc: Catalin Marinas, git

On 2006-02-17 16:01:10 +1300, Sam Vilain wrote:

> Karl Hasselström wrote:
>
> > Let StGIT grok branch names with slashes in them. It used to fall
> > flat on its face when confronted with them.
> >
> > I think I've covered all, or at least most cases, but there are
> > probably some bugs left if you look hard enough.
>
> Does `stgit -r patchname/bottom` still work?

Yes (if you mean 'stg diff -r ... ' :-). It's just branches that can
have slashes in their names, not patches.

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

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  1:41 ` [PATCH] Handle branch names with slashes Karl  Hasselström
  2006-02-17  3:01   ` Sam Vilain
@ 2006-02-17  9:47   ` Catalin Marinas
  2006-02-17  9:53     ` Karl Hasselström
  2006-04-12 19:16     ` Yann Dirson
  1 sibling, 2 replies; 14+ messages in thread
From: Catalin Marinas @ 2006-02-17  9:47 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git

Karl Hasselström <kha@treskal.com> wrote:
> Let StGIT grok branch names with slashes in them. It used to fall flat
> on its face when confronted with them.

Thanks for the patches you sent. I'll have a look at them tomorrow.

As a side note, for future patches, could you please use my
catalin.marinas@gmail.com address instead of the company one? I
maintain StGIT outside the working hours and it's much easier to grab
them from my personal address.

Thanks,

-- 
Catalin

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  9:47   ` Catalin Marinas
@ 2006-02-17  9:53     ` Karl Hasselström
  2006-04-12 19:16     ` Yann Dirson
  1 sibling, 0 replies; 14+ messages in thread
From: Karl Hasselström @ 2006-02-17  9:53 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: git

On 2006-02-17 09:47:21 +0000, Catalin Marinas wrote:

> Karl Hasselström <kha@treskal.com> wrote:
>
> > Let StGIT grok branch names with slashes in them. It used to fall
> > flat on its face when confronted with them.
>
> Thanks for the patches you sent. I'll have a look at them tomorrow.
>
> As a side note, for future patches, could you please use my
> catalin.marinas@gmail.com address instead of the company one? I
> maintain StGIT outside the working hours and it's much easier to
> grab them from my personal address.

Ah, I hadn't realized you were using more than one address. Will do.

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

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  4:21     ` Karl Hasselström
@ 2006-02-27 12:11       ` Karl Hasselström
  2006-02-27 12:29         ` Catalin Marinas
  0 siblings, 1 reply; 14+ messages in thread
From: Karl Hasselström @ 2006-02-27 12:11 UTC (permalink / raw)
  To: Sam Vilain; +Cc: Catalin Marinas, git

On 2006-02-17 05:21:08 +0100, Karl Hasselström wrote:

> On 2006-02-17 16:01:10 +1300, Sam Vilain wrote:
>
> > Karl Hasselström wrote:
> >
> > > Let StGIT grok branch names with slashes in them. It used to
> > > fall flat on its face when confronted with them.
> > >
> > > I think I've covered all, or at least most cases, but there are
> > > probably some bugs left if you look hard enough.
> >
> > Does `stgit -r patchname/bottom` still work?
>
> Yes (if you mean 'stg diff -r ... ' :-). It's just branches that can
> have slashes in their names, not patches.

There was a bug here after all: I just tried "stg pick
multi@kha/patches" (to pick a patch named "multi" from the branch
"kha/patches"), and StGIT tried to pick the patch from branch "kha".

Looking closer, I realized that the complete patch specification
syntax is "patchname@branchname/bottom", not
"patchname/bottom@branchname" as I had assumed. This is obviously hard
to reconcile with branch names containing /.

Thoughts?

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

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-27 12:11       ` Karl Hasselström
@ 2006-02-27 12:29         ` Catalin Marinas
  2006-02-27 13:09           ` Karl Hasselström
  0 siblings, 1 reply; 14+ messages in thread
From: Catalin Marinas @ 2006-02-27 12:29 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Sam Vilain, git

On Mon, 2006-02-27 at 13:11 +0100, Karl Hasselström wrote:
> There was a bug here after all: I just tried "stg pick
> multi@kha/patches" (to pick a patch named "multi" from the branch
> "kha/patches"), and StGIT tried to pick the patch from branch "kha".

I haven't applied your patch yet (too busy to properly review it).

> Looking closer, I realized that the complete patch specification
> syntax is "patchname@branchname/bottom", not
> "patchname/bottom@branchname" as I had assumed. This is obviously hard
> to reconcile with branch names containing /.

I don't have any strong opinion on either. Maybe we should use the
latter if it makes things easier for supporting branch names with /'s.

-- 
Catalin

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-27 12:29         ` Catalin Marinas
@ 2006-02-27 13:09           ` Karl Hasselström
  0 siblings, 0 replies; 14+ messages in thread
From: Karl Hasselström @ 2006-02-27 13:09 UTC (permalink / raw)
  To: catalin.marinas; +Cc: Sam Vilain, git

On 2006-02-27 12:29:51 +0000, Catalin Marinas wrote:

> On Mon, 2006-02-27 at 13:11 +0100, Karl Hasselström wrote:
>
> > There was a bug here after all: I just tried "stg pick
> > multi@kha/patches" (to pick a patch named "multi" from the branch
> > "kha/patches"), and StGIT tried to pick the patch from branch
> > "kha".
>
> I haven't applied your patch yet (too busy to properly review it).

And as I just demonstrated, it certainly needed reviewing! (Actually,
I believe I said that back when I posted the patch, too.)

> > Looking closer, I realized that the complete patch specification
> > syntax is "patchname@branchname/bottom", not
> > "patchname/bottom@branchname" as I had assumed. This is obviously
> > hard to reconcile with branch names containing /.
>
> I don't have any strong opinion on either. Maybe we should use the
> latter if it makes things easier for supporting branch names with
> /'s.

The problem is that the current from is better (bottom is a modifier
to patch@branch, not just patch). And using the other form will break
when someone decides that patches with slashes in their names are a
good idea (not a joke).

Perhaps change /bottom to #bottom (making the complete form
patchname@branchname#bottom), and for backward compatibility accept
patchname@branchname/bottom as well when no branch called
"branchname/bottom" exists.

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

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

* Re: [PATCH] Handle branch names with slashes
  2006-02-17  9:47   ` Catalin Marinas
  2006-02-17  9:53     ` Karl Hasselström
@ 2006-04-12 19:16     ` Yann Dirson
  2006-04-18 11:07       ` Karl Hasselström
  1 sibling, 1 reply; 14+ messages in thread
From: Yann Dirson @ 2006-04-12 19:16 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Karl Hasselström, git

On Fri, Feb 17, 2006 at 09:47:21AM +0000, Catalin Marinas wrote:
> Karl Hasselström <kha@treskal.com> wrote:
> > Let StGIT grok branch names with slashes in them. It used to fall flat
> > on its face when confronted with them.
> 
> Thanks for the patches you sent. I'll have a look at them tomorrow.

Is this still being worked on ?
-- 
Yann Dirson    <ydirson@altern.org> |
Debian-related: <dirson@debian.org> |   Support Debian GNU/Linux:
                                    |  Freedom, Power, Stability, Gratis
     http://ydirson.free.fr/        | Check <http://www.debian.org/>

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

* Re: [PATCH] Handle branch names with slashes
  2006-04-12 19:16     ` Yann Dirson
@ 2006-04-18 11:07       ` Karl Hasselström
  0 siblings, 0 replies; 14+ messages in thread
From: Karl Hasselström @ 2006-04-18 11:07 UTC (permalink / raw)
  To: Yann Dirson; +Cc: Catalin Marinas, git

On 2006-04-12 21:16:55 +0200, Yann Dirson wrote:

> Is this still being worked on?

I haven't worked any more on the slashes-in-branch-names patch, but I
have been using it myself, and it works for me. Getting that fancy new
test suite to excercise it would be a really good next step . . .

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

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

* [PATCH] Handle branch names with slashes
  2006-05-18  6:42 [PATCH 1/2] " Karl Hasselström
@ 2006-05-18  6:50 ` Karl Hasselström
  2006-05-18 12:11   ` Catalin Marinas
  0 siblings, 1 reply; 14+ messages in thread
From: Karl Hasselström @ 2006-05-18  6:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Catalin Marinas

Teach stgit to handle branch names with slashes in them; that is,
branches living in a subdirectory of .git/refs/heads.

I had to change the patch@branch/top command-line syntax to
patch@branch//top, in order to get sane parsing. The /top variant is
still available for repositories that have no slashy branches; it is
disabled as soon as there exists at least one subdirectory of
refs/heads. Preferably, this compatibility hack can be killed some
time in the future.

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


---

Oh yeah, remember to change % to // in the commit comment as well . . .

Catalin, I hope you're paying attention when trying to pick the
correct three patches out of the salvos I've sent you. :-)

 stgit/commands/branch.py |    5 ++
 stgit/commands/common.py |  108 +++++++++++++++++++++++++++-------------------
 stgit/commands/diff.py   |   16 ++++---
 stgit/commands/files.py  |    4 +-
 stgit/commands/id.py     |    2 -
 stgit/commands/mail.py   |    8 ++-
 stgit/git.py             |   42 +++++++++---------
 stgit/stack.py           |   21 ++++++---
 stgit/utils.py           |   88 +++++++++++++++++++++++++++++++++++--
 9 files changed, 202 insertions(+), 92 deletions(-)

4ce56cd9e2d39f3a98b6dd010d11beb6037f8ff3
diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index 2218bbb..d348409 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -172,7 +172,10 @@ def func(parser, options, args):
         if len(args) != 0:
             parser.error('incorrect number of arguments')
 
-        branches = os.listdir(os.path.join(basedir.get(), 'refs', 'heads'))
+        branches = []
+        basepath = os.path.join(basedir.get(), 'refs', 'heads')
+        for path, files, dirs in walk_tree(basepath):
+            branches += [os.path.join(path, f) for f in files]
         branches.sort()
 
         if branches:
diff --git a/stgit/commands/common.py b/stgit/commands/common.py
index c6ca514..93344aa 100644
--- a/stgit/commands/common.py
+++ b/stgit/commands/common.py
@@ -18,7 +18,7 @@ along with this program; if not, write t
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re
+import sys, os, os.path, re
 from optparse import OptionParser, make_option
 
 from stgit.utils import *
@@ -34,54 +34,74 @@ class CmdException(Exception):
 
 
 # Utility functions
+class RevParseException(Exception):
+    """Revision spec parse error."""
+    pass
+
+def parse_rev(rev):
+    """Parse a revision specification into its
+    patchname@branchname//patch_id parts. If no branch name has a slash
+    in it, also accept / instead of //."""
+    files, dirs = list_files_and_dirs(os.path.join(basedir.get(),
+                                                   'refs', 'heads'))
+    if len(dirs) != 0:
+        # We have branch names with / in them.
+        branch_chars = r'[^@]'
+        patch_id_mark = r'//'
+    else:
+        # No / in branch names.
+        branch_chars = r'[^@%/]'
+        patch_id_mark = r'(/|//)'
+    patch_re = r'(?P<patch>[^@/]+)'
+    branch_re = r'@(?P<branch>%s+)' % branch_chars
+    patch_id_re = r'%s(?P<patch_id>[a-z.]*)' % patch_id_mark
+
+    # Try //patch_id.
+    m = re.match(r'^%s$' % patch_id_re, rev)
+    if m:
+        return None, None, m.group('patch_id')
+
+    # Try path[@branch]//patch_id.
+    m = re.match(r'^%s(%s)?%s$' % (patch_re, branch_re, patch_id_re), rev)
+    if m:
+        return m.group('patch'), m.group('branch'), m.group('patch_id')
+
+    # Try patch[@branch].
+    m = re.match(r'^%s(%s)?$' % (patch_re, branch_re), rev)
+    if m:
+        return m.group('patch'), m.group('branch'), None
+
+    # No, we can't parse that.
+    raise RevParseException
+
 def git_id(rev):
     """Return the GIT id
     """
     if not rev:
         return None
-    
-    rev_list = rev.split('/')
-    if len(rev_list) == 2:
-        patch_id = rev_list[1]
-        if not patch_id:
-            patch_id = 'top'
-    elif len(rev_list) == 1:
-        patch_id = 'top'
-    else:
-        patch_id = None
-
-    patch_branch = rev_list[0].split('@')
-    if len(patch_branch) == 1:
-        series = crt_series
-    elif len(patch_branch) == 2:
-        series = stack.Series(patch_branch[1])
-    else:
-        raise CmdException, 'Unknown id: %s' % rev
-
-    patch_name = patch_branch[0]
-    if not patch_name:
-        patch_name = series.get_current()
-        if not patch_name:
-            raise CmdException, 'No patches applied'
-
-    # patch
-    if patch_name in series.get_applied() \
-           or patch_name in series.get_unapplied():
-        if patch_id == 'top':
-            return series.get_patch(patch_name).get_top()
-        elif patch_id == 'bottom':
-            return series.get_patch(patch_name).get_bottom()
-        # Note we can return None here.
-        elif patch_id == 'top.old':
-            return series.get_patch(patch_name).get_old_top()
-        elif patch_id == 'bottom.old':
-            return series.get_patch(patch_name).get_old_bottom()
-
-    # base
-    if patch_name == 'base' and len(rev_list) == 1:
-        return read_string(series.get_base_file())
-
-    # anything else failed
+    try:
+        patch, branch, patch_id = parse_rev(rev)
+        if branch == None:
+            series = crt_series
+        else:
+            series = stack.Series(branch)
+        if patch == None:
+            patch = series.get_current()
+            if not patch:
+                raise CmdException, 'No patches applied'
+        if patch in series.get_applied() or patch in series.get_unapplied():
+            if patch_id in ['top', '', None]:
+                return series.get_patch(patch).get_top()
+            elif patch_id == 'bottom':
+                return series.get_patch(patch).get_bottom()
+            elif patch_id == 'top.old':
+                return series.get_patch(patch).get_old_top()
+            elif patch_id == 'bottom.old':
+                return series.get_patch(patch).get_old_bottom()
+        if patch == 'base' and patch_id == None:
+            return read_string(series.get_base_file())
+    except RevParseException:
+        pass
     return git.rev_parse(rev + '^{commit}')
 
 def check_local_changes():
diff --git a/stgit/commands/diff.py b/stgit/commands/diff.py
index 7dc6c5d..d765784 100644
--- a/stgit/commands/diff.py
+++ b/stgit/commands/diff.py
@@ -33,12 +33,12 @@ or a tree-ish object and another tree-is
 be given to restrict the diff output. The tree-ish object can be a
 standard git commit, tag or tree. In addition to these, the command
 also supports 'base', representing the bottom of the current stack,
-and '[patch]/[bottom | top]' for the patch boundaries (defaulting to
+and '[patch][//[bottom | top]]' for the patch boundaries (defaulting to
 the current one):
 
-rev = '([patch]/[bottom | top]) | <tree-ish> | base'
+rev = '([patch][//[bottom | top]]) | <tree-ish> | base'
 
-If neither bottom or top are given but a '/' is present, the command
+If neither bottom nor top are given but a '//' is present, the command
 shows the specified patch (defaulting to the current one)."""
 
 options = [make_option('-r', metavar = 'rev1[:[rev2]]', dest = 'revs',
@@ -55,10 +55,14 @@ def func(parser, options, args):
         rev_list = options.revs.split(':')
         rev_list_len = len(rev_list)
         if rev_list_len == 1:
-            if rev_list[0][-1] == '/':
+            rev = rev_list[0]
+            if rev[-1] == '/':
                 # the whole patch
-                rev1 = rev_list[0] + 'bottom'
-                rev2 = rev_list[0] + 'top'
+                rev = rev[:-1]
+                if rev[-1] == '/':
+                    rev = rev[:-1]
+                rev1 = rev + '//bottom'
+                rev2 = rev + '//top'
             else:
                 rev1 = rev_list[0]
                 rev2 = None
diff --git a/stgit/commands/files.py b/stgit/commands/files.py
index 0694d83..b33bd2a 100644
--- a/stgit/commands/files.py
+++ b/stgit/commands/files.py
@@ -53,8 +53,8 @@ def func(parser, options, args):
     else:
         parser.error('incorrect number of arguments')
 
-    rev1 = git_id('%s/bottom' % patch)
-    rev2 = git_id('%s/top' % patch)
+    rev1 = git_id('%s//bottom' % patch)
+    rev2 = git_id('%s//top' % patch)
 
     if options.stat:
         print git.diffstat(rev1 = rev1, rev2 = rev2)
diff --git a/stgit/commands/id.py b/stgit/commands/id.py
index 1cf6ea6..284589a 100644
--- a/stgit/commands/id.py
+++ b/stgit/commands/id.py
@@ -28,7 +28,7 @@ usage = """%prog [options] [id]
 
 Print the hash value of a GIT id (defaulting to HEAD). In addition to
 the standard GIT id's like heads and tags, this command also accepts
-'base[@<branch>]' and '[<patch>[@<branch>]][/(bottom | top)]'. If no
+'base[@<branch>]' and '[<patch>[@<branch>]][//[bottom | top]]'. If no
 'top' or 'bottom' are passed and <patch> is a valid patch name, 'top'
 will be used by default."""
 
diff --git a/stgit/commands/mail.py b/stgit/commands/mail.py
index 5e01ea1..3928b81 100644
--- a/stgit/commands/mail.py
+++ b/stgit/commands/mail.py
@@ -324,10 +324,10 @@ def __build_message(tmpl, patch, patch_n
                  'shortdescr':   short_descr,
                  'longdescr':    long_descr,
                  'endofheaders': headers_end,
-                 'diff':         git.diff(rev1 = git_id('%s/bottom' % patch),
-                                          rev2 = git_id('%s/top' % patch)),
-                 'diffstat':     git.diffstat(rev1 = git_id('%s/bottom'%patch),
-                                              rev2 = git_id('%s/top' % patch)),
+                 'diff':         git.diff(rev1 = git_id('%s//bottom' % patch),
+                                          rev2 = git_id('%s//top' % patch)),
+                 'diffstat':     git.diffstat(rev1 = git_id('%s//bottom'%patch),
+                                              rev2 = git_id('%s//top' % patch)),
                  'date':         email.Utils.formatdate(localtime = True),
                  'version':      version_str,
                  'patchnr':      patch_nr_str,
diff --git a/stgit/git.py b/stgit/git.py
index 2884f36..716609c 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -225,7 +225,8 @@ def get_head():
 def get_head_file():
     """Returns the name of the file pointed to by the HEAD link
     """
-    return os.path.basename(_output_one_line('git-symbolic-ref HEAD'))
+    return strip_prefix('refs/heads/',
+                        _output_one_line('git-symbolic-ref HEAD'))
 
 def set_head_file(ref):
     """Resets HEAD to point to a new ref
@@ -233,7 +234,8 @@ def set_head_file(ref):
     # head cache flushing is needed since we might have a different value
     # in the new head
     __clear_head_cache()
-    if __run('git-symbolic-ref HEAD', [ref]) != 0:
+    if __run('git-symbolic-ref HEAD',
+             [os.path.join('refs', 'heads', ref)]) != 0:
         raise GitException, 'Could not set head to "%s"' % ref
 
 def __set_head(val):
@@ -272,6 +274,7 @@ def rev_parse(git_id):
 def branch_exists(branch):
     """Existence check for the named branch
     """
+    branch = os.path.join('refs', 'heads', branch)
     for line in _output_lines('git-rev-parse --symbolic --all 2>&1'):
         if line.strip() == branch:
             return True
@@ -282,12 +285,11 @@ def branch_exists(branch):
 def create_branch(new_branch, tree_id = None):
     """Create a new branch in the git repository
     """
-    new_head = os.path.join('refs', 'heads', new_branch)
-    if branch_exists(new_head):
+    if branch_exists(new_branch):
         raise GitException, 'Branch "%s" already exists' % new_branch
 
     current_head = get_head()
-    set_head_file(new_head)
+    set_head_file(new_branch)
     __set_head(current_head)
 
     # a checkout isn't needed if new branch points to the current head
@@ -297,22 +299,22 @@ def create_branch(new_branch, tree_id = 
     if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
         os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
 
-def switch_branch(name):
+def switch_branch(new_branch):
     """Switch to a git branch
     """
     global __head
 
-    new_head = os.path.join('refs', 'heads', name)
-    if not branch_exists(new_head):
-        raise GitException, 'Branch "%s" does not exist' % name
+    if not branch_exists(new_branch):
+        raise GitException, 'Branch "%s" does not exist' % new_branch
 
-    tree_id = rev_parse(new_head + '^{commit}')
+    tree_id = rev_parse(os.path.join('refs', 'heads', new_branch)
+                        + '^{commit}')
     if tree_id != get_head():
         refresh_index()
         if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
             raise GitException, 'git-read-tree failed (local changes maybe?)'
         __head = tree_id
-    set_head_file(new_head)
+    set_head_file(new_branch)
 
     if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
         os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
@@ -320,25 +322,23 @@ def switch_branch(name):
 def delete_branch(name):
     """Delete a git branch
     """
-    branch_head = os.path.join('refs', 'heads', name)
-    if not branch_exists(branch_head):
+    if not branch_exists(name):
         raise GitException, 'Branch "%s" does not exist' % name
-    os.remove(os.path.join(basedir.get(), branch_head))
+    remove_file_and_dirs(os.path.join(basedir.get(), 'refs', 'heads'),
+                         name)
 
 def rename_branch(from_name, to_name):
     """Rename a git branch
     """
-    from_head = os.path.join('refs', 'heads', from_name)
-    if not branch_exists(from_head):
+    if not branch_exists(from_name):
         raise GitException, 'Branch "%s" does not exist' % from_name
-    to_head = os.path.join('refs', 'heads', to_name)
-    if branch_exists(to_head):
+    if branch_exists(to_name):
         raise GitException, 'Branch "%s" already exists' % to_name
 
     if get_head_file() == from_name:
-        set_head_file(to_head)
-    os.rename(os.path.join(basedir.get(), from_head), \
-              os.path.join(basedir.get(), to_head))
+        set_head_file(to_name)
+    rename(os.path.join(basedir.get(), 'refs', 'heads'),
+           from_name, to_name)
 
 def add(names):
     """Add the files or recursively add the directory contents
diff --git a/stgit/stack.py b/stgit/stack.py
index f83161b..49b50e7 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -443,8 +443,7 @@ class Series:
 
         os.makedirs(self.__patch_dir)
 
-        if not os.path.isdir(bases_dir):
-            os.makedirs(bases_dir)
+        create_dirs(bases_dir)
 
         create_empty_file(self.__applied_file)
         create_empty_file(self.__unapplied_file)
@@ -502,11 +501,14 @@ class Series:
         git.rename_branch(self.__name, to_name)
 
         if os.path.isdir(self.__series_dir):
-            os.rename(self.__series_dir, to_stack.__series_dir)
+            rename(os.path.join(self.__base_dir, 'patches'),
+                   self.__name, to_stack.__name)
         if os.path.exists(self.__base_file):
-            os.rename(self.__base_file, to_stack.__base_file)
+            rename(os.path.join(self.__base_dir, 'refs', 'bases'),
+                   self.__name, to_stack.__name)
         if os.path.exists(self.__refs_dir):
-            os.rename(self.__refs_dir, to_stack.__refs_dir)
+            rename(os.path.join(self.__base_dir, 'refs', 'patches'),
+                   self.__name, to_stack.__name)
 
         self.__init__(to_name)
 
@@ -560,16 +562,19 @@ class Series:
             else:
                 print 'Patch directory %s is not empty.' % self.__name
             if not os.listdir(self.__series_dir):
-                os.rmdir(self.__series_dir)
+                remove_dirs(os.path.join(self.__base_dir, 'patches'),
+                            self.__name)
             else:
                 print 'Series directory %s is not empty.' % self.__name
             if not os.listdir(self.__refs_dir):
-                os.rmdir(self.__refs_dir)
+                remove_dirs(os.path.join(self.__base_dir, 'refs', 'patches'),
+                            self.__name)
             else:
                 print 'Refs directory %s is not empty.' % self.__refs_dir
 
         if os.path.exists(self.__base_file):
-            os.remove(self.__base_file)
+            remove_file_and_dirs(
+                os.path.join(self.__base_dir, 'refs', 'bases'), self.__name)
 
     def refresh_patch(self, files = None, message = None, edit = False,
                       show_patch = False,
diff --git a/stgit/utils.py b/stgit/utils.py
index 5749b3b..68b8f58 100644
--- a/stgit/utils.py
+++ b/stgit/utils.py
@@ -1,6 +1,8 @@
 """Common utility functions
 """
 
+import errno, os, os.path
+
 __copyright__ = """
 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
 
@@ -18,6 +20,12 @@ along with this program; if not, write t
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
+def mkdir_file(filename, mode):
+    """Opens filename with the given mode, creating the directory it's
+    in if it doesn't already exist."""
+    create_dirs(os.path.dirname(filename))
+    return file(filename, mode)
+
 def read_string(filename, multiline = False):
     """Reads the first line from a file
     """
@@ -32,7 +40,7 @@ def read_string(filename, multiline = Fa
 def write_string(filename, line, multiline = False):
     """Writes 'line' to file and truncates it
     """
-    f = file(filename, 'w+')
+    f = mkdir_file(filename, 'w+')
     if multiline:
         f.write(line)
     else:
@@ -42,7 +50,7 @@ def write_string(filename, line, multili
 def append_strings(filename, lines):
     """Appends 'lines' sequence to file
     """
-    f = file(filename, 'a+')
+    f = mkdir_file(filename, 'a+')
     for line in lines:
         print >> f, line
     f.close()
@@ -50,14 +58,14 @@ def append_strings(filename, lines):
 def append_string(filename, line):
     """Appends 'line' to file
     """
-    f = file(filename, 'a+')
+    f = mkdir_file(filename, 'a+')
     print >> f, line
     f.close()
 
 def insert_string(filename, line):
     """Inserts 'line' at the beginning of the file
     """
-    f = file(filename, 'r+')
+    f = mkdir_file(filename, 'r+')
     lines = f.readlines()
     f.seek(0); f.truncate()
     print >> f, line
@@ -67,4 +75,74 @@ def insert_string(filename, line):
 def create_empty_file(name):
     """Creates an empty file
     """
-    file(name, 'w+').close()
+    mkdir_file(name, 'w+').close()
+
+def list_files_and_dirs(path):
+    """Return the sets of filenames and directory names in a
+    directory."""
+    files, dirs = [], []
+    for fd in os.listdir(path):
+        full_fd = os.path.join(path, fd)
+        if os.path.isfile(full_fd):
+            files.append(fd)
+        elif os.path.isdir(full_fd):
+            dirs.append(fd)
+    return files, dirs
+
+def walk_tree(basedir):
+    """Starting in the given directory, iterate through all its
+    subdirectories. For each subdirectory, yield the name of the
+    subdirectory (relative to the base directory), the list of
+    filenames in the subdirectory, and the list of directory names in
+    the subdirectory."""
+    subdirs = ['']
+    while subdirs:
+        subdir = subdirs.pop()
+        files, dirs = list_files_and_dirs(os.path.join(basedir, subdir))
+        for d in dirs:
+            subdirs.append(os.path.join(subdir, d))
+        yield subdir, files, dirs
+
+def strip_prefix(prefix, string):
+    """Return string, without the prefix. Blow up if string doesn't
+    start with prefix."""
+    assert string.startswith(prefix)
+    return string[len(prefix):]
+
+def remove_dirs(basedir, dirs):
+    """Starting at join(basedir, dirs), remove the directory if empty,
+    and try the same with its parent, until we find a nonempty
+    directory or reach basedir."""
+    path = dirs
+    while path:
+        try:
+            os.rmdir(os.path.join(basedir, path))
+        except OSError:
+            return # can't remove nonempty directory
+        path = os.path.dirname(path)
+
+def remove_file_and_dirs(basedir, file):
+    """Remove join(basedir, file), and then remove the directory it
+    was in if empty, and try the same with its parent, until we find a
+    nonempty directory or reach basedir."""
+    os.remove(os.path.join(basedir, file))
+    remove_dirs(basedir, os.path.dirname(file))
+
+def create_dirs(directory):
+    """Create the given directory, if the path doesn't already exist."""
+    if directory:
+        create_dirs(os.path.dirname(directory))
+        try:
+            os.mkdir(directory)
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise e
+
+def rename(basedir, file1, file2):
+    """Rename join(basedir, file1) to join(basedir, file2), not
+    leaving any empty directories behind and creating any directories
+    necessary."""
+    full_file2 = os.path.join(basedir, file2)
+    create_dirs(os.path.dirname(full_file2))
+    os.rename(os.path.join(basedir, file1), full_file2)
+    remove_dirs(basedir, os.path.dirname(file1))
-- 
1.3.2.g639c


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

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

* Re: [PATCH] Handle branch names with slashes
  2006-05-18  6:50 ` [PATCH] " Karl Hasselström
@ 2006-05-18 12:11   ` Catalin Marinas
  2006-05-18 16:00     ` Karl Hasselström
  0 siblings, 1 reply; 14+ messages in thread
From: Catalin Marinas @ 2006-05-18 12:11 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: Junio C Hamano, git

On 18/05/06, Karl Hasselström <kha@treskal.com> wrote:
> Teach stgit to handle branch names with slashes in them; that is,
> branches living in a subdirectory of .git/refs/heads.
[...]
> Catalin, I hope you're paying attention when trying to pick the
> correct three patches out of the salvos I've sent you. :-)

Hopefully, I applied them correctly. I'll update the public repository
tonight and you can check whether they are OK or not.

> --- a/stgit/commands/common.py
> +++ b/stgit/commands/common.py
[...]
> +    if len(dirs) != 0:
> +        # We have branch names with / in them.
> +        branch_chars = r'[^@]'
> +        patch_id_mark = r'//'
> +    else:
> +        # No / in branch names.
> +        branch_chars = r'[^@%/]'

I removed % from the above regexp.

Thanks for the patches. Great work.

-- 
Catalin

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

* Re: [PATCH] Handle branch names with slashes
  2006-05-18 12:11   ` Catalin Marinas
@ 2006-05-18 16:00     ` Karl Hasselström
  0 siblings, 0 replies; 14+ messages in thread
From: Karl Hasselström @ 2006-05-18 16:00 UTC (permalink / raw)
  To: Catalin Marinas; +Cc: Junio C Hamano, git

On 2006-05-18 13:11:52 +0100, Catalin Marinas wrote:

> On 18/05/06, Karl Hasselström <kha@treskal.com> wrote:
>
> > +    if len(dirs) != 0:
> > +        # We have branch names with / in them.
> > +        branch_chars = r'[^@]'
> > +        patch_id_mark = r'//'
> > +    else:
> > +        # No / in branch names.
> > +        branch_chars = r'[^@%/]'
>
> I removed % from the above regexp.

Ah, I missed one. Perhaps I should act surprised . . . :-)

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

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

end of thread, other threads:[~2006-05-18 16:00 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-02-14 17:35 bug: stgit doesn't handle branch names with / in them Karl Hasselström
2006-02-17  1:41 ` [PATCH] Handle branch names with slashes Karl  Hasselström
2006-02-17  3:01   ` Sam Vilain
2006-02-17  4:21     ` Karl Hasselström
2006-02-27 12:11       ` Karl Hasselström
2006-02-27 12:29         ` Catalin Marinas
2006-02-27 13:09           ` Karl Hasselström
2006-02-17  9:47   ` Catalin Marinas
2006-02-17  9:53     ` Karl Hasselström
2006-04-12 19:16     ` Yann Dirson
2006-04-18 11:07       ` Karl Hasselström
  -- strict thread matches above, loose matches on Subject: below --
2006-05-18  6:42 [PATCH 1/2] " Karl Hasselström
2006-05-18  6:50 ` [PATCH] " Karl Hasselström
2006-05-18 12:11   ` Catalin Marinas
2006-05-18 16:00     ` Karl Hasselström

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox