* Re: [PATCH 1/2] Handle branch names with slashes
From: Junio C Hamano @ 2006-05-16 6:48 UTC (permalink / raw)
To: Karl Hasselström; +Cc: git
In-Reply-To: <20060516063541.GA11218@backpacker.hemma.treskal.com>
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
* [PATCH 2/2] Tests for branch names with slashes
From: Karl Hasselström @ 2006-05-16 6:37 UTC (permalink / raw)
To: Catalin Marinas; +Cc: Wartan Hachaturow, git
In-Reply-To: <20060515105810.GA27077@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>
---
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
* [PATCH 1/2] Handle branch names with slashes
From: Karl Hasselström @ 2006-05-16 6:35 UTC (permalink / raw)
To: Catalin Marinas; +Cc: Wartan Hachaturow, git
In-Reply-To: <20060515105810.GA27077@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>
---
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
* Re: [PATCH] Update the documentation for git-merge-base
From: Junio C Hamano @ 2006-05-16 6:13 UTC (permalink / raw)
To: Fredrik Kuivinen; +Cc: git
In-Reply-To: <20060516055815.GA4572@c165.ib.student.liu.se>
Fredrik Kuivinen <freku045@student.liu.se> writes:
> Is the code guaranteed to return a least common ancestor? If that is
> the case we should probably mention it in the documentation.
Unfortunately, no, if you mean by "least common" closest to the
tips.
See the big illustration at the top of the source for how you
can construct pathological case to defeat an attempt to
guarantee such. --all guarantees that the output contains all
interesting ones, but does not guarantee the output has no
suboptimal merge bases.
^ permalink raw reply
* [PATCH] Update the documentation for git-merge-base
From: Fredrik Kuivinen @ 2006-05-16 5:58 UTC (permalink / raw)
To: junkio; +Cc: git
Signed-off-by: Fredrik Kuivinen <freku045@student.liu.se>
---
Is the code guaranteed to return a least common ancestor? If that is
the case we should probably mention it in the documentation.
Documentation/git-merge-base.txt | 18 ++++++++++++++----
1 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index d1d56f1..6099be2 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -8,16 +8,26 @@ git-merge-base - Finds as good a common
SYNOPSIS
--------
-'git-merge-base' <commit> <commit>
+'git-merge-base' [--all] <commit> <commit>
DESCRIPTION
-----------
-"git-merge-base" finds as good a common ancestor as possible. Given a
-selection of equally good common ancestors it should not be relied on
-to decide in any particular way.
+
+"git-merge-base" finds as good a common ancestor as possible between
+the two commits. That is, given two commits A and B 'git-merge-base A
+B' will output a commit which is reachable from both A and B through
+the parent relationship.
+
+Given a selection of equally good common ancestors it should not be
+relied on to decide in any particular way.
The "git-merge-base" algorithm is still in flux - use the source...
+OPTIONS
+-------
+--all::
+ Output all common ancestors for the two commits instead of
+ just one.
Author
------
^ permalink raw reply related
* What's in git.git
From: Junio C Hamano @ 2006-05-16 5:30 UTC (permalink / raw)
To: git
* The 'maint' branch produced the v1.3.3 release I announced
just a minute ago.
* The 'master' branch has these since the last announcement.
- 64-bit (especially BE) fix for pack-objects (Dennis Stosberg)
- Porting issues (Dennis Stosberg, Ben Clifford)
- send-email updates (Eric Wong)
- built-in "grep" (Linus and me)
- "reset --hard" simplification (Linus and me)
- configuration file syntax updates (Linus)
- cvsserver updates (Martin Langhoff, Martyn Smith)
- delta generation fix (Nicolas Pitre)
- "git commit" novice usability fix (Sean Estabrooks)
* The 'next' branch, in addition, has these.
- "diff revA:path1 revB:path2" fix
When two blobs are given, it produced diff in reverse by
mistake ("setup_revisions()" left the parsed objects in
reverse order, and the caller forgot to reverse it).
This is trivial and ready -- I just haven't got around to
merging it up.
- "rebase" help text updates (Sean Estabrooks)
Ready.
- "diff --summary" (Sean Estabrooks)
I haven't really read the code yet, but it is low impact and
should be ready.
- strip leading tags/ from "git tag -l" output (Sean Estabrooks)
"git tag -l" as I wrote it originally stupidly left leading tags/
in its output for all tags. This removes it, and I think it
is a sensible thing to do.
- move remotes/ to config (Johannes)
Now configuration syntax discussion is settled, thanks to
Linus, we can start discussing per-branch attribute
semantics. This series is about the other half of the story.
I think it is ready as it is, if we are not going to change
the semantics of "remote"; except some people seem to want to
reorganize the way per-branch property and remotes interact
with each other.
- Further optimiation of pack-object (Nicolas Pitre)
Testing.
- "apply --cached"
This allows "git apply" to apply a patch to the index without
touching the working tree. It is handy to prepare a tree to
use in 3-way fallback, and updated "git am" takes advantage
of it. I am planning to use it for stash/unstash.
- built-in format-patch (Johannes)
I think this is almost ready to supersede the script version,
except that this does not do attachments.
We need to do RFC2047 for headers as well. I'd rather do it
in this version than fix the script version.
- cache-tree with read-tree/write-tree --prefix
I haven't made any progress on this one, but haven't been
bitten by it either, so it is a good sign.
^ permalink raw reply
* [ANNOUNCE] GIT 1.3.3
From: Junio C Hamano @ 2006-05-16 4:49 UTC (permalink / raw)
To: git; +Cc: linux-kernel
The latest maintenance release GIT 1.3.3 is available at the
usual places:
http://www.kernel.org/pub/software/scm/git/
git-1.3.3.tar.{gz,bz2} (tarball)
RPMS/$arch/git-*-1.3.3-1.$arch.rpm (RPM)
This contains two notable non-fixes:
(1) Future-proofing configuration file syntax by Linus.
Nothing in 1.3.X series takes advantage of it, but it is
there so 1.3.3 would not barf in a repository that you
previously used later versions of git to manipulate its
configuration file.
(2) core.prefersymlinkrefs configuration can be set in the
configuration file while bisecting a project that wants to
use .git/HEAD symbolic link in its historical version
(notably Linux kernel around January this year).
----------------------------------------------------------------
Changes since v1.3.2 are as follows:
Ben Clifford:
include header to define uint32_t, necessary on Mac OS X
Dennis Stosberg:
Fix git-pack-objects for 64-bit platforms
Fix compilation on newer NetBSD systems
Dmitry V. Levin:
Separate object name errors from usage errors
Eric Wong:
apply: fix infinite loop with multiple patches with --index
Install git-send-email by default
Johannes Schindelin:
repo-config: trim white-space before comment
Junio C Hamano:
core.prefersymlinkrefs: use symlinks for .git/HEAD
repo-config: document what value_regexp does a bit more clearly.
Fix repo-config set-multivar error return path.
Documentation: {caret} fixes (git-rev-list.txt)
checkout: use --aggressive when running a 3-way merge (-m).
Fix pack-index issue on 64-bit platforms a bit more portably.
Linus Torvalds:
Fix "git diff --stat" with long filenames
revert/cherry-pick: use aggressive merge.
git config syntax updates
Martin Waitz:
clone: keep --reference even with -l -s
repack: honor -d even when no new pack was created
Matthias Lederhofer:
core-tutorial.txt: escape asterisk
Pavel Roskin:
Release config lock if the regex is invalid
Sean Estabrooks:
Fix for config file section parsing.
Another config file parsing fix.
Ensure author & committer before asking for commit message.
Yakov Lerner:
read-cache.c: use xcalloc() not calloc()
^ permalink raw reply
* Find cheap prescriptions on the internet pharmacy!
From: Horace Cole @ 2006-05-16 3:35 UTC (permalink / raw)
To: git
The next generation online pharmacy.
http://rfvhrec.musurvrrajrc444x944x94mm.tullianhf.com/?zno
^ permalink raw reply
* Pickaxe usage question -- only matching on added string
From: Martin Langhoff @ 2006-05-16 3:37 UTC (permalink / raw)
To: git
Documentation for diffcore-pickaxe (in Documentation/diffcore.txt) says:
When diffcore-pickaxe is in use, it checks if there are
filepairs whose "original" side has the specified string and
whose "result" side does not. Such a filepair represents "the
string appeared in this changeset". It also checks for the
opposite case that loses the specified string.
Now, is there a way to get diffcore to match only on 'added' (or on
'removed', for that matter)? I am tring to identify commtis that added
patches to a project, and I seem to be getting matches that add and
remove when I do:
git-whatchanged -p -C -S"\t" master
cheers,
martin
^ permalink raw reply
* Re: Fix silly typo in new builtin grep
From: Linus Torvalds @ 2006-05-16 3:32 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vu07qfyj0.fsf@assigned-by-dhcp.cox.net>
On Mon, 15 May 2006, Junio C Hamano wrote:
> >
> > (In fact, I would say that doing the above command in just 4 seconds is
> > damn impressive - it's a large code-base, and v2.6.13 is several months,
> > and over 20 _thousand_ revisions ago).
>
> That is a BS praise and you know it ;-). You do not have delta
> chains that are 20k long, so grepping from the tree 10 revs ago
> and from the tree 20k revs ago would not make a difference.
Oh, I agree. I meant in a "general version-control sense". I doubt a lot
of other version-control systems could do it. Git can, exactly because
it's whole-file based, and our deltas are limited.
So it's not that "builtin-grep" is wonderful. It's that _git_ is
wonderful, and the builtin-grep just shows one of the end results.
That's why we have killer features. To show off.
(That said, git will slow down a tad too - the pack-file access won't be
as optimized for an old version tree, and so you'll seek around some more
for the cold-cache case).
Linus
^ permalink raw reply
* Re: Fix silly typo in new builtin grep
From: Linus Torvalds @ 2006-05-16 3:27 UTC (permalink / raw)
To: Morten Welinder; +Cc: Junio C Hamano, Git Mailing List
In-Reply-To: <118833cc0605151910s7619ddf0x8f014adba2a1eba5@mail.gmail.com>
On Mon, 15 May 2006, Morten Welinder wrote:
>
> If I read the code right, it calls regexec for every single character
> on every single line. No wonder that takes a while! Just call it
> once and it'll search for its match quite nicely.
No, it calls it once per pattern per line.
But yes, it calls it once per line, instead of calling it on some bigger
boundary. Partly because of the line-based output, partly probably because
regexec() is not actually amenable to a "<buffer,size>" kind of usage, but
is based on NUL-terminated strings.
> 1. If the pattern contains no regexp characters (and that is very
> common), do a strstr.
>
> 2. If the pattern must start with a specific character, search for that
> by itself.
Yeah, we could do some simple stuff, and see if it helps..
Linus
^ permalink raw reply
* Re: Fix silly typo in new builtin grep
From: Junio C Hamano @ 2006-05-16 3:18 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0605151743360.3866@g5.osdl.org>
Linus Torvalds <torvalds@osdl.org> writes:
> The "-F" flag apparently got mis-translated due to some over-eager
> copy-paste work into a duplicate "-H" when using the external grep.
Thanks. I've pushed it out to "master", along with some other
stuff.
> Me likee the new built-in grep. The ability to say
>
> git grep __make_request v2.6.13 -- '*.c'
>
> to grep for it in a specific version is well worth the fact that it
> obviously ends up being slower than grepping in the currently checked-out
> tree. It's doing a hell of a lot more, but despite that it's not at all
> that slow.
>
> (In fact, I would say that doing the above command in just 4 seconds is
> damn impressive - it's a large code-base, and v2.6.13 is several months,
> and over 20 _thousand_ revisions ago).
That is a BS praise and you know it ;-). You do not have delta
chains that are 20k long, so grepping from the tree 10 revs ago
and from the tree 20k revs ago would not make a difference.
It _would_ be impressive to CVS folks, but even there each path
would not have 20k revisions. The kernel patches tend to touch
3 paths per patch on average, so 60k changes over 18k files
distributed unevenly -- my guess (I could count but haven't) is
probably 200 revisions at most for most frequently touched file.
^ permalink raw reply
* Re: [PATCH] simple euristic for further free packing improvements
From: Nicolas Pitre @ 2006-05-16 2:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v4pzqhh3t.fsf@assigned-by-dhcp.cox.net>
On Mon, 15 May 2006, Junio C Hamano wrote:
> Nicolas Pitre <nico@cam.org> writes:
>
> > @@ -1038,8 +1038,8 @@ static int try_delta(struct unpacked *tr
> >
> > /* Now some size filtering euristics. */
> > size = trg_entry->size;
> > - max_size = size / 2 - 20;
> > - if (trg_entry->delta)
> > + max_size = (size/2 - 20) / (src_entry->depth + 1);
> > + if (trg_entry->delta && trg_entry->delta_size <= max_size)
> > max_size = trg_entry->delta_size-1;
> > src_size = src_entry->size;
> > sizediff = src_size < size ? size - src_size : 0;
>
> At the first glance, this seems rather too agressive. It makes
> me wonder if it is a good balance to penalize the second
> generation base by requiring it to produce a small delta that is
> at most half as we normally would (and the third generation a
> third), or maybe the penalty should kick in more gradually, like
> e.g. ((max_depth * 2 - src_entry->depth) / (max_depth * 2).
>
> Having said that, judging from your past patches, I learned to
> trust that you have tried tweaking this part and settled on this
> simplicity and elegance, so I'll take the patch as is -- if
> somebody wants to play with it that can always be done to
> further improve things.
Actually I didn't play with that part that much. The only thing I tried
besides this version was (size - 20) / (src_entry->depth + 1) but it
produced larger packs than the current version.
So I thought it was better to provide a simple initial rule and leave
possible improvements for later.
Nicolas
^ permalink raw reply
* Re: Fix silly typo in new builtin grep
From: Morten Welinder @ 2006-05-16 2:10 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Junio C Hamano, Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0605151801100.3866@g5.osdl.org>
If I read the code right, it calls regexec for every single character
on every single line. No wonder that takes a while! Just call it
once and it'll search for its match quite nicely.
If that's not enough, the two obvious optimizations are...
1. If the pattern contains no regexp characters (and that is very
common), do a strstr.
2. If the pattern must start with a specific character, search for that
by itself.
M.
^ permalink raw reply
* Re: [PATCH] simple euristic for further free packing improvements
From: Junio C Hamano @ 2006-05-16 1:51 UTC (permalink / raw)
To: Nicolas Pitre; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0605151129540.18071@localhost.localdomain>
Nicolas Pitre <nico@cam.org> writes:
> Given that the early eviction of objects with maximum delta depth
> may exhibit bad packing on its own, why not considering a bias against
> deep base objects in try_delta() to mitigate that bad behavior.
This is really a good stuff. Thanks. Oh, and thanks for
noticing my puzzlement expressed with "#if 0" ;-).
> @@ -1038,8 +1038,8 @@ static int try_delta(struct unpacked *tr
>
> /* Now some size filtering euristics. */
> size = trg_entry->size;
> - max_size = size / 2 - 20;
> - if (trg_entry->delta)
> + max_size = (size/2 - 20) / (src_entry->depth + 1);
> + if (trg_entry->delta && trg_entry->delta_size <= max_size)
> max_size = trg_entry->delta_size-1;
> src_size = src_entry->size;
> sizediff = src_size < size ? size - src_size : 0;
At the first glance, this seems rather too agressive. It makes
me wonder if it is a good balance to penalize the second
generation base by requiring it to produce a small delta that is
at most half as we normally would (and the third generation a
third), or maybe the penalty should kick in more gradually, like
e.g. ((max_depth * 2 - src_entry->depth) / (max_depth * 2).
Having said that, judging from your past patches, I learned to
trust that you have tried tweaking this part and settled on this
simplicity and elegance, so I'll take the patch as is -- if
somebody wants to play with it that can always be done to
further improve things.
^ permalink raw reply
* Re: Fix silly typo in new builtin grep
From: Linus Torvalds @ 2006-05-16 1:07 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0605151743360.3866@g5.osdl.org>
On Mon, 15 May 2006, Linus Torvalds wrote:
>
> Me likee the new built-in grep. The ability to say
>
> git grep __make_request v2.6.13 -- '*.c'
>
> to grep for it in a specific version is well worth the fact that it
> obviously ends up being slower than grepping in the currently checked-out
> tree. It's doing a hell of a lot more, but despite that it's not at all
> that slow.
Side note: it looks like the version generation really isn't much of a
cost. Grepping in v2.6.13 isn't really noticeably slower than the
"pre-external grep" was for grepping in the checked-out file tree.
So it looks like we _could_ improve the grepping of specific versions
noticeably if we were to have a better regex library that was as optimized
as what the external GNU grep seems to do. The actual revision data
generation doesn't seem to be the biggest cost, and at least in _theory_
we could probably speed things up by a factor of two with a faster regex
library.
That's good to keep in mind. It may be that the glibc regexp is just not
very good, but quite frankly, I would personally not be surprised if it's
better than most (ie windows, for example).
Linus
^ permalink raw reply
* Fix silly typo in new builtin grep
From: Linus Torvalds @ 2006-05-16 0:54 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
The "-F" flag apparently got mis-translated due to some over-eager
copy-paste work into a duplicate "-H" when using the external grep.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---
Me likee the new built-in grep. The ability to say
git grep __make_request v2.6.13 -- '*.c'
to grep for it in a specific version is well worth the fact that it
obviously ends up being slower than grepping in the currently checked-out
tree. It's doing a hell of a lot more, but despite that it's not at all
that slow.
(In fact, I would say that doing the above command in just 4 seconds is
damn impressive - it's a large code-base, and v2.6.13 is several months,
and over 20 _thousand_ revisions ago).
And now it doesn't have any performance downsides, so it's all upside.
Good job. Pls merge into mainline, I think the "grep per revision" is a
killer feature.
diff --git a/builtin-grep.c b/builtin-grep.c
index 3d6e515..66111de 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -455,7 +455,7 @@ static int external_grep(struct grep_opt
push_arg("grep");
push_arg("-H");
if (opt->fixed)
- push_arg("-H");
+ push_arg("-F");
if (opt->linenum)
push_arg("-n");
if (opt->regflags & REG_EXTENDED)
^ permalink raw reply related
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Martin Langhoff @ 2006-05-15 22:07 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ryan Anderson, git
In-Reply-To: <7vhd3rgfey.fsf@assigned-by-dhcp.cox.net>
On 5/16/06, Junio C Hamano <junkio@cox.net> wrote:
> Ryan Anderson <ryan@michonline.com> writes:
>
> > I think, in practice, that /usr/lib/sendmail will exist anywhere you hve
> > something running on port 25, at least on unixy machines.
...
> exim, postfix and friends?
/usr/sbin/sendmail is there for any current MTA, so if qmail, exim,
postfix, sendmail and all MTAs I can think of install a working
sendmail binary.
Newish desktop-targetted distros (and MacOSX) are leaning towards not
running an MTA on port 25 any more unless you ask them for it. They'll
do crontab-driven queue flushes, so the messages you feed to
/usr/sbin/sendmail will be sent a few minutes later.
That is why IMHO /usr/sbin/sendmail is a better default than
localhost:25, but I can live with either ;-)
> I used to know somebody who port-forwarded 25/tcp to central
> smtp server from smaller machines in her intranet installation,
I can understand that, but I think the changes in unixy distros
mentioned above make it unnecessary today.
cheers,
martin
^ permalink raw reply
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Ryan Anderson @ 2006-05-15 21:52 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Ryan Anderson, git
In-Reply-To: <7vhd3rgfey.fsf@assigned-by-dhcp.cox.net>
On Mon, May 15, 2006 at 02:13:25PM -0700, Junio C Hamano wrote:
> Ryan Anderson <ryan@michonline.com> writes:
>
> > I think, in practice, that /usr/lib/sendmail will exist anywhere you hve
> > something running on port 25, at least on unixy machines. In my
> > searches at an old job, that appeared to be the canonical place to call
> > sendmail from, and every MTA appears to provide an appropriate binary
> > there.
> >
> > So, I'm not overly worried about it.
>
> exim, postfix and friends?
At least as packaged by Debian and Ubuntu, exim and postfix both fit
that rule.
It's just that there is no other canonical name for the binary that
provides the functionality, so everyone steals the name.
> I used to know somebody who port-forwarded 25/tcp to central
> smtp server from smaller machines in her intranet installation,
> but I would say that is rare. I am not worried about it either;
> I just wanted to make sure _somebody_ thought the potential
> issues through and agreed with the change the patch makes.
Fair enough.
^ permalink raw reply
* [RFC, PATCH] Teach revision.c about renames
From: Fredrik Kuivinen @ 2006-05-15 20:37 UTC (permalink / raw)
To: git; +Cc: junkio
Hi,
The attached patch is a work in progress to add support for rename
following to the revision walking code in revision.c.
When the rename following is enabled (with the new '--renames' flag)
any pathspecs given on the command line are interpreted as
filenames. Those filenames are then followed through the commit graph.
For example, 'git log --renames -- git-fetch.sh' will show us the
history of 'git-fetch.sh' till commit
215a7ad1ef790467a4cd3f0dcffbd6e5f04c38f7 where it was renamed from
git-fetch-script, we then get the history of git-fetch-script instead.
This works with all commands that use revision.c. So 'gitk --renames
git-fetch.sh' and 'git whatchanged --renames git-fetch.sh' also
work. Multiple filenames are supposed to work (e.g., 'gitk --renames
-- git-fetch.sh Documentation/technical/pack-protocol.txt')
The patch currently have a few issues (that I am aware of):
* A linked list is used to keep track of which filenames that we care
about for each commit. A more efficient data structure may be a good
idea.
* Memory leaks. In particular the struct path_lists are never freed.
* Pathspecs that aren't filenames cause assertion failures. (for
example 'git log --renames -- Documentation/') I don't really know
what we should do in this case. Should we interpret 'Documentation/'
as if the user supplied us with all the files in the Documentation
directory and then track those? Or, should we show commits that
change anything under Documentation/ (i.e., the old non-rename
following behaviour) and additionally track files that are moved
from Documentation/ to some other location. Yet another alternative
is to disallow non-filenames. Thoughts?
* Is '--renames' a good name for the flag? I considered
'--follow-renames' first, but found it a bit too long.
Any comments would be greatly appreciated.
- Fredrik
Signed-off-by: Fredrik Kuivinen <freku045@student.liu.se>
---
log-tree.c | 7 +
revision.c | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
revision.h | 30 +++++
3 files changed, 381 insertions(+), 3 deletions(-)
diff --git a/log-tree.c b/log-tree.c
index b90ba67..8ed500a 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -84,11 +84,12 @@ int log_tree_diff_flush(struct rev_info
}
static int diff_root_tree(struct rev_info *opt,
- const unsigned char *new, const char *base)
+ struct commit* commit, const char *base)
{
int retval;
void *tree;
struct tree_desc empty, real;
+ const unsigned char* new = commit->object.sha1;
tree = read_object_with_reference(new, tree_type, &real.size, NULL);
if (!tree)
@@ -97,6 +98,7 @@ static int diff_root_tree(struct rev_inf
empty.buf = "";
empty.size = 0;
+ rev_setup_diffopt_paths(opt, commit, NULL);
retval = diff_tree(&empty, &real, base, &opt->diffopt);
free(tree);
log_tree_diff_flush(opt);
@@ -129,7 +131,7 @@ static int log_tree_diff(struct rev_info
parents = commit->parents;
if (!parents) {
if (opt->show_root_diff)
- diff_root_tree(opt, sha1, "");
+ diff_root_tree(opt, commit, "");
return !opt->loginfo;
}
@@ -148,6 +150,7 @@ static int log_tree_diff(struct rev_info
for (;;) {
struct commit *parent = parents->item;
+ rev_setup_diffopt_paths(opt, commit, parent);
diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
log_tree_diff_flush(opt);
diff --git a/revision.c b/revision.c
index 2294b16..523ab29 100644
--- a/revision.c
+++ b/revision.c
@@ -1,12 +1,17 @@
+#include <assert.h>
+
#include "cache.h"
#include "tag.h"
#include "blob.h"
#include "tree.h"
#include "commit.h"
#include "diff.h"
+#include "diffcore.h"
#include "refs.h"
#include "revision.h"
+#define DEBUG 0
+
static char *path_name(struct name_path *path, const char *name)
{
struct name_path *p;
@@ -507,6 +512,320 @@ static int add_parents_only(struct rev_i
return 1;
}
+/* Rename following code */
+const struct path_list* path_list_find(const struct path_list* list,
+ const char* path)
+{
+ for(; list; list = list->next) {
+ if (!strcmp(path, list->item))
+ return list;
+ }
+
+ return NULL;
+}
+
+struct path_list* path_list_insert(char *item, struct path_list **list_p)
+{
+ struct path_list *new_list = xmalloc(sizeof(struct path_list));
+ new_list->item = item;
+ new_list->next = *list_p;
+ *list_p = new_list;
+ return new_list;
+}
+
+void path_list_print(FILE* file, struct path_list* list)
+{
+ for(; list; list = list->next)
+ fprintf(file, "%s ", list->item);
+ fputc('\n', file);
+}
+
+void free_path_list(struct path_list *list)
+{
+ while (list) {
+ struct path_list *temp = list;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+const char** convert_to_pathspec(struct path_list* l1, struct path_list* l2)
+{
+ struct path_list* l;
+ const char** ret;
+ int i, len = 0;
+
+ for(l = l1; l; l = l->next, len++)
+ ;
+ for(l = l2; l; l = l->next, len++)
+ ;
+
+ ret = xmalloc((len+1)*sizeof(char*));
+ for(i = 0; l1; l1 = l1->next, i++)
+ ret[i] = l1->item;
+ for(; l2; l2 = l2->next, i++)
+ ret[i] = l2->item;
+
+ ret[len] = NULL;
+ return ret;
+}
+
+static struct rev_commit_info* get_util(struct commit *commit)
+{
+ struct rev_commit_info *util = commit->object.util;
+
+ if (util)
+ return util;
+
+ util = xmalloc(sizeof(struct rev_commit_info));
+ util->paths = NULL;
+ util->topo_data = NULL;
+ commit->object.util = util;
+ return util;
+}
+
+void rev_setup_diffopt_paths(struct rev_info* revs,
+ struct commit* commit,
+ struct commit* parent)
+{
+ const char** pathspec =
+ convert_to_pathspec(get_util(commit)->paths,
+ parent ? get_util(parent)->paths : NULL);
+
+ /* FIXME we can't free revs->diffopt.paths here because the
+ * initial value of that variable isn't necessarily allocated
+ * by malloc.
+ *
+ * free(revs->diffopt.paths); */
+ diff_tree_release_paths(&revs->diffopt);
+ diff_tree_setup_paths(pathspec, &revs->diffopt);
+}
+
+
+static void topo_setter(struct commit* c, void* data)
+{
+ struct rev_commit_info* util = c->object.util;
+ util->topo_data = data;
+}
+
+static void* topo_getter(struct commit* c)
+{
+ struct rev_commit_info* util = c->object.util;
+ return util->topo_data;
+}
+
+
+static int same_tree_as_empty_paths(struct rev_info *revs, struct tree* t1,
+ struct path_list* paths)
+{
+ int ret;
+
+ const char** pathspec = convert_to_pathspec(paths, NULL);
+ diff_tree_setup_paths(pathspec, &revs->pruning);
+ ret = rev_same_tree_as_empty(revs, t1);
+ diff_tree_release_paths(&revs->pruning);
+ free(pathspec);
+ return ret;
+}
+
+
+static struct path_list* file_removals;
+static void file_add_remove_ren(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ if (DEBUG)
+ printf("%c base: '%s' path: '%s'\n", addremove, base, path);
+
+ if (addremove == '-') {
+ char* p = xmalloc(strlen(base) + strlen(path) + 1);
+ strcpy(p, base);
+ strcat(p, path);
+ path_list_insert(p, &file_removals);
+ tree_difference = REV_TREE_NEW;
+ } else {
+ assert(0);
+ }
+}
+
+static void file_change_ren(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ if (tree_difference == REV_TREE_SAME)
+ tree_difference = REV_TREE_DIFFERENT;
+}
+
+static int compare_tree_paths(struct rev_info* revs,
+ struct commit* parent, struct commit* commit,
+ struct path_list** added)
+{
+ const char** pathspec;
+ struct diff_options dopts;
+
+ diff_setup(&dopts);
+ dopts.recursive = 1;
+ dopts.add_remove = file_add_remove_ren;
+ dopts.change = file_change_ren;
+ dopts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+ pathspec = convert_to_pathspec(get_util(commit)->paths, NULL);
+ diff_tree_setup_paths(pathspec, &dopts);
+
+ if (diff_setup_done(&dopts) < 0)
+ die("diff_setup_done failed");
+
+ file_removals = NULL;
+ tree_difference = REV_TREE_SAME;
+
+ diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
+ "", &dopts);
+
+ diff_flush(&dopts);
+ diff_tree_release_paths(&dopts);
+ free(pathspec);
+
+ *added = file_removals;
+ return tree_difference;
+}
+
+static struct path_list* find_renames(struct commit* commit,
+ struct commit* parent,
+ struct path_list* paths)
+{
+ int i;
+ struct diff_options dopts;
+ struct path_list* ret = NULL;
+
+ if (DEBUG) {
+ printf("find_renames commit: %s ",
+ sha1_to_hex(commit->object.sha1));
+ puts(sha1_to_hex(parent->object.sha1));
+ printf("rename from paths: ");
+ path_list_print(stdout, paths);
+ }
+
+ diff_setup(&dopts);
+ dopts.recursive = 1;
+ dopts.detect_rename = DIFF_DETECT_RENAME;
+ dopts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+ if (diff_setup_done(&dopts) < 0)
+ die("diff_setup_done failed");
+
+ diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
+ "", &dopts);
+ diffcore_std(&dopts);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+
+ if (0 && p->status == 'R' && DEBUG)
+ printf("rename %s -> %s\n", p->one->path, p->two->path);
+
+ if (p->status == 'R' && path_list_find(paths, p->one->path)) {
+ if (DEBUG)
+ printf("rename %s -> %s\n",
+ p->one->path, p->two->path);
+ path_list_insert(strdup(p->two->path), &ret);
+ }
+ }
+ diff_flush(&dopts);
+
+ if (DEBUG) {
+ printf("rename result: ");
+ path_list_print(stdout, ret);
+ }
+ return ret;
+}
+
+static struct path_list* rewrite_paths(struct rev_info *revs,
+ struct commit* commit,
+ struct commit* parent,
+ struct path_list* removed)
+{
+ struct path_list* new_paths = find_renames(commit, parent, removed);
+ struct path_list* cpaths = get_util(commit)->paths;
+ for(; cpaths; cpaths = cpaths->next) {
+ if (!path_list_find(removed, cpaths->item))
+ path_list_insert(cpaths->item, &new_paths);
+ }
+ return new_paths;
+}
+
+static void simplify_commit_rename(struct rev_info *revs,
+ struct commit *commit)
+{
+ struct commit_list **pp, *parent;
+
+ if (!commit->tree)
+ return;
+
+ {
+ struct rev_commit_info* util = get_util(commit);
+ if (!util->paths) {
+ util->paths = revs->initial_paths;
+ if (DEBUG)
+ printf("NULL paths\n");
+ }
+ }
+
+ if (!commit->parents) {
+ struct rev_commit_info* util = commit->object.util;
+ if (!same_tree_as_empty_paths(revs, commit->tree,
+ util->paths))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
+ struct commit *p = parent->item;
+ struct path_list* removed;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
+ parse_commit(p);
+ switch (compare_tree_paths(revs, p, commit, &removed)) {
+ case REV_TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ get_util(p)->paths = get_util(commit)->paths;
+ return;
+
+ case REV_TREE_NEW:
+ {
+ struct path_list* new_paths =
+ rewrite_paths(revs, commit, p, removed);
+ if (new_paths) {
+ struct rev_commit_info* putil = get_util(p);
+ if (!putil->paths)
+ putil->paths = new_paths;
+ } else {
+ *pp = parent->next;
+ continue;
+ }
+ }
+
+ /* fallthrough */
+ case REV_TREE_DIFFERENT:
+ pp = &parent->next;
+ if (!get_util(p)->paths)
+ get_util(p)->paths = get_util(commit)->paths;
+ continue;
+ }
+ die("bad tree compare for commit %s",
+ sha1_to_hex(commit->object.sha1));
+ }
+ commit->object.flags |= TREECHANGE;
+}
+
void init_revisions(struct rev_info *revs)
{
memset(revs, 0, sizeof(*revs));
@@ -742,6 +1061,11 @@ int setup_revisions(int argc, const char
revs->full_diff = 1;
continue;
}
+ if (!strcmp(arg, "--renames")) {
+ revs->follow_renames = 1;
+ continue;
+ }
+
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
revs->diff = 1;
@@ -841,6 +1165,29 @@ int setup_revisions(int argc, const char
(revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
}
+ if (revs->follow_renames && revs->prune_data) {
+ if (revs->remove_empty_trees)
+ die("--renames and --remove-empty are currently "
+ "mutually exclusive");
+ revs->prune_fn = simplify_commit_rename;
+ revs->topo_setter = topo_setter;
+ revs->topo_getter = topo_getter;
+ revs->limited = 1;
+
+ {
+ char** p;
+ for(p = revs->prune_data; *p; p++) {
+ if (DEBUG)
+ printf("filename: %s\n", *p);
+ path_list_insert(strdup(*p),
+ &revs->initial_paths);
+ }
+ }
+
+ if (DEBUG)
+ printf("prefix: %s\n", revs->prefix);
+ }
+
revs->diffopt.abbrev = revs->abbrev;
diff_setup_done(&revs->diffopt);
diff --git a/revision.h b/revision.h
index 48d7b4c..e73a327 100644
--- a/revision.h
+++ b/revision.h
@@ -39,7 +39,8 @@ struct rev_info {
limited:1,
unpacked:1,
boundary:1,
- parents:1;
+ parents:1,
+ follow_renames:1;
/* Diff flags */
unsigned int diff:1,
@@ -68,6 +69,9 @@ struct rev_info {
struct diff_options diffopt;
struct diff_options pruning;
+ /* Rename following */
+ struct path_list* initial_paths;
+
topo_sort_set_fn_t topo_setter;
topo_sort_get_fn_t topo_getter;
};
@@ -99,4 +103,28 @@ extern struct object_list **add_object(s
struct name_path *path,
const char *name);
+
+
+struct path_list {
+ char* item;
+ struct path_list *next;
+};
+
+struct rev_commit_info {
+ struct path_list* paths;
+ void* topo_data;
+};
+
+extern const struct path_list*
+path_list_find(const struct path_list* list, const char* path);
+extern struct path_list*
+path_list_insert(char *item, struct path_list **list_p);
+extern void path_list_print(FILE*, struct path_list* list);
+extern void free_path_list(struct path_list *list);
+extern const char** convert_to_pathspec(struct path_list* l1,
+ struct path_list* l2);
+extern void rev_setup_diffopt_paths(struct rev_info* revs,
+ struct commit* commit,
+ struct commit* parent);
+
#endif
^ permalink raw reply related
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Junio C Hamano @ 2006-05-15 21:13 UTC (permalink / raw)
To: Ryan Anderson; +Cc: git
In-Reply-To: <20060515210110.GR32076@h4x0r5.com>
Ryan Anderson <ryan@michonline.com> writes:
> I think, in practice, that /usr/lib/sendmail will exist anywhere you hve
> something running on port 25, at least on unixy machines. In my
> searches at an old job, that appeared to be the canonical place to call
> sendmail from, and every MTA appears to provide an appropriate binary
> there.
>
> So, I'm not overly worried about it.
exim, postfix and friends?
I used to know somebody who port-forwarded 25/tcp to central
smtp server from smaller machines in her intranet installation,
but I would say that is rare. I am not worried about it either;
I just wanted to make sure _somebody_ thought the potential
issues through and agreed with the change the patch makes.
^ permalink raw reply
* Re: The git newbie experience
From: Junio C Hamano @ 2006-05-15 21:10 UTC (permalink / raw)
To: Carl Worth; +Cc: git
In-Reply-To: <877j4nvx2w.wl%cworth@cworth.org>
Carl Worth <cworth@cworth.org> writes:
> In particular, when using checkout to change branches, unless I've
> specifically stated with "-m" that I want to carry my changes along, I
> would like git to stash my working tree "into" the branch I'm
> switching away from.
>
> Similarly, when switching to a branch, I'd like to have the working
> tree restored to what it was the last time I switched away from that
> branch.
>
> Does that seem unreasonable to anyone?
I would not call it unreasonable to want to have an easy access
to that mode of operation _as_ _well_, as an option.
If you were suggesting to make it the only way for future git to
work (I think you are not), then it sounds very unreasonable to
me.
The implementation behind the scene does not matter, but I think
set of "stashes" that can be attached to each branch would work
well for what you would want. OTOH, isn't it called stgit?
The reason why I want it to stay as an option is because I often
do a throw-away patch on top of whatever branch is checked out
in my working tree while reading the list traffic to compose a
response with an alternative patch. Usually I follow that by a
"git reset" once the message is sent out, but when I like the
idea well enough, I do "checkout -b jc/that-topic master" to
switch to a new branch with the dirty state carried along, to
work on it further. I do not want that workflow to require -m
flag, which means something different (-m means "I accept the
possibility of the three-way merge failing while doing this
switch that needs a merge"). Instead, I want "checkout -b" to
try carrying state forward and stop if it cannot without
file-level merging (i.e. the current behaviour). Then I can
think if I want to do a stash with "git diff", or if I want to
do the temporary branch not based on "master" but the current
branch (which is guaranteed to work without -m) and deal with
the mess later.
^ permalink raw reply
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Ryan Anderson @ 2006-05-15 21:01 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Eric Wong, git
In-Reply-To: <7vmzdjtya4.fsf@assigned-by-dhcp.cox.net>
On Mon, May 15, 2006 at 02:47:31AM -0700, Junio C Hamano wrote:
> Eric Wong <normalperson@yhbt.net> writes:
>
> > I believe this is what Martin wanted. I think it's a good idea since
> > sendmail binaries tend to be more flexible, but I'm ok with it either
> > way.
>
> I am not opposed to have an option to run a local submission
> agent binary (I said I like that if(){}else{} there, didn't I?).
> The ability to do so is a good thing. I am not however sure
> about changing the default when no option is specified on the
> command line.
I think, in practice, that /usr/lib/sendmail will exist anywhere you hve
something running on port 25, at least on unixy machines. In my
searches at an old job, that appeared to be the canonical place to call
sendmail from, and every MTA appears to provide an appropriate binary
there.
So, I'm not overly worried about it.
^ permalink raw reply
* Re: [PATCH] commit: allow --pretty= args to be abbreviated
From: Junio C Hamano @ 2006-05-15 20:47 UTC (permalink / raw)
To: Eric Wong; +Cc: git
In-Reply-To: <20060515003405.GA5533@localdomain>
Eric Wong <normalperson@yhbt.net> writes:
> Unlike the original one, this one only does prefix matches, so
> you can't do --pretty=er anymore :)
Sounds good. But then you know how long the unique prefix
are for each candidate, so wouldn't this rather be redundant, I
wonder?
> +
> + /* look for abbreviations */
> + len = strlen(arg);
> + found = -1;
> + for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
> + if (!strncmp(cmt_fmts[i].n, arg, len)) {
> + if (found >= 0)
> + die("invalid --pretty format: %s", arg);
> + found = i;
> + }
> + }
> + if (found >= 0)
> + return cmt_fmts[found].v;
> + die("invalid --pretty format: %s", arg);
> }
It would probably be better to say "ambiguous" not "invalid" in
the die() message.
^ permalink raw reply
* Re: The git newbie experience
From: Junio C Hamano @ 2006-05-15 20:47 UTC (permalink / raw)
To: Carl Baldwin; +Cc: Tommi Virtanen, Shawn Pearce, git
In-Reply-To: <20060515164610.GA24295@hpsvcnb.fc.hp.com>
Carl Baldwin <cnb@fc.hp.com> writes:
> My implementation actually wrote the working file state into the object
> store as a tree and stored a reference to the tree under something like
> .git/refs/undo (or .git/refs/stash). Redo was a simple merge of this
> tree back onto the current working files.
>
> I think I would like something like this better than the 'generate
> binary patch and reapply the patch later.
When you think of the "binary patch" as a human readable
representation of your (hierarchical set of) tree objects, you
would realize that these two approaches aren't that much
different at the tree merge level, and it's just a matter of
which representation is more convenient and human readable.
Pros and cons I see are:
* Branch approach needs to teach users only one thing -- create
a branch, merge with it, throw it away. Which is something
the user needs to know anyway, so it is a plus.
* Branch approach needs to store a full postimage tree and the
base commit (so you can use it as a merge base); the
postimage tree includes paths that are not involved in the
change being stashed.
* Patch records only the object names of paths that are relevant
to the stash. Instead of keeping the full postimage tree, it
creates one on the fly when you actually do the unstashing.
* Patch is human readable and can be used for purposes other
than falling back to a three-way merge. When cleanly applies
apply + write-tree is faster than a tree merge.
* Patch could be verbose if the change being stashed is large;
after all the primary information used are the object names
recorded on the "index" lines and the patch text itself is a
waste from storage point of view. This is a disadvantage of
the "patch" approach, but its readability might offset it.
If a change being stashed is large, the user had better be
doing it on a separate topic branch anyway, so this might not
be a big issue.
^ 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