* 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 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 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 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 1/2] Handle branch names with slashes
@ 2006-05-18 6:42 Karl Hasselström
2006-05-18 6:50 ` [PATCH] " Karl Hasselström
0 siblings, 1 reply; 14+ messages in thread
From: Karl Hasselström @ 2006-05-18 6:42 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>
---
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(-)
fb5b39c8867474f4b23f0b52c4090c76aee6b1e8
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* [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