* [PATCH 1/3] Don't die when there are no branches [not found] <20060510060040.GA3034@diana.vm.bytemark.co.uk> @ 2006-05-15 9:53 ` Karl Hasselström 2006-05-15 9:54 ` [PATCH 2/3] Handle branch names with slashes Karl Hasselström 2006-05-15 9:55 ` [PATCH 3/3] " Karl Hasselström 2 siblings, 0 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-15 9:53 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git Signed-off-by: Karl Hasselström <kha@treskal.com> --- stgit/commands/branch.py | 11 +++++++---- 1 files changed, 7 insertions(+), 4 deletions(-) c32f6b7bfd81bdbdb136ff72a4ad073e162ab97c diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py index c7561a8..2218bbb 100644 --- a/stgit/commands/branch.py +++ b/stgit/commands/branch.py @@ -174,11 +174,14 @@ def func(parser, options, args): branches = os.listdir(os.path.join(basedir.get(), 'refs', 'heads')) branches.sort() - max_len = max([len(i) for i in branches]) - print 'Available branches:' - for i in branches: - __print_branch(i, max_len) + if branches: + print 'Available branches:' + max_len = max([len(i) for i in branches]) + for i in branches: + __print_branch(i, max_len) + else: + print 'No branches' return elif options.protect: -- 1.3.2.g639c -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] Handle branch names with slashes [not found] <20060510060040.GA3034@diana.vm.bytemark.co.uk> 2006-05-15 9:53 ` [PATCH 1/3] Don't die when there are no branches Karl Hasselström @ 2006-05-15 9:54 ` Karl Hasselström 2006-05-15 10:22 ` Catalin Marinas 2006-05-15 9:55 ` [PATCH 3/3] " Karl Hasselström 2 siblings, 1 reply; 15+ messages in thread From: Karl Hasselström @ 2006-05-15 9:54 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git 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 | 103 ++++++++++++++++++++++++++-------------------- stgit/commands/diff.py | 12 +++-- 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, 193 insertions(+), 92 deletions(-) 466723a40f8d40b84a23fe720447e1938e22c0a5 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..9439976 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,69 @@ 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 = '[^@#]' + patch_id_mark = '#' + else: + # No / in branch names. + branch_chars = '[^@#/]' + patch_id_mark = '[/#]' + 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 patch[@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') + + # 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..c3ab7d8 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 or 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,10 @@ 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] == '/': + if rev_list[0][-1] in ['/', '#']: # the whole patch - rev1 = rev_list[0] + 'bottom' - rev2 = rev_list[0] + 'top' + rev1 = rev_list[0][:-1] + '#bottom' + rev2 = rev_list[0][:-1] + '#top' else: rev1 = rev_list[0] rev2 = None diff --git a/stgit/commands/files.py b/stgit/commands/files.py index 0694d83..1d5126e 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..5202aa4 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..937efa3 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] 15+ messages in thread
* Re: [PATCH 2/3] Handle branch names with slashes 2006-05-15 9:54 ` [PATCH 2/3] Handle branch names with slashes Karl Hasselström @ 2006-05-15 10:22 ` Catalin Marinas 2006-05-15 10:58 ` Karl Hasselström 0 siblings, 1 reply; 15+ messages in thread From: Catalin Marinas @ 2006-05-15 10:22 UTC (permalink / raw) To: Karl Hasselström; +Cc: Wartan Hachaturow, git Karl, On 15/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. Thanks for the patches. I'll add them, probably tomorrow evening as I'm busy today. > 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. There is one problem with killing "/" entirely (or maybe we could use other character than "#"). I tend to write quite often "stg diff -r /bottom" to see how the whole patch looks like before refreshing. With "#", the shell ignores "#bottom" as being a comment. Otherwise, I'm OK with changing "/" with something else or just keeping both (though I prefer to have a singe way of specifying it). It looks like ^ and ~ are already used by GIT. It leaves us with % and !. Do you have any preference? The exclamation mark looks OK to me. -- Catalin ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] Handle branch names with slashes 2006-05-15 10:22 ` Catalin Marinas @ 2006-05-15 10:58 ` Karl Hasselström 2006-05-16 6:35 ` [PATCH 1/2] " Karl Hasselström 2006-05-16 6:37 ` Karl Hasselström 0 siblings, 2 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-15 10:58 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git On 2006-05-15 11:22:08 +0100, Catalin Marinas wrote: > There is one problem with killing "/" entirely (or maybe we could > use other character than "#"). I tend to write quite often "stg diff > -r /bottom" to see how the whole patch looks like before refreshing. > With "#", the shell ignores "#bottom" as being a comment. > > Otherwise, I'm OK with changing "/" with something else or just > keeping both (though I prefer to have a singe way of specifying it). > It looks like ^ and ~ are already used by GIT. It leaves us with % > and !. Do you have any preference? The exclamation mark looks OK to > me. Ah, right. Well, I would prefer %, since ! is used for some kind of shell history searching, but % is not touched by the shell, I think. -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/2] Handle branch names with slashes 2006-05-15 10:58 ` Karl Hasselström @ 2006-05-16 6:35 ` Karl Hasselström 2006-05-16 6:48 ` Junio C Hamano 2006-05-16 6:37 ` Karl Hasselström 1 sibling, 1 reply; 15+ messages in thread From: Karl Hasselström @ 2006-05-16 6:35 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git 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> --- This is the same patch as before, but with # replaced with %. stgit/commands/branch.py | 5 ++ stgit/commands/common.py | 103 ++++++++++++++++++++++++++-------------------- stgit/commands/diff.py | 12 +++-- 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, 193 insertions(+), 92 deletions(-) 76545c189be3a091ab62b112f1a841473600d35c 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..a428dd9 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,69 @@ 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 = '[^@%]' + patch_id_mark = '%' + else: + # No / in branch names. + branch_chars = '[^@%/]' + patch_id_mark = '[/%]' + 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 patch[@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') + + # 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..e465e7a 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 or 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,10 @@ 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] == '/': + if rev_list[0][-1] in ['/', '%']: # the whole patch - rev1 = rev_list[0] + 'bottom' - rev2 = rev_list[0] + 'top' + rev1 = rev_list[0][:-1] + '%bottom' + rev2 = rev_list[0][:-1] + '%top' else: rev1 = rev_list[0] rev2 = None diff --git a/stgit/commands/files.py b/stgit/commands/files.py index 0694d83..a20ce96 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..1a5938b 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..0d2c260 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] 15+ messages in thread
* Re: [PATCH 1/2] Handle branch names with slashes 2006-05-16 6:35 ` [PATCH 1/2] " Karl Hasselström @ 2006-05-16 6:48 ` Junio C Hamano 2006-05-16 7:45 ` Karl Hasselström 0 siblings, 1 reply; 15+ messages in thread From: Junio C Hamano @ 2006-05-16 6:48 UTC (permalink / raw) To: Karl Hasselström; +Cc: git Karl Hasselström <kha@treskal.com> writes: > 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. I wonder if using double-slashes is an easier alternative to type than '%', like "patch@branch//top". That way, you do not have to forbid per-cent sign in branch names. ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] Handle branch names with slashes 2006-05-16 6:48 ` Junio C Hamano @ 2006-05-16 7:45 ` Karl Hasselström 2006-05-17 15:18 ` Catalin Marinas ` (2 more replies) 0 siblings, 3 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-16 7:45 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Catalin Marinas On 2006-05-15 23:48:04 -0700, Junio C Hamano wrote: > Karl Hasselström <kha@treskal.com> writes: > > > 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. > > I wonder if using double-slashes is an easier alternative to type > than '%', like "patch@branch//top". That way, you do not have to > forbid per-cent sign in branch names. Good argument. And // does look slightly better than %, too. But I'll wait a few days this time, or else someone will surely come along with an even better idea. :-) -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/2] Handle branch names with slashes 2006-05-16 7:45 ` Karl Hasselström @ 2006-05-17 15:18 ` Catalin Marinas 2006-05-18 6:42 ` Karl Hasselström 2006-05-18 6:43 ` [PATCH 2/2] Tests for " Karl Hasselström 2 siblings, 0 replies; 15+ messages in thread From: Catalin Marinas @ 2006-05-17 15:18 UTC (permalink / raw) To: Karl Hasselström; +Cc: Junio C Hamano, git On 16/05/06, Karl Hasselström <kha@treskal.com> wrote: > On 2006-05-15 23:48:04 -0700, Junio C Hamano wrote: > > > Karl Hasselström <kha@treskal.com> writes: > > > > > 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. > > > > I wonder if using double-slashes is an easier alternative to type > > than '%', like "patch@branch//top". That way, you do not have to > > forbid per-cent sign in branch names. > > Good argument. And // does look slightly better than %, too. But I'll > wait a few days this time, or else someone will surely come along with > an even better idea. :-) I also thing // is a better solution than %. There is another option, a bit more complicated, "patch@branch:top". This would imply replacing : with .. in the current handling of the -r option in diff/mail/export but it would make the commit or patch ranges look more like GIT's arguments. -- Catalin ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/2] Handle branch names with slashes 2006-05-16 7:45 ` Karl Hasselström 2006-05-17 15:18 ` Catalin Marinas @ 2006-05-18 6:42 ` Karl Hasselström 2006-05-18 6:50 ` [PATCH] " Karl Hasselström 2006-05-18 6:43 ` [PATCH 2/2] Tests for " Karl Hasselström 2 siblings, 1 reply; 15+ 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] 15+ messages in thread
* [PATCH] Handle branch names with slashes 2006-05-18 6:42 ` Karl Hasselström @ 2006-05-18 6:50 ` Karl Hasselström 2006-05-18 12:11 ` Catalin Marinas 0 siblings, 1 reply; 15+ 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] 15+ 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; 15+ 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] 15+ 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; 15+ 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] 15+ messages in thread
* [PATCH 2/2] Tests for branch names with slashes 2006-05-16 7:45 ` Karl Hasselström 2006-05-17 15:18 ` Catalin Marinas 2006-05-18 6:42 ` Karl Hasselström @ 2006-05-18 6:43 ` Karl Hasselström 2 siblings, 0 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-18 6:43 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Catalin Marinas Test a number of operations on a repository that has branch names containing slashes (that is, branches living in a subdirectory of .git/refs/heads). Signed-off-by: Karl Hasselström <kha@treskal.com> --- t/t0001-subdir-branches.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 59 insertions(+), 0 deletions(-) create mode 100644 t/t0001-subdir-branches.sh d54c84917b40b1e8b05081f0c063fd99b2bbb1ad diff --git a/t/t0001-subdir-branches.sh b/t/t0001-subdir-branches.sh new file mode 100644 index 0000000..ddde238 --- /dev/null +++ b/t/t0001-subdir-branches.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright (c) 2006 Karl Hasselström +# + +test_description='Branch names containing slashes + +Test a number of operations on a repository that has branch names +containing slashes (that is, branches living in a subdirectory of +.git/refs/heads).' + +. ./test-lib.sh + +test_expect_success 'Create a patch' \ + 'stg init && + echo "foo" > foo.txt && + stg add foo.txt && + stg new foo -m "Add foo.txt" && + stg refresh' + +test_expect_success 'Old and new id with non-slashy branch' \ + 'stg id foo && + stg id foo// && + stg id foo/ && + stg id foo//top && + stg id foo/top && + stg id foo@master && + stg id foo@master//top && + stg id foo@master/top' + +test_expect_success 'Clone branch to slashier name' \ + 'stg branch --clone x/y/z' + +test_expect_success 'Try new form of id with slashy branch' \ + 'stg id foo && + stg id foo// && + stg id foo//top && + stg id foo@x/y/z && + stg id foo@x/y/z//top' + +test_expect_failure 'Try old id with slashy branch' \ + 'stg id foo/ || + stg id foo/top || + stg id foo@x/y/z/top' + +test_expect_success 'Create patch in slashy branch' \ + 'echo "bar" >> foo.txt && + stg new bar -m "Add another line" && + stg refresh' + +test_expect_success 'Rename branches' \ + 'stg branch --rename master goo/gaa && + test ! -e .git/refs/heads/master && + stg branch --rename goo/gaa x1/x2/x3/x4 && + test ! -e .git/refs/heads/goo && + stg branch --rename x1/x2/x3/x4 servant && + test ! -e .git/refs/heads/x1' + +test_done -- 1.3.2.g639c -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/2] Tests for branch names with slashes 2006-05-15 10:58 ` Karl Hasselström 2006-05-16 6:35 ` [PATCH 1/2] " Karl Hasselström @ 2006-05-16 6:37 ` Karl Hasselström 1 sibling, 0 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-16 6:37 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git Test a number of operations on a repository that has branch names containing slashes (that is, branches living in a subdirectory of .git/refs/heads). Signed-off-by: Karl Hasselström <kha@treskal.com> --- The test also had to be changed to use % instead of #. t/t0001-subdir-branches.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 59 insertions(+), 0 deletions(-) create mode 100644 t/t0001-subdir-branches.sh 2278d3988ae3fee7624aac6db6bd92677173749f diff --git a/t/t0001-subdir-branches.sh b/t/t0001-subdir-branches.sh new file mode 100644 index 0000000..64f583c --- /dev/null +++ b/t/t0001-subdir-branches.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright (c) 2006 Karl Hasselström +# + +test_description='Branch names containing slashes + +Test a number of operations on a repository that has branch names +containing slashes (that is, branches living in a subdirectory of +.git/refs/heads).' + +. ./test-lib.sh + +test_expect_success 'Create a patch' \ + 'stg init && + echo "foo" > foo.txt && + stg add foo.txt && + stg new foo -m "Add foo.txt" && + stg refresh' + +test_expect_success 'Old and new id with non-slashy branch' \ + 'stg id foo && + stg id foo% && + stg id foo/ && + stg id foo%top && + stg id foo/top && + stg id foo@master && + stg id foo@master%top && + stg id foo@master/top' + +test_expect_success 'Clone branch to slashier name' \ + 'stg branch --clone x/y/z' + +test_expect_success 'Try new form of id with slashy branch' \ + 'stg id foo && + stg id foo% && + stg id foo%top && + stg id foo@x/y/z && + stg id foo@x/y/z%top' + +test_expect_failure 'Try old id with slashy branch' \ + 'stg id foo/ || + stg id foo/top || + stg id foo@x/y/z/top' + +test_expect_success 'Create patch in slashy branch' \ + 'echo "bar" >> foo.txt && + stg new bar -m "Add another line" && + stg refresh' + +test_expect_success 'Rename branches' \ + 'stg branch --rename master goo/gaa && + test ! -e .git/refs/heads/master && + stg branch --rename goo/gaa x1/x2/x3/x4 && + test ! -e .git/refs/heads/goo && + stg branch --rename x1/x2/x3/x4 servant && + test ! -e .git/refs/heads/x1' + +test_done -- 1.3.2.g639c -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] Tests for branch names with slashes [not found] <20060510060040.GA3034@diana.vm.bytemark.co.uk> 2006-05-15 9:53 ` [PATCH 1/3] Don't die when there are no branches Karl Hasselström 2006-05-15 9:54 ` [PATCH 2/3] Handle branch names with slashes Karl Hasselström @ 2006-05-15 9:55 ` Karl Hasselström 2 siblings, 0 replies; 15+ messages in thread From: Karl Hasselström @ 2006-05-15 9:55 UTC (permalink / raw) To: Catalin Marinas; +Cc: Wartan Hachaturow, git Test a number of operations on a repository that has branch names containing slashes (that is, branches living in a subdirectory of .git/refs/heads). Signed-off-by: Karl Hasselström <kha@treskal.com> --- t/t0001-subdir-branches.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 59 insertions(+), 0 deletions(-) create mode 100644 t/t0001-subdir-branches.sh 58badd265645874165379bc841453a44b64e1906 diff --git a/t/t0001-subdir-branches.sh b/t/t0001-subdir-branches.sh new file mode 100644 index 0000000..e3608de --- /dev/null +++ b/t/t0001-subdir-branches.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright (c) 2006 Karl Hasselström +# + +test_description='Branch names containing slashes + +Test a number of operations on a repository that has branch names +containing slashes (that is, branches living in a subdirectory of +.git/refs/heads).' + +. ./test-lib.sh + +test_expect_success 'Create a patch' \ + 'stg init && + echo "foo" > foo.txt && + stg add foo.txt && + stg new foo -m "Add foo.txt" && + stg refresh' + +test_expect_success 'Old and new id with non-slashy branch' \ + 'stg id foo && + stg id foo# && + stg id foo/ && + stg id foo#top && + stg id foo/top && + stg id foo@master && + stg id foo@master#top && + stg id foo@master/top' + +test_expect_success 'Clone branch to slashier name' \ + 'stg branch --clone x/y/z' + +test_expect_success 'Try new form of id with slashy branch' \ + 'stg id foo && + stg id foo# && + stg id foo#top && + stg id foo@x/y/z && + stg id foo@x/y/z#top' + +test_expect_failure 'Try old id with slashy branch' \ + 'stg id foo/ || + stg id foo/top || + stg id foo@x/y/z/top' + +test_expect_success 'Create patch in slashy branch' \ + 'echo "bar" >> foo.txt && + stg new bar -m "Add another line" && + stg refresh' + +test_expect_success 'Rename branches' \ + 'stg branch --rename master goo/gaa && + test ! -e .git/refs/heads/master && + stg branch --rename goo/gaa x1/x2/x3/x4 && + test ! -e .git/refs/heads/goo && + stg branch --rename x1/x2/x3/x4 servant && + test ! -e .git/refs/heads/x1' + +test_done -- 1.3.2.g639c -- Karl Hasselström, kha@treskal.com www.treskal.com/kalle ^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2006-05-18 16:00 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20060510060040.GA3034@diana.vm.bytemark.co.uk>
2006-05-15 9:53 ` [PATCH 1/3] Don't die when there are no branches Karl Hasselström
2006-05-15 9:54 ` [PATCH 2/3] Handle branch names with slashes Karl Hasselström
2006-05-15 10:22 ` Catalin Marinas
2006-05-15 10:58 ` Karl Hasselström
2006-05-16 6:35 ` [PATCH 1/2] " Karl Hasselström
2006-05-16 6:48 ` Junio C Hamano
2006-05-16 7:45 ` Karl Hasselström
2006-05-17 15:18 ` Catalin Marinas
2006-05-18 6:42 ` 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
2006-05-18 6:43 ` [PATCH 2/2] Tests for " Karl Hasselström
2006-05-16 6:37 ` Karl Hasselström
2006-05-15 9:55 ` [PATCH 3/3] " 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