* [PATCH] Handle branch names with slashes
From: Karl Hasselström @ 2006-05-18 6:50 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Catalin Marinas
In-Reply-To: <20060518064214.GA10390@backpacker.hemma.treskal.com>
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
* Re: Necessity of "evil" merge and topic branches
From: Junio C Hamano @ 2006-05-18 6:44 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7vy7wz6e8c.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
> Now, unlike "pu", I never rewind "next", so once I did this
> "evil merge", I do not have to worry about this anymore while
> the topic is still on "next".
Side note. If an evil merge or other hand resolution needs to
be done on a path that results in automerge conflicts, rerere
often helps. I really should start advertising it a bit more.
Unfortunatly, rerere does not even kick in in this case.
^ permalink raw reply
* [PATCH 2/2] Tests for branch names with slashes
From: Karl Hasselström @ 2006-05-18 6:43 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Catalin Marinas
In-Reply-To: <20060516074504.GA27234@diana.vm.bytemark.co.uk>
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
* [PATCH 1/2] Handle branch names with slashes
From: Karl Hasselström @ 2006-05-18 6:42 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Catalin Marinas
In-Reply-To: <20060516074504.GA27234@diana.vm.bytemark.co.uk>
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
* Necessity of "evil" merge and topic branches
From: Junio C Hamano @ 2006-05-18 6:25 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0605172120160.10823@g5.osdl.org>
When you are maintaining two codebases (one is slightly ahead of
the other, e.g. "master" and "next"), you would expect that a
topic branch based on "master" would apply well to "next".
So your "make guts of ls-files accessible from others, and make
git-add a built-in" is stored in "lt/dirwalk" topic branch,
based on a commit on "master". By itself, it passes all tests.
When it is merged into "next", which has other stuff, literal
merging did not really work (not your fault). Many tests that
involve writing trees (typically to create new commits) fail
miserably.
o---o---o---o---o---o---o---o master
. \
. o---o---o lt/dirwalk -- contains builtin-add
\ \
o---o---o---o---o next -- up to date with "master" but
has many other stuff in it.
This is because the rule to manupulate the index is a bit
different on "next" branch, where it has jc/cache-tree topic.
With cache-tree, when you modify the index, you need to either
invalidate the path (and its parent directories up to root) in
the cached tree information, or discard the whole cache-tree,
whichever is easier [*1*]. So I have an "evil merge" that
merges the lt/dirwalk topic to next (it is ae12e59a). It
changes the code like this:
$ git diff lt/dirwalk next -- builtin-add.c
diff --git a/builtin-add.c b/builtin-add.c
index 089c7a8..7083820 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -8,6 +8,7 @@ #include <fnmatch.h>
#include "cache.h"
#include "builtin.h"
#include "dir.h"
+#include "cache-tree.h"
static const char builtin_add_usage[] =
"git-add [-n] [-v] <filepattern>...";
@@ -197,6 +198,7 @@ static int add_file_to_index(const char
die("unable to add %s to index",path);
if (verbose)
printf("add '%s'\n", path);
+ cache_tree_invalidate_path(active_cache_tree, path);
return 0;
}
Obviously, this is a semantic adjustment, not a simple textual
merge, so no automated merge algorithm would help doing this for
me. I literally edited the automerged builtin-add.c (the merge
is "one adds a new file, the other does not do anything" case,
so it trivially automerges at the tree level) and amended the
commit when I made the merge.
Now, unlike "pu", I never rewind "next", so once I did this
"evil merge", I do not have to worry about this anymore while
the topic is still on "next". However, when jc/cache-tree topic
and lt/dirwalk topic both graduate to "master", I will somehow
need to remember to do this again. That sounds somewhat painful
and quite error prone.
I am not going to rewind "next", so this evil merge will stay
there, but I am wondering if there was a better way I could have
handled this.
If I _know_ lt/dirwalk is going to graduate first (and I think
that is the case), I could have pulled lt/dirwalk into
jc/cache-tree topic, done an equivalent "evil merge" as the
above on jc/cache-tree topic, and pulled the result into "next".
That way, when lt/dirwalk alone graduates, I can just pull it
into "master". Later, when the jc/cache-tree graduates, the
necessary "evil merge" will be pulled along with it, so I can
truly forget and not worry about it after I do the evil merge
once.
But in general, you would not know which one will graduate
first, so pulling one topic into another is not always a good
idea. If I pull lt/dirwalk into jc/cache-tree, making
jc/cache-tree graduate alone becomes impossible. Depending on
the readiness of these two topics, it may not be acceptable to
create such a dependency between topics.
One possibility I can think of is that I could have created
another topic branch that merged jc/cache-tree and lt/dirwalk in
the evil way, and pulled that into "next". Then either one of
the topics can independently graduate to "master" without
waiting for the other. I still have to remember that I need to
merge the third topic with evil merge when I make both of them
graduate to "master", but at least I do not have to remember the
details of how that evil merge should look like when I do so. I
only need to remember that it needs to be done.
In practice, when I merge a topic branch into "master", I note
the paths that merge touches from "master", and run diff between
"master" and "next" for them to see if the remaining changes on
them are reasonable. I will hopefully remember the need for
(and the details of) the evil merge that way when I pull these
two branches to "master", so what I did would not cause trouble
down the road (with some luck ;-), but I feel there should be a
better way to handle this situation.
Thoughts?
[Footnotes]
*1* In this message, let's not discuss if cache-tree is a good
idea to begin with, if we should rip it out and/or if we
should replace it with tree objects in the index. I'd like to
discuss SCM issue to handle somewhat interrelated topic
branches here, not cache-tree.
^ permalink raw reply
* Re: cvsimport weird
From: YOSHIFUJI Hideaki / 吉藤英明 @ 2006-05-18 5:56 UTC (permalink / raw)
To: proski; +Cc: martin.langhoff, beber.mailing, git, yoshfuji
In-Reply-To: <1147931094.32050.51.camel@dv>
In article <1147931094.32050.51.camel@dv> (at Thu, 18 May 2006 01:44:54 -0400), Pavel Roskin <proski@gnu.org> says:
> Address resolution is broken in cvsps on 64-bit machines. This patch to
> cvsps is needed:
>
> --- cbtcommon/tcpsocket.c
> +++ cbtcommon/tcpsocket.c
> @@ -198,7 +198,7 @@ convert_address(long *dest, const char *
> memcpy(dest, &ip.s_addr, sizeof(ip.s_addr));
> }
> #else
> - if ( (*dest = inet_addr(addr_str)) != -1)
> + if ( (*dest = inet_addr(addr_str)) != INADDR_NONE)
> {
> /* nothing */
> }
You need to define INADDR_NONE on some platforms; e.g. Solaris.
--yoshfuji
^ permalink raw reply
* Re: cvsimport weird
From: Pavel Roskin @ 2006-05-18 5:44 UTC (permalink / raw)
To: Martin Langhoff; +Cc: Bertrand Jacquin, Git Mailing List
In-Reply-To: <1147924771.32050.40.camel@dv>
On Wed, 2006-05-17 at 23:59 -0400, Pavel Roskin wrote:
> I'm quite sure that it's a bug in cvsps. It displays such things on
> x86_64, but works properly on 32-bit PowerPC.
Address resolution is broken in cvsps on 64-bit machines. This patch to
cvsps is needed:
--- cbtcommon/tcpsocket.c
+++ cbtcommon/tcpsocket.c
@@ -198,7 +198,7 @@ convert_address(long *dest, const char *
memcpy(dest, &ip.s_addr, sizeof(ip.s_addr));
}
#else
- if ( (*dest = inet_addr(addr_str)) != -1)
+ if ( (*dest = inet_addr(addr_str)) != INADDR_NONE)
{
/* nothing */
}
However, it's not sufficient to fix the original problem of empty CVS
server version string. For some reason cvsps fails to authenticate with
pserver. The ext protocol is working.
It's interesting that both git-cvsimport and cvsps have code to
authenticate over the pserver protocol. I think maybe git-cvsimport
shouldn't do it, or maybe it should close some sockets before running
cvsps.
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: [Patch] git-cvsimport: tiny fix
From: Martin Langhoff @ 2006-05-18 4:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Elrond
In-Reply-To: <7vd5eccvns.fsf@assigned-by-dhcp.cox.net>
On 5/18/06, Junio C Hamano <junkio@cox.net> wrote:
> Could somebody who actually works with CVS import Ack this?
> Pretty please?
Sounds sane. It would be interesting to hear about what repo (and
server) this was seen against. Elrond, can you tell us more about
this?
cheers,
martin
^ permalink raw reply
* Remove old "git-add.sh" remnants
From: Linus Torvalds @ 2006-05-18 4:21 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
Repeat after me: "It's now a built-in"
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---
diff --git a/Makefile b/Makefile
index a1d2e08..3a28580 100644
--- a/Makefile
+++ b/Makefile
@@ -113,7 +113,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powe
### --- END CONFIGURATION SECTION ---
SCRIPT_SH = \
- git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
+ git-bisect.sh git-branch.sh git-checkout.sh \
git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
git-fetch.sh \
git-format-patch.sh git-ls-remote.sh \
@@ -170,7 +170,7 @@ PROGRAMS = \
BUILT_INS = git-log$X git-whatchanged$X git-show$X \
git-count-objects$X git-diff$X git-push$X \
- git-grep$X
+ git-grep$X git-add$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
diff --git a/git-add.sh b/git-add.sh
deleted file mode 100755
index d6a4bc7..0000000
--- a/git-add.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/bin/sh
-
-USAGE='[-n] [-v] <file>...'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-show_only=
-verbose=
-while : ; do
- case "$1" in
- -n)
- show_only=true
- ;;
- -v)
- verbose=--verbose
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
- shift
-done
-
-# Check misspelled pathspec
-case "$#" in
-0) ;;
-*)
- git-ls-files --error-unmatch --others --cached -- "$@" >/dev/null || {
- echo >&2 "Maybe you misspelled it?"
- exit 1
- }
- ;;
-esac
-
-if test -f "$GIT_DIR/info/exclude"
-then
- git-ls-files -z \
- --exclude-from="$GIT_DIR/info/exclude" \
- --others --exclude-per-directory=.gitignore -- "$@"
-else
- git-ls-files -z \
- --others --exclude-per-directory=.gitignore -- "$@"
-fi |
-case "$show_only" in
-true)
- xargs -0 echo ;;
-*)
- git-update-index --add $verbose -z --stdin ;;
-esac
^ permalink raw reply related
* Re: cvsimport weird
From: Pavel Roskin @ 2006-05-18 3:59 UTC (permalink / raw)
To: Martin Langhoff; +Cc: Bertrand Jacquin, Git Mailing List
In-Reply-To: <46a038f90605171954n7e75ee64t412b22e8d405d909@mail.gmail.com>
On Thu, 2006-05-18 at 14:54 +1200, Martin Langhoff wrote:
> On 5/18/06, Bertrand Jacquin <beber.mailing@gmail.com> wrote:
> >
> The cvs server is strange -- buggy probably. cvsps thinks it is old,
> but it is not even returning a version string. Is it really cvs?
The version reporting is working for me:
$ cvs -d :pserver:anonymous@anoncvs.enlightenment.org:/var/cvs/e version
Client: Concurrent Versions System (CVS) 1.11.21 (client/server)
Server: Concurrent Versions System (CVS) 1.11.17 (client/server)
But I can reproduce the problem with git-cvsimport. git main branch,
cvsps 2.1.
I'm quite sure that it's a bug in cvsps. It displays such things on
x86_64, but works properly on 32-bit PowerPC.
x86_64:
$ cvsps --cvs-direct -A -u --root :pserver:anonymous@anoncvs.enlightenment.org:/var/cvs/e e17
connect error: Network is unreachable
WARNING: malformed CVS version: no data
WARNING: malformed CVS version str: (UNKNOWN CLIENT)
WARNING: Your CVS client version:
[(UNKNOWN CLIENT)]
and/or server version:
[(UNKNOWN SERVER)]
ppc:
$ cvsps --cvs-direct -A -u --root :pserver:anonymous@anoncvs.enlightenment.org:/var/cvs/e e17
cvs_direct initialized to CVSROOT /var/cvs/e
cvs rlog: Logging e17
cvs rlog: Logging e17/CVSROOT
cvs rlog: Logging e17/apps
cvs rlog: Logging e17/apps/e
cvs rlog: Logging e17/apps/e/client
...
Both are cvsps 2.1 on Fedora Core 5.
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: Fwd: [OT] Re: Git via a proxy server?
From: Sam Song @ 2006-05-18 3:44 UTC (permalink / raw)
To: Petr Vandrovec; +Cc: Jan-Benedict Glaw, git
In-Reply-To: <446B00CE.9000609@vmware.com>
Petr Vandrovec <petr@vmware.com> wrote:
> Jan-Benedict Glaw <jbglaw@lug-owl.de> wrote:
> > Well, install some package to have `socket'
> > available? Debian calls
> > the packet `socket', too, so I guess Fedora may
> > have something similar.
>
> Surprisingly they do not... You should be able to
> replace 'socket' with
> 'netcat' - and I believe that netcat/nc package is
> available for Fedora. For
> this purpose they have same command line & behavior.
Ummm, I am trying on that. nc is avaiable for Fedora.
But what could be the replacement for CONNECT in
Fedora? :-)
Thanks for your kind support,
Sam
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
^ permalink raw reply
* Re: cvsimport weird
From: Martin Langhoff @ 2006-05-18 2:54 UTC (permalink / raw)
To: Bertrand Jacquin; +Cc: Git Mailing List
In-Reply-To: <4fb292fa0605171800n4f041dd2l8af06d82bdbe6bff@mail.gmail.com>
On 5/18/06, Bertrand Jacquin <beber.mailing@gmail.com> wrote:
> Hi,
>
> I would like to make some git-cvsimport test on a public repo. And I
> get some problem and don't know if it's a remote server
> (enlightenment) problem or a git-cvsimport one.
>
> Here is the log :
>
> /mnt/data/src/e-tmp % git-cvsimport -v
> -d:pserver:anonymous@anoncvs.enlightenment.org:/var/cvs/e e17
> connect error: Network is unreachable
> WARNING: malformed CVS version str: Server:
> WARNING: Your CVS client version:
> [Client: Concurrent Versions System (CVS) 1.12.12 (client)]
> and/or server version:
> [Server: ]
> are too old to properly support the rlog command.
> This command was introduced in 1.11.1. Cvsps
> will use log instead, but PatchSet numbering
> may become unstable due to pruned empty
> directories.
The cvs server is strange -- buggy probably. cvsps thinks it is old,
but it is not even returning a version string. Is it really cvs?
> cvs [log aborted]: end of file from server (consult above messages if any)
The remote end is dying on you.
> I don't why it tell me that Network is unreachable as I can do normal
> cvs checkout.
I think it is the remote end that is saying Network unreachable.
Perhaps it is a cvs proxy that only allows some commands?
> Also, I have the repo in another directory, and I don't know how to
> use a :local: CVSROOT
git-cvsimport -d /tmp/cvsrepo/ modulename
should work ok. You should also try parsecvs ;-)
m
^ permalink raw reply
* cvsimport weird
From: Bertrand Jacquin @ 2006-05-18 1:00 UTC (permalink / raw)
To: Git Mailing List
Hi,
I would like to make some git-cvsimport test on a public repo. And I
get some problem and don't know if it's a remote server
(enlightenment) problem or a git-cvsimport one.
Here is the log :
/mnt/data/src/e-tmp % git-cvsimport -v
-d:pserver:anonymous@anoncvs.enlightenment.org:/var/cvs/e e17
connect error: Network is unreachable
WARNING: malformed CVS version str: Server:
WARNING: Your CVS client version:
[Client: Concurrent Versions System (CVS) 1.12.12 (client)]
and/or server version:
[Server: ]
are too old to properly support the rlog command.
This command was introduced in 1.11.1. Cvsps
will use log instead, but PatchSet numbering
may become unstable due to pruned empty
directories.
cvs [log aborted]: end of file from server (consult above messages if any)
DONE.
Already up-to-date.
/mnt/data/src/e-tmp %
I use cvs 1.12.12, cvsps 2.1, git 1.3.3
I don't why it tell me that Network is unreachable as I can do normal
cvs checkout.
Also, I have the repo in another directory, and I don't know how to
use a :local: CVSROOT
--
Beber
#e.fr@freenode
^ permalink raw reply
* Re: 1.3.2 git-clone segfaults
From: Linus Torvalds @ 2006-05-18 0:54 UTC (permalink / raw)
To: Pavel Roskin; +Cc: Bill Yoder, git, Wolfgang Denk
In-Reply-To: <1147909920.32050.29.camel@dv>
On Wed, 17 May 2006, Pavel Roskin wrote:
>
> Looks like a curl bug to me. curl 7.15.1, glibc 2.4, git master branch.
If the thing is fixed by turning off DAV support (and I thought somebody
reported it was), maybe we should turn that off by default? Ie, make
NO_EXPAT be the default, and you have to explicitly turn it off.
Linus
^ permalink raw reply
* Re: 1.3.2 git-clone segfaults
From: Pavel Roskin @ 2006-05-17 23:52 UTC (permalink / raw)
To: Bill Yoder; +Cc: git, Wolfgang Denk
In-Reply-To: <1147894165.16654.10.camel@dv>
On Wed, 2006-05-17 at 15:29 -0400, Pavel Roskin wrote:
> On Wed, 2006-05-17 at 13:32 -0500, Bill Yoder wrote:
> > /usr/local/downloads/git-1.3.2/git-clone: line 323: 25972
> > Segmentation fault git-http-fetch -v -a -w "$tname" "$name" "$1/"
>
> I've seen git-http-fetch segfaults many times when cloning qgit, but
> it's hard to reproduce on demand.
>
> I think you should compile git without optimizations and allow coredumps
> (ulimit -c unlimited), then load git-http-fetch in gdb with the core
> (gdb --core=core git-http-fetch) and run bt to see the backtrace.
Also comment out both "trap" invocations in git-clone, or the coredump
will be deleted.
That's what I've got on Fedora Core 5 x86_64 with glibc and curl debug
info installed:
#0 __strncasecmp (s1=Variable "s1" is not available.
) at strncase.c:68
68 while ((result = TOLOWER (*p1) - TOLOWER (*p2++)) == 0)
(gdb) where
#0 __strncasecmp (s1=Variable "s1" is not available.
) at strncase.c:68
#1 0x00000031f3e26c09 in curl_strnequal (first=Variable "first" is not available.
) at strequal.c:60
#2 0x00000031f3e0f43a in checkheaders (data=Variable "data" is not available.
) at http.c:119
#3 0x00000031f3e10cf9 in Curl_http (conn=0x1c421c0, done=Variable "done" is not available.
) at http.c:1580
#4 0x00000031f3e1a858 in Curl_do (connp=0x83af88, done=0x7fff29c97ebb "\001\001")
at url.c:3841
#5 0x00000031f3e28f22 in curl_multi_perform (multi_handle=0x53b590,
running_handles=0x7fff29c97ef8) at multi.c:526
#6 0x00000000004040c0 in step_active_slots () at http.c:376
#7 0x000000000040412c in run_active_slot (slot=0x546690) at http.c:400
#8 0x0000000000403e44 in http_cleanup () at http.c:275
#9 0x00000000004077d7 in main (argc=7, argv=0x7fff29c98258) at http-fetch.c:1274
(gdb) p p1
$1 = (const unsigned char *) 0x0
(gdb) p p2
$2 = (const unsigned char *) 0x31f3e2e817 "User-Agent:"
(gdb)
Looks like a curl bug to me. curl 7.15.1, glibc 2.4, git master branch.
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: [PATCH] Implement git-quiltimport (take 2)
From: Junio C Hamano @ 2006-05-17 23:34 UTC (permalink / raw)
To: Eric W. Biederman; +Cc: git
In-Reply-To: <m1zmhg31cm.fsf@ebiederm.dsl.xmission.com>
ebiederm@xmission.com (Eric W. Biederman) writes:
> Junio C Hamano <junkio@cox.net> writes:
>
>> What's the expected workflow for you to work on a 1300 patch
>> series you get from Andrew in the next installment to deal with
>> 88 unattributed patches? Answer the question 88 times and make
>> sure you get the answers right every time? Or abort and
>> hand-edit them to help mailinfo to notice the correct
>> attribution and re-run?
>
> For the internal consumption case it isn't a big deal. I
> can specify --author with something bogus and it works.
Yes.
>> I know I am guilty of suggesting "going interactive", but I have
>> a feeling that having an optional file that maps patch-name to
>> author might be easier to work with. If the old patches are
>> recycled in the updated -mm set, you probably can reuse the
>> mapping for them, adding entries for newly introduced "unnamed"
>> patches as needed.
>
> Short of getting the script where it has a sane restart in the
> middle mode going interactive and asking questions makes a lot
> of sense. Especially with smaller trees.
Yes perhaps on smaller trees, but that does not mean much. For
smaller trees and/or smaller patch series almost anything would
do.
How about doing something like this, so that the user can record
the fixup information, especially with --dry-run patch? Then
the next round from the updated -mm tree the user would not have
to retype them again ("then..fi" part should be indented in the
final version, but I did not want indentation changes to
distract you):
# Parse the author information
export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info")
export GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info")
+ already_tried_fixup=
while test -z "$GIT_AUTHOR_EMAIL" && test -z "$GIT_AUTHOR_NAME" ; do
if [ -n "$quilt_author" ] ; then
GIT_AUTHOR_NAME="$quilt_author_name";
GIT_AUTHOR_EMAIL="$quilt_author_email";
else
+ if test -z "$already_tried_fixup"
+ then
+ patch_author=`grep author-fixup "$patch_name"`
+ already_tried_fixup=t
+ fi
+ if test -z "$patch_author"
+ then
echo "No author found in $patch_name";
echo "---"
cat $tmp_msg
echo -n "Author: ";
read patch_author
+ fi
echo "$patch_author"
> For Andrews tree before I play anymore with technical solutions I
> need to talk to Andrew and see if we can improve the situation
> upstream. Possibly with a quilt-audit script that finds problem
> patches.
Yes, that sounds very sensible.
^ permalink raw reply
* Re: [RFC] qgit with tabs
From: Pavel Roskin @ 2006-05-17 23:21 UTC (permalink / raw)
To: Marco Costalba; +Cc: git
In-Reply-To: <e5bfff550605131538u63b87002o3e9b5542c0e15bf7@mail.gmail.com>
Hi, Marco!
On Sun, 2006-05-14 at 00:38 +0200, Marco Costalba wrote:
> Hi Pavel,
>
> >
> > Sure, but I often want to see what changed in a particular file.
> >
> > And of course I only mean the subwindow dislaying the files affected by the
> > patch. The file tree should still have file annotation bound to the double
> > click.
> >
>
> I understand your reasons, but I have some doubts about this change:
>
> 1) The context menu is currently shared between the tree and the file
> list, splitting in two subcases adds some crap to the code (ok, this
> is not the real doubt ;-) )
Actually, "Get revision/patch" and "External diff" shouldn't be in the
popup used in the tree, or at least they should only appear for the
files affected by the currently selected patch.
"External diff" may be a good candidate for the toolbar if you find out
how to feed a multi-file patch to kompare (I guess you'll need two
temporary directories populated with differing files).
> 2) The context menu is currently shared between the file list in main
> view and the file list in patch view. The file list in patch view, of
> course, does not need a double click, a single click is enough to
> select corresponding file's diff. In main view you currently need a
> single click _plus_ a 'p' key press to change the view. So we should
> add another subcase here.
Yes. It should be perfectly OK to have different menus for different
contexts.
> 3) It is true that double clicking on a revision switch to the patch
> view at top position (if no file is selected), but it's also true that
> you can select the file's related diff directly in patch view with a
> single click on the right column file list.
That's true. But I still find myself double clicking on the file in the
file list and expecting to see the patch for the file. It's very
natural.
If I see the list of the recent patches, I see the descriptions and the
affected files. If I'm interested to see what changed in the file, it's
only natural for me to double-click the corresponding entry in the list.
Full history of a file is a much more advanced operation, and it's not
something I need to do often while browsing recently merged changes.
> 4) Once a file is selected, as example with a single click, you can
> browse through rev list and the selection is preserved, it means that
> anytime you switch to patch view page the content will be _already_
> centered on the correct diff.
That's useful, but irrelevant.
> 5) Double clicking on a file name is currently the only way (without
> opening the menu) to show the file content tab, with your suggested
> change we will have two ways to switch to patch view and no one to
> switch to file view.
That's a good thing. That's called consistency. git is about patches,
so showing the patches should be the default whenever practical.
The file viewer is a great feature, and it should be discoverable, but
it doesn't mean it should pop up when users expect something else.
>
> 6) Selecting from the tree view is very slow if you have to search for
> the correct file, it is fast only if the file is already selected, but
> in this case is faster to press 'p' key ;-)
That's true, but we are talking about double click behavior. Every
reasonable person can learn shortcuts, but the software should work
predictably even if operated by the mouse.
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Linus Torvalds @ 2006-05-17 23:20 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vd5ecb688.fsf@assigned-by-dhcp.cox.net>
On Wed, 17 May 2006, Junio C Hamano wrote:
>
> If you give a pattern that would match two files but one of them
> were hidden by .gitignore, this approach would not help you
> much.
Correct. On the other hand, what could you do?
I think that the common case for that is literally something like
git add dirname/
or
git add file*
and it turns out that there are object files under the directory, or that
there's a file.c, file.h _and_ a already-compiled file.o file.
Under both of those circumstances (one pattern that matches multiple files
but ignores others - or several different patterns where _some_ of the
patterns are ignored), we actually do what I think is the only sane thing.
Namely to just silently add everything that makes sense to add.
> Even if we wanted to say something like "if the user explicitly
> tells us to add etc/mtab~ by spelling it out, we should ignore
> *~ entry in .gitignore", the shell expansion bites us because it
> is done before we get to what the user give us. We cannot
> distinguish that with the user typing etc/?tab* for example.
Right.
The only case that we cando anything at all about is really the case where
we didn't add anything at all, and then we might reasonably ask "do you
know what the heck you're doing".
That's kind of what my last patch did. It's a total special case, but it's
the _only_ special case that I can see that is at all relevant (ie in all
other cases it would just be annoying as _hell_ if we started talking
about how we're ignoring object files. Of _course_ we're ignoring them,
and that's why they are listed in .gitignore).
So I'd love to have the built-in "git add", but quite frankly, if you drop
that last patch as "too ugly to live", I certainly won't complain. I sent
it out more as a "we -could- do this" thing rather than anything more
serious.
Linus
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Junio C Hamano @ 2006-05-17 23:07 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0605171342370.10823@g5.osdl.org>
Linus Torvalds <torvalds@osdl.org> writes:
> Well, with the new-and-improved builtin "git add", you could probably do
> something like the appended (on top of my most recent patch).
>
> It says
>
> No added files - did you perhaps mean to do a 'git update-index'?
>
> whenever it finds that "git add" has ignored a file and not actually added
> anything. That, btw, can happen either because it refused to see the file
> in the first place (ie it was ignored), or because all the files listed
> were already added.
>
> In both cases the warning may or may not be sensible.
>
> Anyway, I dunno. I don't have any strong opinions on this.
If you give a pattern that would match two files but one of them
were hidden by .gitignore, this approach would not help you
much.
Even if we wanted to say something like "if the user explicitly
tells us to add etc/mtab~ by spelling it out, we should ignore
*~ entry in .gitignore", the shell expansion bites us because it
is done before we get to what the user give us. We cannot
distinguish that with the user typing etc/?tab* for example.
If somebody (Jakub, perhaps?) cares strong enough, we could show
by default "matched the pathspec but ignored by .gitignore"
paths with fprintf(stderr, "ignoring '%s'\n"), and have an
option -q to squelch it.
I do not have strong feeling on this, so I'll see if somebody
comes up with a better implementation.
^ permalink raw reply
* [RFC 6/5] Fix ref log parsing so it works properly.
From: Shawn Pearce @ 2006-05-17 22:34 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
The log parser was only ever matching the last log record due to
calling strtoul on "> 1136091609" rather than " 1136091609". Also
once a match for '@' has been found after the name of the ref there
is no point in looking for another '@' within the remaining text.
---
Uh yea, I found a couple of bugs. :-)
This applies on top of the other 5 patches (hence the 6/5).
refs.c | 2 +-
sha1_name.c | 1 +
2 files changed, 2 insertions(+), 1 deletions(-)
fbc7bf049255370f1611a5772c39d35422a81e24
diff --git a/refs.c b/refs.c
index 4c99e37..ae9825d 100644
--- a/refs.c
+++ b/refs.c
@@ -459,7 +459,7 @@ int read_ref_at(const char *ref, unsigne
c++;
if (c == logend || *c == '\n')
die("Log %s is corrupt.", logfile);
- date = strtoul(c, NULL, 10);
+ date = strtoul(c + 1, NULL, 10);
if (date <= at_time) {
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
diff --git a/sha1_name.c b/sha1_name.c
index 3ac3ab4..4376cb3 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -267,6 +267,7 @@ static int get_sha1_basic(const char *st
at_time = approxidate(date_spec);
free(date_spec);
len = at_mark;
+ break;
}
}
--
1.3.2.g7278
^ permalink raw reply related
* Re: [RFC 5/5] Support 'master@2 hours ago' syntax
From: Shawn Pearce @ 2006-05-17 22:32 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <20060517220613.GC30313@spearce.org>
Shawn Pearce <spearce@spearce.org> wrote:
> Junio C Hamano <junkio@cox.net> wrote:
> >
> > Also I wonder how much complexity would we suffer and how much
> > efficiency would we gain if we binary search the logdata (the
> > committer info is variable length, so you would need to resync
> > in each step).
>
> I thought about doing this but did not think it would be worth the
> effort (either developer to code or CPU to execute) at this point
> in time. I don't think users will be pulling refs from the log very
> often and if they are they will probably be pulling from recent time,
> not very far back. Thus starting at the end and walking back is
> probably "good enough".
>
> But if it proves to be too slow in practice I'm sure I can come up
> with a faster way to walk through the log. :-)
I just ran a test on my PowerBook: walking a 10,000 line log file and
extracting the very oldest commit along. Each hit on git-rev-parse
seems to took about 100 ms. Hardly worth worrying about for casual
use. Further git-rev-parse is taking 73 ms just to run '--verify
HEAD' so an extra 30 ms to read the 10k log is pretty much nothing.
[spearce@pb15 trash]$ wc -l .git/logs/refs/heads/master
10000 .git/logs/refs/heads/master
[spearce@pb15 trash]$ head -n 1 .git/logs/refs/heads/master
b943559a305bdd6bdee2cef6e5df2413c3d30a00 0000000000000000000000000000000000000000 A U Thor <example@example.com> 1136091600 -0500
[spearce@pb15 trash]$ perl -e 'print scalar(localtime shift),"\n"' 1136091600
Sun Jan 1 00:00:00 2006
[spearce@pb15 trash]$ time ../../git-rev-parse --verify HEAD@'300 days'
warning: Log .git/logs/refs/heads/master only goes back to Thu, 1 Jan 1970 00:00:00 +0000.
b943559a305bdd6bdee2cef6e5df2413c3d30a00
real 0m0.112s
user 0m0.029s
sys 0m0.023s
[spearce@pb15 trash]$ time ../../git-rev-parse --verify HEAD@'300 days'
warning: Log .git/logs/refs/heads/master only goes back to Thu, 1 Jan 1970 00:00:00 +0000.
b943559a305bdd6bdee2cef6e5df2413c3d30a00
real 0m0.105s
user 0m0.029s
sys 0m0.023s
--
Shawn.
^ permalink raw reply
* Re: [RFC 5/5] Support 'master@2 hours ago' syntax
From: Shawn Pearce @ 2006-05-17 22:06 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vbqtwhpum.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> wrote:
> Shawn Pearce <spearce@spearce.org> writes:
>
> + fprintf(stderr, "warning: Log %s only goes back to %s.\n",
> + logfile, show_rfc2822_date(date, tz));
> + return 0;
>
> I am not sure about this part. If the oldest log entry was 3
> hours ago, the second oldest 2 hours ago, we can tell during
> that one hour period the ref was at that point. If the user
> asked "ref as of four hours ago", and if the oldest log entry
> had old SHA1 that is not 0{40} (because the log was not enabled
> before that record), it might make more sense to give that back.
If I understand my own code here what I'm doing is walking back
in the log file, realizing I fell off the first line of it, then
loading the old ref from the first line. This is the oldest ref
I can find so I return it as a valid ref to the caller but I'm
printing out this warning to tell the user that the oldest point
in time I found in the log is effectively the update date as I have
no idea when that old sha1 first became the value of the ref.
So I think I'm doing what you are expecting here. The log will start
with the value in the ref at the time the log started, not 0{40}
and that value is what gets returned when we have this warning
come out.. That's the best anyone can expect...
> Also I wonder how much complexity would we suffer and how much
> efficiency would we gain if we binary search the logdata (the
> committer info is variable length, so you would need to resync
> in each step).
I thought about doing this but did not think it would be worth the
effort (either developer to code or CPU to execute) at this point
in time. I don't think users will be pulling refs from the log very
often and if they are they will probably be pulling from recent time,
not very far back. Thus starting at the end and walking back is
probably "good enough".
But if it proves to be too slow in practice I'm sure I can come up
with a faster way to walk through the log. :-)
--
Shawn.
^ permalink raw reply
* Make git-rev-list understand --tags/--branches/--remotes
From: Linus Torvalds @ 2006-05-17 21:44 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
We shouldn't add stuff to git-rev-parse without teaching git-rev-list and
all the other tools to do the same.
In fact, these days there is much less reason for git-rev-parse in the
first place: it's usually used to verify a particular reference, or to
just split the different argument types up from each other. Most tools
don't need or use it any more (eg "gitk" will just pass its arguments
directly to git-rev-list).
With this, you can now do (for example)
gitk HEAD --not --tags
to see all the work on all the main branch that hasn't been included in a
tagged version (replace HEAD with "--branches" to show all branches, of
course).
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---
diff --git a/revision.c b/revision.c
index 2294b16..1fc6725 100644
--- a/revision.c
+++ b/revision.c
@@ -470,11 +470,13 @@ static int handle_one_ref(const char *pa
return 0;
}
-static void handle_all(struct rev_info *revs, unsigned flags)
+typedef int (*ref_fn_t)(int (*)(const char *, const unsigned char *));
+
+static void handle_ref(ref_fn_t fn, struct rev_info *revs, unsigned flags)
{
all_revs = revs;
all_flags = flags;
- for_each_ref(handle_one_ref);
+ fn(handle_one_ref);
}
static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -614,7 +616,19 @@ int setup_revisions(int argc, const char
continue;
}
if (!strcmp(arg, "--all")) {
- handle_all(revs, flags);
+ handle_ref(for_each_ref, revs, flags);
+ continue;
+ }
+ if (!strcmp(arg, "--branches")) {
+ handle_ref(for_each_branch_ref, revs, flags);
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ handle_ref(for_each_tag_ref, revs, flags);
+ continue;
+ }
+ if (!strcmp(arg, "--remotes")) {
+ handle_ref(for_each_remote_ref, revs, flags);
continue;
}
if (!strcmp(arg, "--not")) {
^ permalink raw reply related
* Re: [RFC 5/5] Support 'master@2 hours ago' syntax
From: Shawn Pearce @ 2006-05-17 21:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Linus Torvalds, git
In-Reply-To: <7v7j4kec3h.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> wrote:
> Linus Torvalds <torvalds@osdl.org> writes:
>
> > On Wed, 17 May 2006, Junio C Hamano wrote:
> >>
> >> This does not allow '2006-05-17 00:00:00' as the timespec, and
> >> the documentation carefully avoids giving that example, but I
> >> think it is better to spell that limitation out.
> >
> > It doesn't? The "approxidate()" function should handle any reasonable date
> > specifier, and the above is certainly more than reasonable.
> >
> > Why doesn't approxidate handle it?
>
> The way I read the code is that get_sha1() would first do its
> magic at the first colon and feeds get_sha1_1() with prefix up
> to the first colon. This gets passed down to get_sha1_basic()
> and what approxidate() is fed is the suffix of that prefix. It
> ends up seeing stuff between '@' and ':'. I.e.
>
> "master@2006-05-17 00:00:00:cache.h"
>
> would ask for "00:00:cache.h" file in the "master" branch as of
> timestamp "2006-05-17 00".
Good catch. I'll see if I can deal with it later; probably early
tomorrow morning before work. It may just come down to documenting
this particular case as ambiguous and sure to parse the way you
did not mean it to. :-)
I tested a bunch of other date formats but not the basic ISO. Argh.
I'll send a test case soon for the expression parsing here, to be
sure we pull stuff from the log as expected as well as parse the
expression in a consistent way between releases. :-)
--
Shawn.
^ permalink raw reply
* Re: git-add + git-reset --hard = Arrrggh!
From: Shawn Pearce @ 2006-05-17 21:35 UTC (permalink / raw)
To: Alex Riesen; +Cc: git
In-Reply-To: <81b0412b0605170722u15702301p2565e8ac29a5a0da@mail.gmail.com>
Alex Riesen <raa.lkml@gmail.com> wrote:
> On 5/17/06, Shawn Pearce <spearce@spearce.org> wrote:
> >All I can say is I'm very happy that update-index does a lot more
> >than just update the index. I was easily able to find the deleted
> >test by finding the most recently modified object in my .git/objects
> >directory and pulling it back out with git cat-file. :-)
> >
>
> Maybe git-lost-found would help here?
Thanks! I did that the hard way with git fsck-objects only to find
I actually had a lot of dangling objects. Luckily the most recent
one was the one I was looking for so a quick pipe through perl and
ls -t found it quite quickly.
What would have really helped me was just using GIT properly
rather than slamming something in fast with `git reset --hard`.
Somehow that option has become part of my finger feel when using
reset and that's just not right. :-)
--
Shawn.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox