Merge branch 'jc/skip-count'
authorJunio C Hamano <junkio@cox.net>
Mon, 25 Dec 2006 11:27:41 +0000 (03:27 -0800)
committerJunio C Hamano <junkio@cox.net>
Mon, 25 Dec 2006 11:27:41 +0000 (03:27 -0800)
* jc/skip-count:
revision: --skip=<n>

70 files changed:
.gitignore
Documentation/Makefile
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-diff.txt
Documentation/git-show-branch.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/install-doc-quick.sh [new file with mode: 0755]
Makefile
builtin-add.c
builtin-blame.c
builtin-branch.c
builtin-commit-tree.c
builtin-init-db.c
builtin-merge-file.c
builtin-repo-config.c
builtin-rerere.c [new file with mode: 0644]
builtin-rev-list.c
builtin-show-branch.c
builtin.h
cache.h
commit.c
compat/inet_ntop.c
compat/mmap.c
config.c
configure.ac
contrib/emacs/vc-git.el
diff.c
exec_cmd.c
git-add--interactive.perl [new file with mode: 0755]
git-am.sh
git-checkout.sh
git-clone.sh
git-compat-util.h
git-merge-one-file.sh
git-merge.sh
git-parse-remote.sh
git-pull.sh
git-rebase.sh
git-reset.sh
git-revert.sh
git-svn.perl
git-tag.sh
git.c
gitweb/gitweb.perl
imap-send.c
log-tree.c
merge-recursive.c
revision.c
revision.h
t/Makefile
t/lib-git-svn.sh
t/t4200-rerere.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t6024-recursive-merge.sh
t/t9100-git-svn-basic.sh
t/t9101-git-svn-props.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9104-git-svn-follow-parent.sh
t/t9105-git-svn-commit-diff.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/test-lib.sh
templates/remotes-- [deleted file]
usage.c
utf8.c [new file with mode: 0644]
utf8.h [new file with mode: 0644]
xdiff-interface.c
xdiff-interface.h
index d706dd92c6a63474616c09388fc9744ff055cedc..91e69665f46bdecaedd4a9664e5238448b42467a 100644 (file)
@@ -2,6 +2,7 @@ GIT-CFLAGS
 GIT-VERSION-FILE
 git
 git-add
+git-add--interactive
 git-am
 git-annotate
 git-apply
index d68bc4a788743412719b3590487ec6dc7efee3e1..93c7024b481157ca44dc40e844fa279616a03b3d 100644 (file)
@@ -32,6 +32,7 @@ man7dir=$(mandir)/man7
 # DESTDIR=
 
 INSTALL?=install
+DOC_REF = origin/man
 
 -include ../config.mak.autogen
 
@@ -112,3 +113,6 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
 
 install-webdoc : html
        sh ./install-webdoc.sh $(WEBDOC_DEST)
+
+quick-install:
+       sh ./install-doc-quick.sh $(DOC_REF) $(mandir)
index d86c0e7f19ee67556b48bb7f94ab9039a0195ea6..8710b3a75e0bfb7d0643a25ee7b9548ea5b01261 100644 (file)
@@ -7,7 +7,7 @@ git-add - Add file contents to the changeset to be committed next
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] [--] <file>...
+'git-add' [-n] [-v] [--interactive] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -43,6 +43,10 @@ OPTIONS
 -v::
         Be verbose.
 
+\--interactive::
+       Add modified contents in the working tree interactively to
+       the index.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
@@ -67,6 +71,119 @@ git-add git-*.sh::
        (i.e. you are listing the files explicitly), it does not
        consider `subdir/git-foo.sh`.
 
+Interactive mode
+----------------
+When the command enters the interactive mode, it shows the
+output of the 'status' subcommand, and then goes into ints
+interactive command loop.
+
+The command loop shows the list of subcommands available, and
+gives a prompt "What now> ".  In general, when the prompt ends
+with a single '>', you can pick only one of the choices given
+and type return, like this:
+
+------------
+    *** Commands ***
+      1: status       2: update       3: revert       4: add untracked
+      5: patch        6: diff         7: quit         8: help
+    What now> 1
+------------
+
+You also could say "s" or "sta" or "status" above as long as the
+choice is unique.
+
+The main command loop has 6 subcommands (plus help and quit).
+
+status::
+
+   This shows the change between HEAD and index (i.e. what will be
+   committed if you say "git commit"), and between index and
+   working tree files (i.e. what you could stage further before
+   "git commit" using "git-add") for each path.  A sample output
+   looks like this:
++
+------------
+              staged     unstaged path
+     1:       binary      nothing foo.png
+     2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+It shows that foo.png has differences from HEAD (but that is
+binary so line count cannot be shown) and there is no
+difference between indexed copy and the working tree
+version (if the working tree version were also different,
+'binary' would have been shown in place of 'nothing').  The
+other file, git-add--interactive.perl, has 403 lines added
+and 35 lines deleted if you commit what is in the index, but
+working tree file has further modifications (one addition and
+one deletion).
+
+update::
+
+   This shows the status information and gives prompt
+   "Update>>".  When the prompt ends with double '>>', you can
+   make more than one selection, concatenated with whitespace or
+   comma.  Also you can say ranges.  E.g. "2-5 7,9" to choose
+   2,3,4,5,7,9 from the list.  You can say '*' to choose
+   everything.
++
+What you chose are then highlighted with '*',
+like this:
++
+------------
+           staged     unstaged path
+  1:       binary      nothing foo.png
+* 2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+To remove selection, prefix the input with `-`
+like this:
++
+------------
+Update>> -2
+------------
++
+After making the selection, answer with an empty line to stage the
+contents of working tree files for selected paths in the index.
+
+revert::
+
+  This has a very similar UI to 'update', and the staged
+  information for selected paths are reverted to that of the
+  HEAD version.  Reverting new paths makes them untracked.
+
+add untracked::
+
+  This has a very similar UI to 'update' and
+  'revert', and lets you add untracked paths to the index.
+
+patch::
+
+  This lets you choose one path out of 'status' like selection.
+  After choosing the path, it presents diff between the index
+  and the working tree file and asks you if you want to stage
+  the change of each hunk.  You can say:
+
+       y - add the change from that hunk to index
+       n - do not add the change from that hunk to index
+       a - add the change from that hunk and all the rest to index
+       d - do not the change from that hunk nor any of the rest to index
+       j - do not decide on this hunk now, and view the next
+           undecided hunk
+       J - do not decide on this hunk now, and view the next hunk
+       k - do not decide on this hunk now, and view the previous
+           undecided hunk
+       K - do not decide on this hunk now, and view the previous hunk
++
+After deciding the fate for all hunks, if there is any hunk
+that was chosen, the index is updated with the selected hunks.
+
+diff::
+
+  This lets you review what will be committed (i.e. between
+  HEAD and index).
+
+
 See Also
 --------
 gitlink:git-status[1]
index 71417feba8f4987bb9d7565ec58f82044d343283..c464bd2fda2e4828d2e5a36ca05b4c66ad592f7c 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 'git-branch' [-r | -a] [-v [--abbrev=<length>]]
 'git-branch' [-l] [-f] <branchname> [<start-point>]
 'git-branch' (-m | -M) [<oldbranch>] <newbranch>
-'git-branch' (-d | -D) <branchname>...
+'git-branch' (-d | -D) [-r] <branchname>...
 
 DESCRIPTION
 -----------
@@ -33,7 +33,8 @@ to happen.
 
 With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
 specify more than one branch for deletion.  If the branch currently
-has a ref log then the ref log will also be deleted.
+has a ref log then the ref log will also be deleted. Use -r together with -d
+to delete remote-tracking branches.
 
 
 OPTIONS
@@ -60,7 +61,7 @@ OPTIONS
        Move/rename a branch even if the new branchname already exists.
 
 -r::
-       List the remote-tracking branches.
+       List or delete (if used with -d) the remote-tracking branches.
 
 -a::
        List both remote-tracking branches and local branches.
@@ -111,10 +112,12 @@ Delete unneeded branch::
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -D todo    <1>
+$ git branch -d -r todo html man   <1>
+$ git branch -D test               <2>
 ------------
 +
-<1> delete todo branch even if the "master" branch does not have all
+<1> delete remote-tracking branches "todo", "html", "man"
+<2> delete "test" branch even if the "master" branch does not have all
 commits from todo branch.
 
 
index bfddb21fee0a73641dbbbb7efb1863f162851653..874934a33243106544181ffeae2fee8b03ff5e8d 100644 (file)
@@ -11,8 +11,7 @@ SYNOPSIS
 [verse]
 'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
          [-o <name>] [-u <upload-pack>] [--reference <repository>]
-         [--use-separate-remote | --no-separate-remote] <repository>
-         [<directory>]
+         <repository> [<directory>]
 
 DESCRIPTION
 -----------
@@ -99,18 +98,6 @@ OPTIONS
        if unset the templates are taken from the installation
        defined default, typically `/usr/share/git-core/templates`.
 
---use-separate-remote::
-       Save remotes heads under `$GIT_DIR/refs/remotes/origin/` instead
-       of `$GIT_DIR/refs/heads/`.  Only the local master branch is
-       saved in the latter. This is the default.
-
---no-separate-remote::
-       Save remotes heads in the same namespace as the local
-       heads, `$GIT_DIR/refs/heads/'.  In regular repositories,
-       this is a legacy setup git-clone created by default in
-       older Git versions, and will be removed before the next
-       major release.
-
 <repository>::
        The (possibly remote) repository to clone from.  It can
        be any URL git-fetch supports.
index 10fdf88dce296f9fd7aea849b974cfbe709fa1b4..8977877b21b6681528d13f71b558c060e8c26c4d 100644 (file)
@@ -8,7 +8,7 @@ git-diff - Show changes between commits, commit and working tree, etc
 
 SYNOPSIS
 --------
-'git-diff' [ --diff-options ] <tree-ish>{0,2} [--] [<path>...]
+'git-diff' [ --diff-options ] <commit>{0,2} [--] [<path>...]
 
 DESCRIPTION
 -----------
@@ -26,7 +26,7 @@ tree and the index file, or the index file and the working tree.
 'git-diff' [--options] --cached [<commit>] [--] [<path>...]::
 
        This form is to view the changes you staged for the next
-       commit relative to the named <tree-ish>.  Typically you
+       commit relative to the named <commit>.  Typically you
        would want comparison with the latest commit, so if you
        do not give <commit>, it defaults to HEAD.
 
index 948ff10e6c83b8842de453e798ff7916ac98d911..dafacd43080b6ab00205fab2a84617834ad5851f 100644 (file)
@@ -8,7 +8,7 @@ git-show-branch - Show branches and their commits
 SYNOPSIS
 --------
 [verse]
-'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+'git-show-branch' [--all] [--remotes] [--topo-order] [--current]
                [--more=<n> | --list | --independent | --merge-base]
                [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
 
@@ -37,9 +37,11 @@ OPTIONS
        branches under $GIT_DIR/refs/heads/topic, giving
        `topic/*` would show all of them.
 
---all --heads --tags::
-       Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads,
-       and $GIT_DIR/refs/tags, respectively.
+-r|--remotes::
+       Show the remote-tracking branches.
+
+-a|--all::
+       Show both remote-tracking branches and local branches.
 
 --current::
        With this option, the command includes the current
index c589a9863064ef3e5f12876d40174fb991cf3b59..f5f57e8f87d4271a634f13f33d6b33af43909473 100644 (file)
@@ -64,7 +64,7 @@ manually joining branches on commit.
        against the latest changes in the SVN repository.
        An optional command-line argument may be specified as an
        alternative to HEAD.
-       This is advantageous over 'commit' (below) because it produces
+       This is advantageous over 'set-tree' (below) because it produces
        cleaner, more linear history.
 
 'log'::
@@ -89,7 +89,7 @@ manually joining branches on commit.
 
        Any other arguments are passed directly to `git log'
 
-'commit'::
+'set-tree'::
        You should consider using 'dcommit' instead of this command.
        Commit specified commit or tree objects to SVN.  This relies on
        your imported fetch data being up-to-date.  This makes
@@ -172,7 +172,7 @@ This can allow you to make partial mirrors when running fetch.
 -::
 --stdin::
 
-Only used with the 'commit' command.
+Only used with the 'set-tree' command.
 
 Read a list of commits from stdin and commit them in reverse
 order.  Only the leading sha1 is read from each line, so
@@ -180,7 +180,7 @@ git-rev-list --pretty=oneline output can be used.
 
 --rmdir::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Remove directories from the SVN tree if there are no files left
 behind.  SVN can version empty directories, and they are not
@@ -193,7 +193,7 @@ repo-config key: svn.rmdir
 -e::
 --edit::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Edit the commit message before committing to SVN.  This is off by
 default for objects that are commits, and forced on when committing
@@ -204,7 +204,7 @@ repo-config key: svn.edit
 -l<num>::
 --find-copies-harder::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 They are both passed directly to git-diff-tree see
 gitlink:git-diff-tree[1] for more information.
@@ -276,7 +276,7 @@ ADVANCED OPTIONS
 
 -b<refname>::
 --branch <refname>::
-Used with 'fetch', 'dcommit' or 'commit'.
+Used with 'fetch', 'dcommit' or 'set-tree'.
 
 This can be used to join arbitrary git branches to remotes/git-svn
 on new commits where the tree object is equivalent.
@@ -392,11 +392,11 @@ REBASE VS. PULL
 ---------------
 
 Originally, git-svn recommended that the remotes/git-svn branch be
-pulled from.  This is because the author favored 'git-svn commit B'
-to commit a single head rather than the 'git-svn commit A..B' notation
+pulled from.  This is because the author favored 'git-svn set-tree B'
+to commit a single head rather than the 'git-svn set-tree A..B' notation
 to commit multiple commits.
 
-If you use 'git-svn commit A..B' to commit several diffs and you do not
+If you use 'git-svn set-tree A..B' to commit several diffs and you do not
 have the latest remotes/git-svn merged into my-branch, you should use
 'git rebase' to update your work branch instead of 'git pull'.  'pull'
 can cause non-linear history to be flattened when committing into SVN,
index 45476c2e415112347372f034276dec367f7a6a8d..48b82b86f83df4e3fb381da329b48fe12a4ce82d 100644 (file)
@@ -9,7 +9,8 @@ git-tag - Create a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg> | -F <file>]
+        <name> [<head>]
 'git-tag' -l [<pattern>]
 
 DESCRIPTION
@@ -60,6 +61,9 @@ OPTIONS
 -m <msg>::
        Use the given tag message (instead of prompting)
 
+-F <file>::
+       Take the tag message from the given file.  Use '-' to
+       read the message from the standard input.
 
 Author
 ------
diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh
new file mode 100755 (executable)
index 0000000..a640549
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# This requires a branch named in $head
+# (usually 'man' or 'html', provided by the git.git repository)
+set -e
+head="$1"
+mandir="$2"
+SUBDIRECTORY_OK=t
+USAGE='<refname> <target directory>'
+. git-sh-setup
+export GIT_DIR
+
+test -z "$mandir" && usage
+if ! git-rev-parse --verify "$head^0" >/dev/null; then
+       echo >&2 "head: $head does not exist in the current repository"
+       usage
+fi
+
+GIT_INDEX_FILE=`pwd`/.quick-doc.index
+export GIT_INDEX_FILE
+rm -f "$GIT_INDEX_FILE"
+git-read-tree $head
+git-checkout-index -a -f --prefix="$mandir"/
+
+if test -n "$GZ"; then
+       cd "$mandir"
+       for i in `git-ls-tree -r --name-only $head`
+       do
+               gzip < $i > $i.gz && rm $i
+       done
+fi
+rm -f "$GIT_INDEX_FILE"
index 76511045a80cf5da72851d8194742de56282d953..475047f100ef1a2a9cf73557eba3b300e74cac9d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -79,9 +79,6 @@ all:
 #
 # Define NO_ICONV if your libc does not properly support iconv.
 #
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
-#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -173,8 +170,8 @@ SCRIPT_SH = \
        git-lost-found.sh git-quiltimport.sh
 
 SCRIPT_PERL = \
+       git-add--interactive.perl \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-rerere.perl \
        git-cvsserver.perl \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
@@ -233,7 +230,8 @@ LIB_H = \
        archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \
        diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
-       tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h
+       tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
+       utf8.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -252,7 +250,8 @@ LIB_OBJS = \
        revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o list-objects.o grep.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
-       color.o wt-status.o archive-zip.o archive-tar.o
+       color.o wt-status.o archive-zip.o archive-tar.o \
+       utf8.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -289,6 +288,7 @@ BUILTIN_OBJS = \
        builtin-push.o \
        builtin-read-tree.o \
        builtin-repo-config.o \
+       builtin-rerere.o \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-rm.o \
@@ -549,9 +549,6 @@ else
 endif
 endif
 endif
-ifdef NO_ACCURATE_DIFF
-       BASIC_CFLAGS += -DNO_ACCURATE_DIFF
-endif
 ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
 endif
@@ -830,6 +827,8 @@ install: all
 install-doc:
        $(MAKE) -C Documentation install
 
+quick-install-doc:
+       $(MAKE) -C Documentation quick-install
 
 
 
index c8a114fefb60a106dccffda5c703f2f00fa30ce9..17641b433d5249a88a396fa4d68415f1d1dcd7ca 100644 (file)
@@ -6,10 +6,11 @@
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
+#include "exec_cmd.h"
 #include "cache-tree.h"
 
 static const char builtin_add_usage[] =
-"git-add [-n] [-v] <filepattern>...";
+"git-add [-n] [-v] [--interactive] [--] <filepattern>...";
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
@@ -87,6 +88,20 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        int verbose = 0, show_only = 0;
        const char **pathspec;
        struct dir_struct dir;
+       int add_interactive = 0;
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("--interactive", argv[i]))
+                       add_interactive++;
+       }
+       if (add_interactive) {
+               const char *args[] = { "add--interactive", NULL };
+
+               if (add_interactive != 1 || argc != 2)
+                       die("add --interactive does not take any parameters");
+               execv_git_cmd(args);
+               exit(1);
+       }
 
        git_config(git_default_config);
 
@@ -111,6 +126,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                }
                usage(builtin_add_usage);
        }
+       if (argc <= i) {
+               fprintf(stderr, "Nothing specified, nothing added.\n");
+               fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+               return 0;
+       }
        pathspec = get_pathspec(prefix, argv + i);
 
        fill_directory(&dir, pathspec);
index 9bf6ec951fb5d7535441a8db5b73d20f509ed982..4a1accf13c1220745c73999c6c345735aeadc3e5 100644 (file)
@@ -18,7 +18,9 @@
 static char blame_usage[] =
 "git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
 "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  --root              Do not treat root commits as boundaries (Default: off)\n"
 "  -t, --time          Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
@@ -32,6 +34,8 @@ static int longest_author;
 static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
+static int show_root;
+static int blank_boundary;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -1091,6 +1095,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
                        if (commit->object.parsed)
                                mark_parents_uninteresting(commit);
                }
+               /* treat root commit as boundary */
+               if (!commit->parents && !show_root)
+                       commit->object.flags |= UNINTERESTING;
 
                /* Take responsibility for the remaining entries */
                for (ent = sb->ent; ent; ent = ent->next)
@@ -1314,8 +1321,12 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
 
                if (suspect->commit->object.flags & UNINTERESTING) {
-                       length--;
-                       putchar('^');
+                       if (!blank_boundary) {
+                               length--;
+                               putchar('^');
+                       }
+                       else
+                               memset(hex, ' ', length);
                }
 
                printf("%.*s", length, hex);
@@ -1635,6 +1646,19 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
+static int git_blame_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "blame.showroot")) {
+               show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.blankboundary")) {
+               blank_boundary = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -1650,6 +1674,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        char type[10];
        const char *bottomtop = NULL;
 
+       git_config(git_blame_config);
        save_commit_buffer = 0;
 
        opt = 0;
@@ -1658,6 +1683,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                const char *arg = argv[i];
                if (*arg != '-')
                        break;
+               else if (!strcmp("-b", arg))
+                       blank_boundary = 1;
+               else if (!strcmp("--root", arg))
+                       show_root = 1;
                else if (!strcmp("-c", arg))
                        output_option |= OUTPUT_ANNOTATE_COMPAT;
                else if (!strcmp("-t", arg))
index 515330c155a8e54c72fd02ea74e30be10b852490..903d5cf05600f9a7e297dc9450ad5980388072e9 100644 (file)
 #include "builtin.h"
 
 static const char builtin_branch_usage[] =
-  "git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]";
+  "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]";
 
+#define REF_UNKNOWN_TYPE    0x00
+#define REF_LOCAL_BRANCH    0x01
+#define REF_REMOTE_BRANCH   0x02
+#define REF_TAG             0x04
 
 static const char *head;
 static unsigned char head_sha1[20];
@@ -89,12 +93,28 @@ static int in_merge_bases(const unsigned char *sha1,
        return ret;
 }
 
-static void delete_branches(int argc, const char **argv, int force)
+static int delete_branches(int argc, const char **argv, int force, int kinds)
 {
        struct commit *rev, *head_rev = head_rev;
        unsigned char sha1[20];
-       char *name;
+       char *name = NULL;
+       const char *fmt, *remote;
        int i;
+       int ret = 0;
+
+       switch (kinds) {
+       case REF_REMOTE_BRANCH:
+               fmt = "refs/remotes/%s";
+               remote = "remote ";
+               force = 1;
+               break;
+       case REF_LOCAL_BRANCH:
+               fmt = "refs/heads/%s";
+               remote = "";
+               break;
+       default:
+               die("cannot use -a with -d");
+       }
 
        if (!force) {
                head_rev = lookup_commit_reference(head_sha1);
@@ -102,16 +122,30 @@ static void delete_branches(int argc, const char **argv, int force)
                        die("Couldn't look up commit object for HEAD");
        }
        for (i = 0; i < argc; i++) {
-               if (!strcmp(head, argv[i]))
-                       die("Cannot delete the branch you are currently on.");
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+                       error("Cannot delete the branch '%s' "
+                               "which you are currently on.", argv[i]);
+                       ret = 1;
+                       continue;
+               }
+
+               if (name)
+                       free(name);
 
-               name = xstrdup(mkpath("refs/heads/%s", argv[i]));
-               if (!resolve_ref(name, sha1, 1, NULL))
-                       die("Branch '%s' not found.", argv[i]);
+               name = xstrdup(mkpath(fmt, argv[i]));
+               if (!resolve_ref(name, sha1, 1, NULL)) {
+                       error("%sbranch '%s' not found.",
+                                       remote, argv[i]);
+                       ret = 1;
+                       continue;
+               }
 
                rev = lookup_commit_reference(sha1);
-               if (!rev)
-                       die("Couldn't look up commit object for '%s'", name);
+               if (!rev) {
+                       error("Couldn't look up commit object for '%s'", name);
+                       ret = 1;
+                       continue;
+               }
 
                /* This checks whether the merge bases of branch and
                 * HEAD contains branch -- which means that the HEAD
@@ -120,26 +154,28 @@ static void delete_branches(int argc, const char **argv, int force)
 
                if (!force &&
                    !in_merge_bases(sha1, rev, head_rev)) {
-                       fprintf(stderr,
-                               "The branch '%s' is not a strict subset of your current HEAD.\n"
-                               "If you are sure you want to delete it, run 'git branch -D %s'.\n",
-                               argv[i], argv[i]);
-                       exit(1);
+                       error("The branch '%s' is not a strict subset of "
+                               "your current HEAD.\n"
+                               "If you are sure you want to delete it, "
+                               "run 'git branch -D %s'.", argv[i], argv[i]);
+                       ret = 1;
+                       continue;
                }
 
-               if (delete_ref(name, sha1))
-                       printf("Error deleting branch '%s'\n", argv[i]);
-               else
-                       printf("Deleted branch %s.\n", argv[i]);
+               if (delete_ref(name, sha1)) {
+                       error("Error deleting %sbranch '%s'", remote,
+                              argv[i]);
+                       ret = 1;
+               } else
+                       printf("Deleted %sbranch %s.\n", remote, argv[i]);
 
-               free(name);
        }
-}
 
-#define REF_UNKNOWN_TYPE    0x00
-#define REF_LOCAL_BRANCH    0x01
-#define REF_REMOTE_BRANCH   0x02
-#define REF_TAG             0x04
+       if (name)
+               free(name);
+
+       return(ret);
+}
 
 struct ref_item {
        char *name;
@@ -435,7 +471,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        head += 11;
 
        if (delete)
-               delete_branches(argc - i, argv + i, force_delete);
+               return delete_branches(argc - i, argv + i, force_delete, kinds);
        else if (i == argc)
                print_ref_list(kinds, verbose, abbrev);
        else if (rename && (i == argc - 1))
index 856f3cd841818bdad4446b3f7b75d5ab22959788..f641787988e197209f097cbc9d1b260a2cb6d9d8 100644 (file)
@@ -7,6 +7,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "builtin.h"
+#include "utf8.h"
 
 #define BLOCKING (1ul << 14)
 
@@ -32,7 +33,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
        len = vsnprintf(one_line, sizeof(one_line), fmt, args);
        va_end(args);
        size = *sizep;
-       newsize = size + len;
+       newsize = size + len + 1;
        alloc = (size + 32767) & ~32767;
        buf = *bufp;
        if (newsize > alloc) {
@@ -40,7 +41,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
                buf = xrealloc(buf, alloc);
                *bufp = buf;
        }
-       *sizep = newsize;
+       *sizep = newsize - 1;
        memcpy(buf + size, one_line, len);
 }
 
@@ -77,6 +78,11 @@ static int new_parent(int idx)
        return 1;
 }
 
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
        int i;
@@ -101,6 +107,9 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                a = argv[i]; b = argv[i+1];
                if (!b || strcmp(a, "-p"))
                        usage(commit_tree_usage);
+
+               if (parents >= MAXPARENT)
+                       die("Too many parents (%d max)", MAXPARENT);
                if (get_sha1(b, parent_sha1[parents]))
                        die("Not a valid object name %s", b);
                check_valid(parent_sha1[parents], commit_type);
@@ -127,6 +136,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        while (fgets(comment, sizeof(comment), stdin) != NULL)
                add_buffer(&buffer, &size, "%s", comment);
 
+       /* And check the encoding */
+       buffer[size] = '\0';
+       if (!strcmp(git_commit_encoding, "utf-8") && !is_utf8(buffer))
+               fprintf(stderr, commit_utf8_warn);
+
        if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
                printf("%s\n", sha1_to_hex(commit_sha1));
                return 0;
index 1d7d15e8d53a7a2817ceff6f5c5e056747688c9e..01f366ad0bf5860aaf9123ef5d5653c612871a9f 100644 (file)
@@ -124,8 +124,11 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        int template_len;
        DIR *dir;
 
-       if (!template_dir)
-               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       if (!template_dir) {
+               template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+               if (!template_dir)
+                       template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       }
        strcpy(template_path, template_dir);
        template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
index 6c4c3a351333b94ab62b77793b540af5c9bcaa3a..913577390862a847857a650516740059072b60ad 100644 (file)
@@ -1,26 +1,10 @@
 #include "cache.h"
 #include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
 
 static const char merge_file_usage[] =
 "git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
 
-static int read_file(mmfile_t *ptr, const char *filename)
-{
-       struct stat st;
-       FILE *f;
-
-       if (stat(filename, &st))
-               return error("Could not stat %s", filename);
-       if ((f = fopen(filename, "rb")) == NULL)
-               return error("Could not open %s", filename);
-       ptr->ptr = xmalloc(st.st_size);
-       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
-               return error("Could not read %s", filename);
-       fclose(f);
-       ptr->size = st.st_size;
-       return 0;
-}
-
 int cmd_merge_file(int argc, char **argv, char **envp)
 {
        char *names[3];
@@ -53,7 +37,7 @@ int cmd_merge_file(int argc, char **argv, char **envp)
                names[i] = argv[i + 1];
 
        for (i = 0; i < 3; i++)
-               if (read_file(mmfs + i, argv[i + 1]))
+               if (read_mmfile(mmfs + i, argv[i + 1]))
                        return -1;
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
index a7ab4cee58fcd316497d10a766602e9fbd577998..90633119d4ae873ed30889e93dbb4307af0ae0d1 100644 (file)
@@ -66,10 +66,10 @@ static int get_value(const char* key_, const char* regex_)
        char *global = NULL, *repo_config = NULL;
        const char *local;
 
-       local = getenv("GIT_CONFIG");
+       local = getenv(CONFIG_ENVIRONMENT);
        if (!local) {
                const char *home = getenv("HOME");
-               local = getenv("GIT_CONFIG_LOCAL");
+               local = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!local)
                        local = repo_config = xstrdup(git_path("config"));
                if (home)
diff --git a/builtin-rerere.c b/builtin-rerere.c
new file mode 100644 (file)
index 0000000..d064bd8
--- /dev/null
@@ -0,0 +1,406 @@
+#include "cache.h"
+#include "path-list.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+#include <time.h>
+
+static const char git_rerere_usage[] =
+"git-rerere [clear | status | diff | gc]";
+
+/* these values are days */
+static int cutoff_noresolve = 15;
+static int cutoff_resolve = 60;
+
+static char *merge_rr_path;
+
+static const char *rr_path(const char *name, const char *file)
+{
+       return git_path("rr-cache/%s/%s", name, file);
+}
+
+static void read_rr(struct path_list *rr)
+{
+       unsigned char sha1[20];
+       char buf[PATH_MAX];
+       FILE *in = fopen(merge_rr_path, "r");
+       if (!in)
+               return;
+       while (fread(buf, 40, 1, in) == 1) {
+               int i;
+               char *name;
+               if (get_sha1_hex(buf, sha1))
+                       die("corrupt MERGE_RR");
+               buf[40] = '\0';
+               name = xstrdup(buf);
+               if (fgetc(in) != '\t')
+                       die("corrupt MERGE_RR");
+               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
+                       ; /* do nothing */
+               if (i == sizeof(buf))
+                       die("filename too long");
+               path_list_insert(buf, rr)->util = xstrdup(name);
+       }
+       fclose(in);
+}
+
+static struct lock_file write_lock;
+
+static int write_rr(struct path_list *rr, int out_fd)
+{
+       int i;
+       for (i = 0; i < rr->nr; i++) {
+               const char *path = rr->items[i].path;
+               write(out_fd, rr->items[i].util, 40);
+               write(out_fd, "\t", 1);
+               write(out_fd, path, strlen(path) + 1);
+       }
+       close(out_fd);
+       return commit_lock_file(&write_lock);
+}
+
+struct buffer {
+       char *ptr;
+       int nr, alloc;
+};
+
+static void append_line(struct buffer *buffer, const char *line)
+{
+       int len = strlen(line);
+
+       if (buffer->nr + len > buffer->alloc) {
+               buffer->alloc = alloc_nr(buffer->nr + len);
+               buffer->ptr = xrealloc(buffer->ptr, buffer->alloc);
+       }
+       memcpy(buffer->ptr + buffer->nr, line, len);
+       buffer->nr += len;
+}
+
+static int handle_file(const char *path,
+        unsigned char *sha1, const char *output)
+{
+       SHA_CTX ctx;
+       char buf[1024];
+       int hunk = 0, hunk_no = 0;
+       struct buffer minus = { NULL, 0, 0 }, plus = { NULL, 0, 0 };
+       struct buffer *one = &minus, *two = &plus;
+       FILE *f = fopen(path, "r");
+       FILE *out;
+
+       if (!f)
+               return error("Could not open %s", path);
+
+       if (output) {
+               out = fopen(output, "w");
+               if (!out) {
+                       fclose(f);
+                       return error("Could not write %s", output);
+               }
+       } else
+               out = NULL;
+
+       if (sha1)
+               SHA1_Init(&ctx);
+
+       while (fgets(buf, sizeof(buf), f)) {
+               if (!strncmp("<<<<<<< ", buf, 8))
+                       hunk = 1;
+               else if (!strncmp("=======", buf, 7))
+                       hunk = 2;
+               else if (!strncmp(">>>>>>> ", buf, 8)) {
+                       hunk_no++;
+                       hunk = 0;
+                       if (memcmp(one->ptr, two->ptr, one->nr < two->nr ?
+                                               one->nr : two->nr) > 0) {
+                               struct buffer *swap = one;
+                               one = two;
+                               two = swap;
+                       }
+                       if (out) {
+                               fputs("<<<<<<<\n", out);
+                               fwrite(one->ptr, one->nr, 1, out);
+                               fputs("=======\n", out);
+                               fwrite(two->ptr, two->nr, 1, out);
+                               fputs(">>>>>>>\n", out);
+                       }
+                       if (sha1) {
+                               SHA1_Update(&ctx, one->ptr, one->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                               SHA1_Update(&ctx, two->ptr, two->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                       }
+               } else if (hunk == 1)
+                       append_line(one, buf);
+               else if (hunk == 2)
+                       append_line(two, buf);
+               else if (out)
+                       fputs(buf, out);
+       }
+
+       fclose(f);
+       if (out)
+               fclose(out);
+       if (sha1)
+               SHA1_Final(sha1, &ctx);
+       return hunk_no;
+}
+
+static int find_conflict(struct path_list *conflict)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+       for (i = 0; i + 2 < active_nr; i++) {
+               struct cache_entry *e1 = active_cache[i];
+               struct cache_entry *e2 = active_cache[i + 1];
+               struct cache_entry *e3 = active_cache[i + 2];
+               if (ce_stage(e1) == 1 && ce_stage(e2) == 2 &&
+                               ce_stage(e3) == 3 && ce_same_name(e1, e2) &&
+                               ce_same_name(e1, e3)) {
+                       path_list_insert((const char *)e1->name, conflict);
+                       i += 3;
+               }
+       }
+       return 0;
+}
+
+static int merge(const char *name, const char *path)
+{
+       int ret;
+       mmfile_t cur, base, other;
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+
+       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+               return 1;
+
+       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
+                       read_mmfile(&base, rr_path(name, "preimage")) ||
+                       read_mmfile(&other, rr_path(name, "postimage")))
+               return 1;
+       ret = xdl_merge(&base, &cur, "", &other, "",
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       if (!ret) {
+               FILE *f = fopen(path, "w");
+               if (!f)
+                       return error("Could not write to %s", path);
+               fwrite(result.ptr, result.size, 1, f);
+               fclose(f);
+       }
+
+       free(cur.ptr);
+       free(base.ptr);
+       free(other.ptr);
+       free(result.ptr);
+
+       return ret;
+}
+
+static void unlink_rr_item(const char *name)
+{
+       unlink(rr_path(name, "thisimage"));
+       unlink(rr_path(name, "preimage"));
+       unlink(rr_path(name, "postimage"));
+       rmdir(git_path("rr-cache/%s", name));
+}
+
+static void garbage_collect(struct path_list *rr)
+{
+       struct path_list to_remove = { NULL, 0, 0, 1 };
+       char buf[1024];
+       DIR *dir;
+       struct dirent *e;
+       int len, i, cutoff;
+       time_t now = time(NULL), then;
+
+       strlcpy(buf, git_path("rr-cache"), sizeof(buf));
+       len = strlen(buf);
+       dir = opendir(buf);
+       strcpy(buf + len++, "/");
+       while ((e = readdir(dir))) {
+               const char *name = e->d_name;
+               struct stat st;
+               if (name[0] == '.' && (name[1] == '\0' ||
+                                       (name[1] == '.' && name[2] == '\0')))
+                       continue;
+               i = snprintf(buf + len, sizeof(buf) - len, "%s", name);
+               strlcpy(buf + len + i, "/preimage", sizeof(buf) - len - i);
+               if (stat(buf, &st))
+                       continue;
+               then = st.st_mtime;
+               strlcpy(buf + len + i, "/postimage", sizeof(buf) - len - i);
+               cutoff = stat(buf, &st) ? cutoff_noresolve : cutoff_resolve;
+               if (then < now - cutoff * 86400) {
+                       buf[len + i] = '\0';
+                       path_list_insert(xstrdup(name), &to_remove);
+               }
+       }
+       for (i = 0; i < to_remove.nr; i++)
+               unlink_rr_item(to_remove.items[i].path);
+       path_list_clear(&to_remove, 0);
+}
+
+static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               write(1, ptr[i].ptr, ptr[i].size);
+       return 0;
+}
+
+static int diff_two(const char *file1, const char *label1,
+               const char *file2, const char *label2)
+{
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+       mmfile_t minus, plus;
+
+       if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+               return 1;
+
+       printf("--- a/%s\n+++ b/%s\n", label1, label2);
+       fflush(stdout);
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = 0;
+       ecb.outf = outf;
+       xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+       free(minus.ptr);
+       free(plus.ptr);
+       return 0;
+}
+
+static int copy_file(const char *src, const char *dest)
+{
+       FILE *in, *out;
+       char buffer[32768];
+       int count;
+
+       if (!(in = fopen(src, "r")))
+               return error("Could not open %s", src);
+       if (!(out = fopen(dest, "w")))
+               return error("Could not open %s", dest);
+       while ((count = fread(buffer, 1, sizeof(buffer), in)))
+               fwrite(buffer, 1, count, out);
+       fclose(in);
+       fclose(out);
+       return 0;
+}
+
+static int do_plain_rerere(struct path_list *rr, int fd)
+{
+       struct path_list conflict = { NULL, 0, 0, 1 };
+       int i;
+
+       find_conflict(&conflict);
+
+       /*
+        * MERGE_RR records paths with conflicts immediately after merge
+        * failed.  Some of the conflicted paths might have been hand resolved
+        * in the working tree since then, but the initial run would catch all
+        * and register their preimages.
+        */
+
+       for (i = 0; i < conflict.nr; i++) {
+               const char *path = conflict.items[i].path;
+               if (!path_list_has_path(rr, path)) {
+                       unsigned char sha1[20];
+                       char *hex;
+                       int ret;
+                       ret = handle_file(path, sha1, NULL);
+                       if (ret < 1)
+                               continue;
+                       hex = xstrdup(sha1_to_hex(sha1));
+                       path_list_insert(path, rr)->util = hex;
+                       if (mkdir(git_path("rr-cache/%s", hex), 0755))
+                               continue;;
+                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
+               }
+       }
+
+       /*
+        * Now some of the paths that had conflicts earlier might have been
+        * hand resolved.  Others may be similar to a conflict already that
+        * was resolved before.
+        */
+
+       for (i = 0; i < rr->nr; i++) {
+               struct stat st;
+               int ret;
+               const char *path = rr->items[i].path;
+               const char *name = (const char *)rr->items[i].util;
+
+               if (!stat(rr_path(name, "preimage"), &st) &&
+                               !stat(rr_path(name, "postimage"), &st)) {
+                       if (!merge(name, path)) {
+                               fprintf(stderr, "Resolved '%s' using "
+                                               "previous resolution.\n", path);
+                               goto tail_optimization;
+                       }
+               }
+
+               /* Let's see if we have resolved it. */
+               ret = handle_file(path, NULL, NULL);
+               if (ret)
+                       continue;
+
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+               copy_file(path, rr_path(name, "postimage"));
+tail_optimization:
+               if (i < rr->nr - 1) {
+                       memmove(rr->items + i,
+                                       rr->items + i + 1,
+                                       rr->nr - i - 1);
+               }
+               rr->nr--;
+               i--;
+       }
+
+       return write_rr(rr, fd);
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int i, fd = -1;
+       struct stat st;
+
+       if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode))
+               return 0;
+
+       merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
+       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+       read_rr(&merge_rr);
+
+       if (argc < 2)
+               return do_plain_rerere(&merge_rr, fd);
+       else if (!strcmp(argv[1], "clear")) {
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       if (!stat(git_path("rr-cache/%s", name), &st) &&
+                                       S_ISDIR(st.st_mode) &&
+                                       stat(rr_path(name, "postimage"), &st))
+                               unlink_rr_item(name);
+               }
+               unlink(merge_rr_path);
+       } else if (!strcmp(argv[1], "gc"))
+               garbage_collect(&merge_rr);
+       else if (!strcmp(argv[1], "status"))
+               for (i = 0; i < merge_rr.nr; i++)
+                       printf("%s\n", merge_rr.items[i].path);
+       else if (!strcmp(argv[1], "diff"))
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *path = merge_rr.items[i].path;
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       diff_two(rr_path(name, "preimage"), path, path, path);
+               }
+       else
+               usage(git_rerere_usage);
+
+       path_list_clear(&merge_rr, 1);
+       return 0;
+}
+
index fb7fc92145b6a8baec340176abf40c6b37f45a8e..1bb3a06680194ce5855185ad1a1c7e39eeebba94 100644 (file)
@@ -54,6 +54,12 @@ static void show_commit(struct commit *commit)
                fputs(header_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
+       else if (revs.left_right) {
+               if (commit->object.flags & SYMMETRIC_LEFT)
+                       putchar('<');
+               else
+                       putchar('>');
+       }
        if (revs.abbrev_commit && revs.abbrev)
                fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
                      stdout);
index b9d9781d4d6a12860d377607369a4dea95a3c7be..c67f2fa2fe2203b5308eb156d502b75ce44751d5 100644 (file)
@@ -4,7 +4,7 @@
 #include "builtin.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>";
+"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>";
 
 static int default_num;
 static int default_alloc;
@@ -383,6 +383,20 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f
        return append_ref(refname + ofs, sha1, flag, cb_data);
 }
 
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char tmp[20];
+       int ofs = 13;
+       if (strncmp(refname, "refs/remotes/", ofs))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1, flag, cb_data);
+}
+
 static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        if (strncmp(refname, "refs/tags/", 10))
@@ -423,16 +437,16 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i
        return append_ref(refname, sha1, flag, cb_data);
 }
 
-static void snarf_refs(int head, int tag)
+static void snarf_refs(int head, int remotes)
 {
        if (head) {
                int orig_cnt = ref_name_cnt;
                for_each_ref(append_head_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
-       if (tag) {
+       if (remotes) {
                int orig_cnt = ref_name_cnt;
-               for_each_ref(append_tag_ref, NULL);
+               for_each_ref(append_remote_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
 }
@@ -554,7 +568,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        struct commit_list *list = NULL, *seen = NULL;
        unsigned int rev_mask[MAX_REVS];
        int num_rev, i, extra = 0;
-       int all_heads = 0, all_tags = 0;
+       int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
        int lifo = 1;
        char head[128];
@@ -586,12 +600,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        ac--; av++;
                        break;
                }
-               else if (!strcmp(arg, "--all"))
-                       all_heads = all_tags = 1;
-               else if (!strcmp(arg, "--heads"))
-                       all_heads = 1;
-               else if (!strcmp(arg, "--tags"))
-                       all_tags = 1;
+               else if (!strcmp(arg, "--all") || !strcmp(arg, "-a"))
+                       all_heads = all_remotes = 1;
+               else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r"))
+                       all_remotes = 1;
                else if (!strcmp(arg, "--more"))
                        extra = 1;
                else if (!strcmp(arg, "--list"))
@@ -636,11 +648,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                usage(show_branch_usage);
 
        /* If nothing is specified, show all branches by default */
-       if (ac + all_heads + all_tags == 0)
+       if (ac + all_heads + all_remotes == 0)
                all_heads = 1;
 
-       if (all_heads + all_tags)
-               snarf_refs(all_heads, all_tags);
+       if (all_heads + all_remotes)
+               snarf_refs(all_heads, all_remotes);
        if (reflog) {
                int reflen;
                if (!ac)
index 08519e7c828b7993a54b4603bf5da0b03b972653..8ffd8b2653a4879f6e5ff59d6ae8a82cae34a2ae 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -52,6 +52,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
+extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 8ad5920d2be9593ad7810e3ccefa1c8b9f4e0750..4943056c19ffb72a7cfb994daaa788ec1b01d60b 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -122,6 +122,10 @@ extern int cache_errno;
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
 #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
+#define CONFIG_ENVIRONMENT "GIT_CONFIG"
+#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
+#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
 
 extern int is_bare_git_dir(const char *dir);
 extern const char *get_git_dir(void);
index a6d543eee7831cd6a479d4cf8b2ba81a1438b298..289ef65eb1162ff8f386bf31fa6ee27008bb3096 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -868,11 +868,11 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
 
 /* merge-rebase stuff */
 
-/* bits #0..7 in revision.h */
-#define PARENT1                (1u<< 8)
-#define PARENT2                (1u<< 9)
-#define STALE          (1u<<10)
-#define RESULT         (1u<<11)
+/* bits #0..15 in revision.h */
+#define PARENT1                (1u<<16)
+#define PARENT2                (1u<<17)
+#define STALE          (1u<<18)
+#define RESULT         (1u<<19)
 
 static struct commit *interesting(struct commit_list *list)
 {
index ec8c1bff539232c581fd10ef1d01c345442a2e35..4d7ab9d9758428c003a74d9f85699d7fc6922e05 100644 (file)
@@ -93,7 +93,7 @@ inet_ntop6(src, dst, size)
         */
        char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
        struct { int base, len; } best, cur;
-       u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
+       unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
        int i;
 
        /*
index 0fd46e793d088e6567b20c7e81a5cde7023bdb58..4cfaee31361d98d2c8f68d250609b09130335baa 100644 (file)
@@ -1,17 +1,11 @@
 #include "../git-compat-util.h"
 
-void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
 {
-       int n = 0;
-       off_t current_offset = lseek(fd, 0, SEEK_CUR);
+       size_t n = 0;
 
        if (start != NULL || !(flags & MAP_PRIVATE))
-               die("Invalid usage of gitfakemmap.");
-
-       if (lseek(fd, offset, SEEK_SET) < 0) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
+               die("Invalid usage of mmap when built with NO_MMAP");
 
        start = xmalloc(length);
        if (start == NULL) {
@@ -20,14 +14,16 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
        }
 
        while (n < length) {
-               int count = read(fd, start+n, length-n);
+               ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
 
                if (count == 0) {
-                       memset(start+n, 0, length-n);
+                       memset((char *)start+n, 0, length-n);
                        break;
                }
 
                if (count < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
                        free(start);
                        errno = EACCES;
                        return MAP_FAILED;
@@ -36,15 +32,10 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
                n += count;
        }
 
-       if (current_offset != lseek(fd, current_offset, SEEK_SET)) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
-
        return start;
 }
 
-int gitfakemunmap(void *start, size_t length)
+int git_munmap(void *start, size_t length)
 {
        free(start);
        return 0;
index e86b2328abe11a7e1c299e65086a0dbcca721a6c..1662a4626e569b07d96c622b357928216a24538c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -349,10 +349,10 @@ int git_config(config_fn_t fn)
         * $GIT_CONFIG_LOCAL will make it process it in addition to the
         * global config file, the same way it would the per-repository
         * config file otherwise. */
-       filename = getenv("GIT_CONFIG");
+       filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
                home = getenv("HOME");
-               filename = getenv("GIT_CONFIG_LOCAL");
+               filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!filename)
                        filename = repo_config = xstrdup(git_path("config"));
        }
@@ -543,9 +543,9 @@ int git_config_set_multivar(const char* key, const char* value,
        char* lock_file;
        const char* last_dot = strrchr(key, '.');
 
-       config_filename = getenv("GIT_CONFIG");
+       config_filename = getenv(CONFIG_ENVIRONMENT);
        if (!config_filename) {
-               config_filename = getenv("GIT_CONFIG_LOCAL");
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!config_filename)
                        config_filename  = git_path("config");
        }
@@ -753,9 +753,9 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        int out_fd;
        char buf[1024];
 
-       config_filename = getenv("GIT_CONFIG");
+       config_filename = getenv(CONFIG_ENVIRONMENT);
        if (!config_filename) {
-               config_filename = getenv("GIT_CONFIG_LOCAL");
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!config_filename)
                        config_filename  = git_path("config");
        }
index e153d5382364def42fa062a39148a696e2e49785..7cfb3a0666ef88dca43598ebe879f53787b4efab 100644 (file)
@@ -235,9 +235,6 @@ AC_SUBST(NO_SETENV)
 #
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
 # Enable it on Windows.  By default, symrefs are still used.
-#
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
 
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
index 8b6361922fd6e6a2fcd9acb20fd54f5b645b36f0..3eb4bd19e921f293f1ed1ab1fd1bcd9f78763c4c 100644 (file)
@@ -58,8 +58,9 @@
   (with-temp-buffer
     (let* ((dir (file-name-directory file))
            (name (file-relative-name file dir)))
-      (when dir (cd dir))
-      (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
+      (and (ignore-errors
+             (when dir (cd dir))
+             (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
            (let ((str (buffer-string)))
              (and (> (length str) (length name))
                   (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
diff --git a/diff.c b/diff.c
index 91f956b23b0d641e4e330e1e22c0b8bc8cc0fed0..f14288bb8a100c43c6709f658b9a9ca44832fd7f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -860,8 +860,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        if (line[0] == '+') {
                int i, spaces = 0;
 
-               data->lineno++;
-
                /* check space before tab */
                for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
                        if (line[i] == ' ')
@@ -876,6 +874,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
                if (isspace(line[len - 1]))
                        printf("%s:%d: white space at end: %.*s\n",
                                data->filename, data->lineno, (int)len, line);
+
+               data->lineno++;
        } else if (line[0] == ' ')
                data->lineno++;
        else if (line[0] == '@') {
index 5d6a1247b4a1955dffae2b52da064a6eb489d83b..3996bce33fe11b8f0bae120fabd40a74c66de342 100644 (file)
@@ -21,7 +21,7 @@ const char *git_exec_path(void)
        if (current_exec_path)
                return current_exec_path;
 
-       env = getenv("GIT_EXEC_PATH");
+       env = getenv(EXEC_PATH_ENVIRONMENT);
        if (env && *env) {
                return env;
        }
@@ -35,7 +35,7 @@ int execv_git_cmd(const char **argv)
        char git_command[PATH_MAX + 1];
        int i;
        const char *paths[] = { current_exec_path,
-                               getenv("GIT_EXEC_PATH"),
+                               getenv(EXEC_PATH_ENVIRONMENT),
                                builtin_exec_path };
 
        for (i = 0; i < ARRAY_SIZE(paths); ++i) {
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
new file mode 100755 (executable)
index 0000000..0057f86
--- /dev/null
@@ -0,0 +1,804 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+
+sub run_cmd_pipe {
+       my $fh = undef;
+       open($fh, '-|', @_) or die;
+       return <$fh>;
+}
+
+my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
+
+if (!defined $GIT_DIR) {
+       exit(1); # rev-parse would have already said "not a git repo"
+}
+chomp($GIT_DIR);
+
+sub refresh {
+       my $fh;
+       open $fh, '-|', qw(git update-index --refresh)
+           or die;
+       while (<$fh>) {
+               ;# ignore 'needs update'
+       }
+       close $fh;
+}
+
+sub list_untracked {
+       map {
+               chomp $_;
+               $_;
+       }
+       run_cmd_pipe(qw(git ls-files --others
+                       --exclude-per-directory=.gitignore),
+                    "--exclude-from=$GIT_DIR/info/exclude",
+                    '--', @_);
+}
+
+my $status_fmt = '%12s %12s %s';
+my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+
+# Returns list of hashes, contents of each of which are:
+# PRINT:       print message
+# VALUE:       pathname
+# BINARY:      is a binary path
+# INDEX:       is index different from HEAD?
+# FILE:                is file different from index?
+# INDEX_ADDDEL:        is it add/delete between HEAD and index?
+# FILE_ADDDEL: is it add/delete between index and file?
+
+sub list_modified {
+       my ($only) = @_;
+       my (%data, @return);
+       my ($add, $del, $adddel, $file);
+
+       for (run_cmd_pipe(qw(git diff-index --cached
+                            --numstat --summary HEAD))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file} = {
+                               INDEX => $change,
+                               BINARY => $bin,
+                               FILE => 'nothing',
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{INDEX_ADDDEL} = $adddel;
+               }
+       }
+
+       for (run_cmd_pipe(qw(git diff-files --numstat --summary))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       if (!exists $data{$file}) {
+                               $data{$file} = +{
+                                       INDEX => 'unchanged',
+                                       BINARY => 0,
+                               };
+                       }
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file}{FILE} = $change;
+                       if ($bin) {
+                               $data{$file}{BINARY} = 1;
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{FILE_ADDDEL} = $adddel;
+               }
+       }
+
+       for (sort keys %data) {
+               my $it = $data{$_};
+
+               if ($only) {
+                       if ($only eq 'index-only') {
+                               next if ($it->{INDEX} eq 'unchanged');
+                       }
+                       if ($only eq 'file-only') {
+                               next if ($it->{FILE} eq 'nothing');
+                       }
+               }
+               push @return, +{
+                       VALUE => $_,
+                       PRINT => (sprintf $status_fmt,
+                                 $it->{INDEX}, $it->{FILE}, $_),
+                       %$it,
+               };
+       }
+       return @return;
+}
+
+sub find_unique {
+       my ($string, @stuff) = @_;
+       my $found = undef;
+       for (my $i = 0; $i < @stuff; $i++) {
+               my $it = $stuff[$i];
+               my $hit = undef;
+               if (ref $it) {
+                       if ((ref $it) eq 'ARRAY') {
+                               $it = $it->[0];
+                       }
+                       else {
+                               $it = $it->{VALUE};
+                       }
+               }
+               eval {
+                       if ($it =~ /^$string/) {
+                               $hit = 1;
+                       };
+               };
+               if (defined $hit && defined $found) {
+                       return undef;
+               }
+               if ($hit) {
+                       $found = $i + 1;
+               }
+       }
+       return $found;
+}
+
+sub list_and_choose {
+       my ($opts, @stuff) = @_;
+       my (@chosen, @return);
+       my $i;
+
+      TOPLOOP:
+       while (1) {
+               my $last_lf = 0;
+
+               if ($opts->{HEADER}) {
+                       if (!$opts->{LIST_FLAT}) {
+                               print "     ";
+                       }
+                       print "$opts->{HEADER}\n";
+               }
+               for ($i = 0; $i < @stuff; $i++) {
+                       my $chosen = $chosen[$i] ? '*' : ' ';
+                       my $print = $stuff[$i];
+                       if (ref $print) {
+                               if ((ref $print) eq 'ARRAY') {
+                                       $print = $print->[0];
+                               }
+                               else {
+                                       $print = $print->{PRINT};
+                               }
+                       }
+                       printf("%s%2d: %s", $chosen, $i+1, $print);
+                       if (($opts->{LIST_FLAT}) &&
+                           (($i + 1) % ($opts->{LIST_FLAT}))) {
+                               print "\t";
+                               $last_lf = 0;
+                       }
+                       else {
+                               print "\n";
+                               $last_lf = 1;
+                       }
+               }
+               if (!$last_lf) {
+                       print "\n";
+               }
+
+               return if ($opts->{LIST_ONLY});
+
+               print $opts->{PROMPT};
+               if ($opts->{SINGLETON}) {
+                       print "> ";
+               }
+               else {
+                       print ">> ";
+               }
+               my $line = <STDIN>;
+               last if (!$line);
+               chomp $line;
+               my $donesomething = 0;
+               for my $choice (split(/[\s,]+/, $line)) {
+                       my $choose = 1;
+                       my ($bottom, $top);
+
+                       # Input that begins with '-'; unchoose
+                       if ($choice =~ s/^-//) {
+                               $choose = 0;
+                       }
+                       # A range can be specified like 5-7
+                       if ($choice =~ /^(\d+)-(\d+)$/) {
+                               ($bottom, $top) = ($1, $2);
+                       }
+                       elsif ($choice =~ /^\d+$/) {
+                               $bottom = $top = $choice;
+                       }
+                       elsif ($choice eq '*') {
+                               $bottom = 1;
+                               $top = 1 + @stuff;
+                       }
+                       else {
+                               $bottom = $top = find_unique($choice, @stuff);
+                               if (!defined $bottom) {
+                                       print "Huh ($choice)?\n";
+                                       next TOPLOOP;
+                               }
+                       }
+                       if ($opts->{SINGLETON} && $bottom != $top) {
+                               print "Huh ($choice)?\n";
+                               next TOPLOOP;
+                       }
+                       for ($i = $bottom-1; $i <= $top-1; $i++) {
+                               next if (@stuff <= $i);
+                               $chosen[$i] = $choose;
+                               $donesomething++;
+                       }
+               }
+               last if (!$donesomething || $opts->{IMMEDIATE});
+       }
+       for ($i = 0; $i < @stuff; $i++) {
+               if ($chosen[$i]) {
+                       push @return, $stuff[$i];
+               }
+       }
+       return @return;
+}
+
+sub status_cmd {
+       list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
+                       list_modified());
+       print "\n";
+}
+
+sub say_n_paths {
+       my $did = shift @_;
+       my $cnt = scalar @_;
+       print "$did ";
+       if (1 < $cnt) {
+               print "$cnt paths\n";
+       }
+       else {
+               print "one path\n";
+       }
+}
+
+sub update_cmd {
+       my @mods = list_modified('file-only');
+       return if (!@mods);
+
+       my @update = list_and_choose({ PROMPT => 'Update',
+                                      HEADER => $status_head, },
+                                    @mods);
+       if (@update) {
+               system(qw(git update-index --add --),
+                      map { $_->{VALUE} } @update);
+               say_n_paths('updated', @update);
+       }
+       print "\n";
+}
+
+sub revert_cmd {
+       my @update = list_and_choose({ PROMPT => 'Revert',
+                                      HEADER => $status_head, },
+                                    list_modified());
+       if (@update) {
+               my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+                                        map { $_->{VALUE} } @update);
+               my $fh;
+               open $fh, '|-', qw(git update-index --index-info)
+                   or die;
+               for (@lines) {
+                       print $fh $_;
+               }
+               close($fh);
+               for (@update) {
+                       if ($_->{INDEX_ADDDEL} &&
+                           $_->{INDEX_ADDDEL} eq 'create') {
+                               system(qw(git update-index --force-remove --),
+                                      $_->{VALUE});
+                               print "note: $_->{VALUE} is untracked now.\n";
+                       }
+               }
+               refresh();
+               say_n_paths('reverted', @update);
+       }
+       print "\n";
+}
+
+sub add_untracked_cmd {
+       my @add = list_and_choose({ PROMPT => 'Add untracked' },
+                                 list_untracked());
+       if (@add) {
+               system(qw(git update-index --add --), @add);
+               say_n_paths('added', @add);
+       }
+       print "\n";
+}
+
+sub parse_diff {
+       my ($path) = @_;
+       my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
+       my (@hunk) = { TEXT => [] };
+
+       for (@diff) {
+               if (/^@@ /) {
+                       push @hunk, { TEXT => [] };
+               }
+               push @{$hunk[-1]{TEXT}}, $_;
+       }
+       return @hunk;
+}
+
+sub hunk_splittable {
+       my ($text) = @_;
+
+       my @s = split_hunk($text);
+       return (1 < @s);
+}
+
+sub parse_hunk_header {
+       my ($line) = @_;
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+           $line =~ /^@@ -(\d+)(?:,(\d+)) \+(\d+)(?:,(\d+)) @@/;
+       return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
+}
+
+sub split_hunk {
+       my ($text) = @_;
+       my @split = ();
+
+       # If there are context lines in the middle of a hunk,
+       # it can be split, but we would need to take care of
+       # overlaps later.
+
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $hunk_start = 1;
+       my $next_hunk_start;
+
+      OUTER:
+       while (1) {
+               my $next_hunk_start = undef;
+               my $i = $hunk_start - 1;
+               my $this = +{
+                       TEXT => [],
+                       OLD => $o_ofs,
+                       NEW => $n_ofs,
+                       OCNT => 0,
+                       NCNT => 0,
+                       ADDDEL => 0,
+                       POSTCTX => 0,
+               };
+
+               while (++$i < @$text) {
+                       my $line = $text->[$i];
+                       if ($line =~ /^ /) {
+                               if ($this->{ADDDEL} &&
+                                   !defined $next_hunk_start) {
+                                       # We have seen leading context and
+                                       # adds/dels and then here is another
+                                       # context, which is trailing for this
+                                       # split hunk and leading for the next
+                                       # one.
+                                       $next_hunk_start = $i;
+                               }
+                               push @{$this->{TEXT}}, $line;
+                               $this->{OCNT}++;
+                               $this->{NCNT}++;
+                               if (defined $next_hunk_start) {
+                                       $this->{POSTCTX}++;
+                               }
+                               next;
+                       }
+
+                       # add/del
+                       if (defined $next_hunk_start) {
+                               # We are done with the current hunk and
+                               # this is the first real change for the
+                               # next split one.
+                               $hunk_start = $next_hunk_start;
+                               $o_ofs = $this->{OLD} + $this->{OCNT};
+                               $n_ofs = $this->{NEW} + $this->{NCNT};
+                               $o_ofs -= $this->{POSTCTX};
+                               $n_ofs -= $this->{POSTCTX};
+                               push @split, $this;
+                               redo OUTER;
+                       }
+                       push @{$this->{TEXT}}, $line;
+                       $this->{ADDDEL}++;
+                       if ($line =~ /^-/) {
+                               $this->{OCNT}++;
+                       }
+                       else {
+                               $this->{NCNT}++;
+                       }
+               }
+
+               push @split, $this;
+               last;
+       }
+
+       for my $hunk (@split) {
+               $o_ofs = $hunk->{OLD};
+               $n_ofs = $hunk->{NEW};
+               $o_cnt = $hunk->{OCNT};
+               $n_cnt = $hunk->{NCNT};
+
+               my $head = ("@@ -$o_ofs" .
+                           (($o_cnt != 1) ? ",$o_cnt" : '') .
+                           " +$n_ofs" .
+                           (($n_cnt != 1) ? ",$n_cnt" : '') .
+                           " @@\n");
+               unshift @{$hunk->{TEXT}}, $head;
+       }
+       return map { $_->{TEXT} } @split;
+}
+
+sub find_last_o_ctx {
+       my ($it) = @_;
+       my $text = $it->{TEXT};
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $i = @{$text};
+       my $last_o_ctx = $o_ofs + $o_cnt;
+       while (0 < --$i) {
+               my $line = $text->[$i];
+               if ($line =~ /^ /) {
+                       $last_o_ctx--;
+                       next;
+               }
+               last;
+       }
+       return $last_o_ctx;
+}
+
+sub merge_hunk {
+       my ($prev, $this) = @_;
+       my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
+           parse_hunk_header($prev->{TEXT}[0]);
+       my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
+           parse_hunk_header($this->{TEXT}[0]);
+
+       my (@line, $i, $ofs, $o_cnt, $n_cnt);
+       $ofs = $o0_ofs;
+       $o_cnt = $n_cnt = 0;
+       for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
+               my $line = $prev->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+
+               last if ($o1_ofs <= $ofs);
+
+               $o_cnt++;
+               $ofs++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+
+       for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
+               my $line = $this->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+               $ofs++;
+               $o_cnt++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+       my $head = ("@@ -$o0_ofs" .
+                   (($o_cnt != 1) ? ",$o_cnt" : '') .
+                   " +$n0_ofs" .
+                   (($n_cnt != 1) ? ",$n_cnt" : '') .
+                   " @@\n");
+       @{$prev->{TEXT}} = ($head, @line);
+}
+
+sub coalesce_overlapping_hunks {
+       my (@in) = @_;
+       my @out = ();
+
+       my ($last_o_ctx);
+
+       for (grep { $_->{USE} } @in) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+               if (defined $last_o_ctx &&
+                   $o_ofs <= $last_o_ctx) {
+                       merge_hunk($out[-1], $_);
+               }
+               else {
+                       push @out, $_;
+               }
+               $last_o_ctx = find_last_o_ctx($out[-1]);
+       }
+       return @out;
+}
+
+sub help_patch_cmd {
+       print <<\EOF ;
+y - stage this hunk
+n - do not stage this hunk
+a - stage this and all the remaining hunks
+d - do not stage this hunk nor any of the remaining hunks
+j - leave this hunk undecided, see next undecided hunk
+J - leave this hunk undecided, see next hunk
+k - leave this hunk undecided, see previous undecided hunk
+K - leave this hunk undecided, see previous hunk
+s - split the current hunk into smaller hunks
+EOF
+}
+
+sub patch_update_cmd {
+       my @mods = list_modified('file-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+
+       my ($it) = list_and_choose({ PROMPT => 'Patch update',
+                                    SINGLETON => 1,
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!$it);
+
+       my ($ix, $num);
+       my $path = $it->{VALUE};
+       my ($head, @hunk) = parse_diff($path);
+       for (@{$head->{TEXT}}) {
+               print;
+       }
+       $num = scalar @hunk;
+       $ix = 0;
+
+       while (1) {
+               my ($prev, $next, $other, $undecided, $i);
+               $other = '';
+
+               if ($num <= $ix) {
+                       $ix = 0;
+               }
+               for ($i = 0; $i < $ix; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $prev = 1;
+                               $other .= '/k';
+                               last;
+                       }
+               }
+               if ($ix) {
+                       $other .= '/K';
+               }
+               for ($i = $ix + 1; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $next = 1;
+                               $other .= '/j';
+                               last;
+                       }
+               }
+               if ($ix < $num - 1) {
+                       $other .= '/J';
+               }
+               for ($i = 0; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $undecided = 1;
+                               last;
+                       }
+               }
+               last if (!$undecided);
+
+               if (hunk_splittable($hunk[$ix]{TEXT})) {
+                       $other .= '/s';
+               }
+               for (@{$hunk[$ix]{TEXT}}) {
+                       print;
+               }
+               print "Stage this hunk [y/n/a/d$other/?]? ";
+               my $line = <STDIN>;
+               if ($line) {
+                       if ($line =~ /^y/i) {
+                               $hunk[$ix]{USE} = 1;
+                       }
+                       elsif ($line =~ /^n/i) {
+                               $hunk[$ix]{USE} = 0;
+                       }
+                       elsif ($line =~ /^a/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 1;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($line =~ /^d/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 0;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /K/ && $line =~ /^K/) {
+                               $ix--;
+                               next;
+                       }
+                       elsif ($other =~ /J/ && $line =~ /^J/) {
+                               $ix++;
+                               next;
+                       }
+                       elsif ($other =~ /k/ && $line =~ /^k/) {
+                               while (1) {
+                                       $ix--;
+                                       last if (!$ix ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /j/ && $line =~ /^j/) {
+                               while (1) {
+                                       $ix++;
+                                       last if ($ix >= $num ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /s/ && $line =~ /^s/) {
+                               my @split = split_hunk($hunk[$ix]{TEXT});
+                               if (1 < @split) {
+                                       print "Split into ",
+                                       scalar(@split), " hunks.\n";
+                               }
+                               splice(@hunk, $ix, 1,
+                                      map { +{ TEXT => $_, USE => undef } }
+                                      @split);
+                               $num = scalar @hunk;
+                               next;
+                       }
+                       else {
+                               help_patch_cmd($other);
+                               next;
+                       }
+                       # soft increment
+                       while (1) {
+                               $ix++;
+                               last if ($ix >= $num ||
+                                        !defined $hunk[$ix]{USE});
+                       }
+               }
+       }
+
+       @hunk = coalesce_overlapping_hunks(@hunk);
+
+       my ($o_lofs, $n_lofs) = (0, 0);
+       my @result = ();
+       for (@hunk) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+
+               if (!$_->{USE}) {
+                       if (!defined $o_cnt) { $o_cnt = 1; }
+                       if (!defined $n_cnt) { $n_cnt = 1; }
+
+                       # We would have added ($n_cnt - $o_cnt) lines
+                       # to the postimage if we were to use this hunk,
+                       # but we didn't.  So the line number that the next
+                       # hunk starts at would be shifted by that much.
+                       $n_lofs -= ($n_cnt - $o_cnt);
+                       next;
+               }
+               else {
+                       if ($n_lofs) {
+                               $n_ofs += $n_lofs;
+                               $text->[0] = ("@@ -$o_ofs" .
+                                             ((defined $o_cnt)
+                                              ? ",$o_cnt" : '') .
+                                             " +$n_ofs" .
+                                             ((defined $n_cnt)
+                                              ? ",$n_cnt" : '') .
+                                             " @@\n");
+                       }
+                       for (@$text) {
+                               push @result, $_;
+                       }
+               }
+       }
+
+       if (@result) {
+               my $fh;
+
+               open $fh, '|-', qw(git apply --cached);
+               for (@{$head->{TEXT}}, @result) {
+                       print $fh $_;
+               }
+               if (!close $fh) {
+                       for (@{$head->{TEXT}}, @result) {
+                               print STDERR $_;
+                       }
+               }
+               refresh();
+       }
+
+       print "\n";
+}
+
+sub diff_cmd {
+       my @mods = list_modified('index-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+       my (@them) = list_and_choose({ PROMPT => 'Review diff',
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!@them);
+       system(qw(git diff-index -p --cached HEAD --),
+              map { $_->{VALUE} } @them);
+}
+
+sub quit_cmd {
+       print "Bye.\n";
+       exit(0);
+}
+
+sub help_cmd {
+       print <<\EOF ;
+status        - show paths with changes
+update        - add working tree state to the staged set of changes
+revert        - revert staged set of changes back to the HEAD version
+patch         - pick hunks and update selectively
+diff         - view diff between HEAD and index
+add untracked - add contents of untracked files to the staged set of changes
+EOF
+}
+
+sub main_loop {
+       my @cmd = ([ 'status', \&status_cmd, ],
+                  [ 'update', \&update_cmd, ],
+                  [ 'revert', \&revert_cmd, ],
+                  [ 'add untracked', \&add_untracked_cmd, ],
+                  [ 'patch', \&patch_update_cmd, ],
+                  [ 'diff', \&diff_cmd, ],
+                  [ 'quit', \&quit_cmd, ],
+                  [ 'help', \&help_cmd, ],
+       );
+       while (1) {
+               my ($it) = list_and_choose({ PROMPT => 'What now',
+                                            SINGLETON => 1,
+                                            LIST_FLAT => 4,
+                                            HEADER => '*** Commands ***',
+                                            IMMEDIATE => 1 }, @cmd);
+               if ($it) {
+                       eval {
+                               $it->[1]->();
+                       };
+                       if ($@) {
+                               print "$@";
+                       }
+               }
+       }
+}
+
+my @z;
+
+refresh();
+status_cmd();
+main_loop();
index 5df6787a3f285f18d3a41c1ab2f0579f407e02de..0126a77b924e7f1fe7f3913b84350ab35e6e043a 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -401,14 +401,14 @@ do
                changed="$(git-diff-index --cached --name-only HEAD)"
                if test '' = "$changed"
                then
-                       echo "No changes - did you forget update-index?"
+                       echo "No changes - did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                unmerged=$(git-ls-files -u)
                if test -n "$unmerged"
                then
                        echo "You still have unmerged paths in your index"
-                       echo "did you forget update-index?"
+                       echo "did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                apply_status=0
index 4192a99fec1e137599b250a4f65c1b1e4201c601..92ec069a3acacc2d12c2c709311969f3d0aa4153 100755 (executable)
@@ -146,8 +146,11 @@ fi
 
 [ -z "$branch$newbranch" ] &&
        [ "$new" != "$old" ] &&
-       die "git checkout: to checkout the requested commit you need to specify 
-              a name for a new branch which is created and switched to"
+       die "git checkout: provided reference cannot be checked out directly
+
+  You need -b to associate a new branch with the wanted checkout. Example:
+  git checkout -b <new_branch_name> $arg
+"
 
 if [ "X$old" = X ]
 then
index 1f5d07a057ed04dd9ffc7eb705d0c752d250bf72..490f3e48db02d01cb480047c011aa7b670c431bc 100755 (executable)
@@ -14,7 +14,7 @@ die() {
 }
 
 usage() {
-       die "Usage: $0 [--template=<template_directory>] [--no-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+       die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
 }
 
 get_repo_base() {
@@ -137,11 +137,9 @@ while
        *,--template=*)
          template="$1" ;;
        *,-q|*,--quiet) quiet=-q ;;
-       *,--use-separate-remote)
-               # default
-               use_separate_remote=t ;;
+       *,--use-separate-remote) ;;
        *,--no-separate-remote)
-               use_separate_remote= ;;
+               die "clones are always made with separate-remote layout" ;;
        1,--reference) usage ;;
        *,--reference)
                shift; reference="$1" ;;
@@ -327,12 +325,8 @@ cd "$D" || exit
 
 if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
 then
-       # Figure out which remote branch HEAD points at.
-       case "$use_separate_remote" in
-       '')     remote_top=refs/heads ;;
-       *)      remote_top="refs/remotes/$origin" ;;
-       esac
-
+       # a non-bare repository is always in separate-remote layout
+       remote_top="refs/remotes/$origin"
        head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
        case "$head_sha1" in
        'ref: refs/'*)
@@ -366,41 +360,26 @@ then
                )
        )
 
-       # Write out remotes/$origin file, and update our "$head_points_at".
+       # Write out remote.$origin config, and update our "$head_points_at".
        case "$head_points_at" in
        ?*)
-               mkdir -p "$GIT_DIR/remotes" &&
+               # Local default branch
                git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
-               case "$use_separate_remote" in
-               t)      origin_track="$remote_top/$head_points_at"
-                       git-update-ref HEAD "$head_sha1" ;;
-               *)      origin_track="$remote_top/$origin"
-                       git-update-ref "refs/heads/$origin" "$head_sha1" ;;
-               esac &&
+
+               # Tracking branch for the primary branch at the remote.
+               origin_track="$remote_top/$head_points_at" &&
+               git-update-ref HEAD "$head_sha1" &&
+
+               # Upstream URL
                git-repo-config remote."$origin".url "$repo" &&
+
+               # Set up the mappings to track the remote branches.
                git-repo-config remote."$origin".fetch \
-                       "refs/heads/$head_points_at:$origin_track" &&
-               (cd "$GIT_DIR/$remote_top" && find . -type f -print) |
-               while read dotslref
-               do
-                       name=`expr "$dotslref" : './\(.*\)'`
-                       if test "z$head_points_at" = "z$name"
-                       then
-                               continue
-                       fi
-                       if test "$use_separate_remote" = '' &&
-                          test "z$origin" = "z$name"
-                       then
-                               continue
-                       fi
-                       git-repo-config remote."$origin".fetch "refs/heads/${name}:$remote_top/${name}" '^$'
-               done &&
-               case "$use_separate_remote" in
-               t)
-                       rm -f "refs/remotes/$origin/HEAD"
-                       git-symbolic-ref "refs/remotes/$origin/HEAD" \
-                               "refs/remotes/$origin/$head_points_at"
-               esac &&
+                       "refs/heads/*:$remote_top/*" '^$' &&
+               rm -f "refs/remotes/$origin/HEAD"
+               git-symbolic-ref "refs/remotes/$origin/HEAD" \
+                       "refs/remotes/$origin/$head_points_at" &&
+
                git-repo-config branch."$head_points_at".remote "$origin" &&
                git-repo-config branch."$head_points_at".merge "refs/heads/$head_points_at"
        esac
index bc296b3a45708296c3f14f5af53624801045e554..5d9eb2615b2e21979f547199121828aafbf02135 100644 (file)
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
+#endif
 #define _GNU_SOURCE
 #define _BSD_SOURCE
 
 extern void usage(const char *err) NORETURN;
 extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern void warn(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 extern void set_usage_routine(void (*routine)(const char *err) NORETURN);
 extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
 extern void set_error_routine(void (*routine)(const char *err, va_list params));
+extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 
 #ifdef NO_MMAP
 
@@ -83,10 +87,10 @@ extern void set_error_routine(void (*routine)(const char *err, va_list params));
 #define MAP_FAILED ((void*)-1)
 #endif
 
-#define mmap gitfakemmap
-#define munmap gitfakemunmap
-extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
-extern int gitfakemunmap(void *start, size_t length);
+#define mmap git_mmap
+#define munmap git_munmap
+extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int git_munmap(void *start, size_t length);
 
 #else /* NO_MMAP */
 
index c49e4c65af606496c7e65b5147e36fee105d9ae7..7d62d7902cb1d7cd2749091b86d532306bea89b3 100755 (executable)
@@ -104,7 +104,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        # Be careful for funny filename such as "-L" in "$4", which
        # would confuse "merge" greatly.
        src1=`git-unpack-file $2`
-       merge "$src1" "$orig" "$src2"
+       git-merge-file "$src1" "$orig" "$src2"
        ret=$?
 
        # Create the working tree file, using "our tree" version from the
index 4ebfcf65d99f7743048df901b38969c28cc11edb..7dd0a112368a8b3672a98fdfcdb5290146cefeba 100755 (executable)
@@ -32,7 +32,7 @@ savestate() {
 restorestate() {
         if test -f "$GIT_DIR/MERGE_SAVE"
        then
-               git reset --hard $head
+               git reset --hard $head >/dev/null
                cpio -iuv <"$GIT_DIR/MERGE_SAVE"
                git-update-index --refresh >/dev/null
        fi
@@ -221,6 +221,8 @@ do
        remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
            die "$remote - not something we can merge"
        remoteheads="${remoteheads}$remotehead "
+       eval GITHEAD_$remotehead='"$remote"'
+       export GITHEAD_$remotehead
 done
 set x $remoteheads ; shift
 
index bc881cc5f9d932c40f10bf0448aafa3263724c35..aaef861ada3f6bac2a67a7eb0de20ab2ea95b6bf 100755 (executable)
@@ -7,18 +7,7 @@ GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
 get_data_source () {
        case "$1" in
        */*)
-               # Not so fast.  This could be the partial URL shorthand...
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               if test "$(git-repo-config --get "remote.$token.url")"
-               then
-                       echo config-partial
-               elif test -f "$GIT_DIR/branches/$token"
-               then
-                       echo branches-partial
-               else
-                       echo ''
-               fi
+               echo ''
                ;;
        *)
                if test "$(git-repo-config --get "remote.$1.url")"
@@ -40,12 +29,7 @@ get_remote_url () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
        '')
-               echo "$1" ;;
-       config-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(git-repo-config --get "remote.$token.url")
-               echo "$url/$remainder"
+               echo "$1"
                ;;
        config)
                git-repo-config --get "remote.$1.url"
@@ -54,14 +38,10 @@ get_remote_url () {
                sed -ne '/^URL: */{
                        s///p
                        q
-               }' "$GIT_DIR/remotes/$1" ;;
+               }' "$GIT_DIR/remotes/$1"
+               ;;
        branches)
-               sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;;
-       branches-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token")
-               echo "$url/$remainder"
+               sed -e 's/#.*//' "$GIT_DIR/branches/$1"
                ;;
        *)
                die "internal error: get-remote-url $1" ;;
@@ -77,7 +57,7 @@ get_default_remote () {
 get_remote_default_refs_for_push () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches | branches-partial)
+       '' | branches)
                ;; # no default push mapping, just send matching refs.
        config)
                git-repo-config --get-all "remote.$1.push" ;;
@@ -132,11 +112,12 @@ canon_refs_list_for_fetch () {
        # or the first one otherwise; add prefix . to the rest
        # to prevent the secondary branches to be merged by default.
        merge_branches=
-       found_mergeref=
        curr_branch=
        if test "$1" = "-d"
        then
                shift ; remote="$1" ; shift
+               set x $(expand_refs_wildcard "$@")
+               shift
                if test "$remote" = "$(get_default_remote)"
                then
                        curr_branch=$(git-symbolic-ref HEAD | \
@@ -144,8 +125,6 @@ canon_refs_list_for_fetch () {
                        merge_branches=$(git-repo-config \
                            --get-all "branch.${curr_branch}.merge")
                fi
-               set x $(expand_refs_wildcard "$@")
-               shift
        fi
        for ref
        do
@@ -171,10 +150,6 @@ canon_refs_list_for_fetch () {
                            dot_prefix= && break
                        done
                fi
-               if test -z $dot_prefix
-               then
-                       found_mergeref=true
-               fi
                case "$remote" in
                '') remote=HEAD ;;
                refs/heads/* | refs/tags/* | refs/remotes/*) ;;
@@ -195,18 +170,13 @@ canon_refs_list_for_fetch () {
                fi
                echo "${dot_prefix}${force}${remote}:${local}"
        done
-       if test -z "$found_mergeref" -a "$curr_branch"
-       then
-               echo >&2 "Warning: No merge candidate found because value of config option
-         \"branch.${curr_branch}.merge\" does not match any remote branch fetched."
-       fi
 }
 
 # Returns list of src: (no store), or src:dst (store)
 get_remote_default_refs_for_fetch () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches-partial)
+       '')
                echo "HEAD:" ;;
        config)
                canon_refs_list_for_fetch -d "$1" \
index e23beb685d15447ac114c58d1795ed15bef88c9d..1703091bbb988e22f39b2c8d4c70ec7340a2f109 100755 (executable)
@@ -76,6 +76,10 @@ merge_head=$(sed -e '/       not-for-merge   /d' \
 
 case "$merge_head" in
 '')
+       curr_branch=$(git-symbolic-ref HEAD | \
+               sed -e 's|^refs/heads/||')
+       echo >&2 "Warning: No merge candidate found because value of config option
+         \"branch.${curr_branch}.merge\" does not match any remote branch fetched."
        echo >&2 "No changes."
        exit 0
        ;;
index 2b4f3477fa941afe4f6450f74f6f8cf43a7567c0..ece31425d08a7fc2758b769afac65c37956d20cb 100755 (executable)
@@ -292,6 +292,7 @@ then
 fi
 
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
+echo "First, rewinding head to replay your work on top of it..."
 git-reset --hard "$onto"
 
 # If the $onto is a proper descendant of the tip of the branch, then
index 8d95e3748d2cfdaa248dee385766dedde5ff4075..2379db082f956e68a360cfbd667761490ecad5dd 100755 (executable)
@@ -86,7 +86,12 @@ update_ref_status=$?
 
 case "$reset_type" in
 --hard )
-       ;; # Nothing else to do
+       test $update_ref_status = 0 && {
+               echo -n "HEAD is now at "
+               GIT_PAGER= git log --max-count=1 --pretty=oneline \
+                       --abbrev-commit HEAD
+       }
+       ;;
 --soft )
        ;; # Nothing else to do
 --mixed )
index 6eab3c72df0db0994a352836018a7fc35ba1eae3..50cc47b0634201e7acaaa0a0642be779aaf7c2da 100755 (executable)
@@ -155,7 +155,7 @@ Conflicts:
                uniq
            } >>"$GIT_DIR/MERGE_MSG"
            echo >&2 "Automatic $me failed.  After resolving the conflicts,"
-           echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
+           echo >&2 "mark the corrected paths with 'git-add <paths>'"
            echo >&2 "and commit the result."
            case "$me" in
            cherry-pick)
index 73ab8d873fb33f4447c78b5eab4781e245697c10..4288a05c166b0b75bba387c099daa8622fea487a 100755 (executable)
 );
 
 sub fatal (@) { print STDERR @_; exit 1 }
-# If SVN:: library support is added, please make the dependencies
-# optional and preserve the capability to use the command-line client.
-# use eval { require SVN::... } to make it lazy load
-# We don't use any modules not in the standard Perl distribution:
+require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
+require SVN::Ra;
+require SVN::Delta;
+if ($SVN::Core::VERSION lt '1.1.0') {
+       fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
+}
+push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
+*SVN::Git::Fetcher::process_rm = *process_rm;
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
-use File::Spec qw//;
-use File::Copy qw/copy/;
 use POSIX qw/strftime/;
 use IPC::Open3;
 use Memoize;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 memoize('revisions_eq');
 memoize('cmt_metadata');
 memoize('get_commit_time');
 
-my ($SVN, $_use_lib);
-
-sub nag_lib {
-       print STDERR <<EOF;
-! Please consider installing the SVN Perl libraries (version 1.1.0 or
-! newer).  You will generally get better performance and fewer bugs,
-! especially if you:
-! 1) have a case-insensitive filesystem
-! 2) replace symlinks with files (and vice-versa) in commits
-
-EOF
-}
-
-$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
-libsvn_load();
-nag_lib() unless $_use_lib;
+my ($SVN);
 
 my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
 my $sha1 = qr/[a-f\d]{40}/;
@@ -82,7 +72,7 @@ sub nag_lib {
        $_username, $_config_dir, $_no_auth_cache, $_xfer_delta,
        $_pager, $_color);
 my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_co_url_revs, $_svn_pg_peg_revs, $_svn_can_do_switch);
+my ($_svn_can_do_switch);
 my @repo_path_split_cache;
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
@@ -117,7 +107,12 @@ sub nag_lib {
        init => [ \&init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
-       commit => [ \&commit, "Commit git revisions to SVN",
+       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+                       { 'merge|m|M' => \$_merge,
+                         'strategy|s=s' => \$_strategy,
+                         'dry-run|n' => \$_dry_run,
+                       %cmt_opts, %fc_opts } ],
+       'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
                        {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
@@ -160,11 +155,6 @@ sub nag_lib {
                          'file|F=s' => \$_file,
                          'revision|r=s' => \$_revision,
                        %cmt_opts } ],
-       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
-                       { 'merge|m|M' => \$_merge,
-                         'strategy|s=s' => \$_strategy,
-                         'dry-run|n' => \$_dry_run,
-                       %cmt_opts } ],
 );
 
 my $cmd;
@@ -191,7 +181,6 @@ sub nag_lib {
 init_vars();
 load_authors() if $_authors;
 load_all_refs() if $_branch_all_refs;
-svn_compat_check() unless $_use_lib;
 migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
 $cmd{$cmd}->[0]->(@ARGV);
 exit 0;
@@ -232,28 +221,27 @@ sub version {
 }
 
 sub rebuild {
-       if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
+       if (!verify_ref("refs/remotes/$GIT_SVN^0")) {
                copy_remote_ref();
        }
        $SVN_URL = shift or undef;
        my $newest_rev = 0;
        if ($_upgrade) {
-               sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+               command_noisy('update-ref',"refs/remotes/$GIT_SVN","
+                             $GIT_SVN-HEAD");
        } else {
                check_upgrade_needed();
        }
 
-       my $pid = open(my $rev_list,'-|');
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
-       }
+       my ($rev_list, $ctx) = command_output_pipe("rev-list",
+                                                  "refs/remotes/$GIT_SVN");
        my $latest;
        while (<$rev_list>) {
                chomp;
                my $c = $_;
                croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-               my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
+               my @commit = grep(/^git-svn-id: /,
+                                 command(qw/cat-file commit/, $c));
                next if (!@commit); # skip merges
                my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
                if (!defined $rev || !$uuid) {
@@ -279,33 +267,7 @@ sub rebuild {
                print "r$rev = $c\n";
                $newest_rev = $rev if ($rev > $newest_rev);
        }
-       close $rev_list or croak $?;
-
-       goto out if $_use_lib;
-       if (!chdir $SVN_WC) {
-               svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
-               chdir $SVN_WC or croak $!;
-       }
-
-       $pid = fork;
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @svn_up = qw(svn up);
-               push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-               sys(@svn_up,"-r$newest_rev");
-               $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
-               index_changes();
-               exec('git-write-tree') or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-out:
-       if ($_upgrade) {
-               print STDERR <<"";
-Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
-when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
-
-       }
+       command_close_pipe($rev_list, $ctx);
 }
 
 sub init {
@@ -323,10 +285,10 @@ sub init {
 
        $SVN_URL = $url;
        unless (-d $GIT_DIR) {
-               my @init_db = ('git-init-db');
+               my @init_db = ('init-db');
                push @init_db, "--template=$_template" if defined $_template;
                push @init_db, "--shared" if defined $_shared;
-               sys(@init_db);
+               command_noisy(@init_db);
        }
        setup_git_svn();
 }
@@ -334,70 +296,13 @@ sub init {
 sub fetch {
        check_upgrade_needed();
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
-       if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
-                                               refs/heads/master^0))) {
-               sys(qw(git-update-ref refs/heads/master),$ret->{commit});
+       my $ret = fetch_lib(@_);
+       if ($ret->{commit} && !verify_ref('refs/heads/master^0')) {
+               command_noisy(qw(update-ref refs/heads/master),$ret->{commit});
        }
        return $ret;
 }
 
-sub fetch_cmd {
-       my (@parents) = @_;
-       my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
-       unless ($_revision) {
-               $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
-       }
-       push @log_args, "-r$_revision";
-       push @log_args, '--stop-on-copy' unless $_no_stop_copy;
-
-       my $svn_log = svn_log_raw(@log_args);
-
-       my $base = next_log_entry($svn_log) or croak "No base revision!\n";
-       # don't need last_revision from grab_base_rev() because
-       # user could've specified a different revision to skip (they
-       # didn't want to import certain revisions into git for whatever
-       # reason, so trust $base->{revision} instead.
-       my (undef, $last_commit) = svn_grab_base_rev();
-       unless (-d $SVN_WC) {
-               svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               $last_commit = git_commit($base, @parents);
-               assert_tree($last_commit);
-       } else {
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               # looks like a user manually cp'd and svn switch'ed
-               unless ($last_commit) {
-                       sys(qw/svn revert -R ./);
-                       assert_svn_wc_clean($base->{revision});
-                       $last_commit = git_commit($base, @parents);
-                       assert_tree($last_commit);
-               }
-       }
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       my $last = $base;
-       while (my $log_msg = next_log_entry($svn_log)) {
-               if ($last->{revision} >= $log_msg->{revision}) {
-                       croak "Out of order: last >= current: ",
-                               "$last->{revision} >= $log_msg->{revision}\n";
-               }
-               # Revert is needed for cases like:
-               # https://svn.musicpd.org/Jamming/trunk (r166:167), but
-               # I can't seem to reproduce something like that on a test...
-               sys(qw/svn revert -R ./);
-               assert_svn_wc_clean($last->{revision});
-               sys(@svn_up,"-r$log_msg->{revision}");
-               $last_commit = git_commit($log_msg, $last_commit, @parents);
-               $last = $log_msg;
-       }
-       close $svn_log->{fh};
-       $last->{commit} = $last_commit;
-       return $last;
-}
-
 sub fetch_lib {
        my (@parents) = @_;
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
@@ -416,16 +321,16 @@ sub fetch_lib {
        read_uuid();
        if (defined $last_commit) {
                unless (-e $GIT_SVN_INDEX) {
-                       sys(qw/git-read-tree/, $last_commit);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp (my $x = `git-write-tree`);
-               my ($y) = (`git-cat-file commit $last_commit`
+               my $x = command_oneline('write-tree');
+               my ($y) = (command(qw/cat-file commit/, $last_commit)
                                                        =~ /^tree ($sha1)/m);
                if ($y ne $x) {
                        unlink $GIT_SVN_INDEX or croak $!;
-                       sys(qw/git-read-tree/, $last_commit);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp ($x = `git-write-tree`);
+               $x = command_oneline('write-tree');
                if ($y ne $x) {
                        print STDERR "trees ($last_commit) $y != $x\n",
                                 "Something is seriously wrong...\n";
@@ -489,45 +394,19 @@ sub commit {
        }
        my @revs;
        foreach my $c (@commits) {
-               chomp(my @tmp = safe_qx('git-rev-parse',$c));
+               my @tmp = command('rev-parse',$c);
                if (scalar @tmp == 1) {
                        push @revs, $tmp[0];
                } elsif (scalar @tmp > 1) {
-                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
+                       push @revs, reverse(command('rev-list',@tmp));
                } else {
                        die "Failed to rev-parse $c\n";
                }
        }
-       chomp @revs;
-       $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
+       commit_lib(@revs);
        print "Done committing ",scalar @revs," revisions to SVN\n";
 }
 
-sub commit_cmd {
-       my (@revs) = @_;
-
-       chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
-       my $info = svn_info('.');
-       my $fetched = fetch();
-       if ($info->{Revision} != $fetched->{revision}) {
-               print STDERR "There are new revisions that were fetched ",
-                               "and need to be merged (or acknowledged) ",
-                               "before committing.\n";
-               exit 1;
-       }
-       $info = svn_info('.');
-       read_uuid($info);
-       my $last = $fetched;
-       foreach my $c (@revs) {
-               my $mods = svn_checkout_tree($last, $c);
-               if (scalar @$mods == 0) {
-                       print "Skipping, no changes detected\n";
-                       next;
-               }
-               $last = svn_commit_tree($last, $c);
-       }
-}
-
 sub commit_lib {
        my (@revs) = @_;
        my ($r_last, $cmt_last) = svn_grab_base_rev();
@@ -606,10 +485,10 @@ sub commit_lib {
 sub dcommit {
        my $head = shift || 'HEAD';
        my $gs = "refs/remotes/$GIT_SVN";
-       chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..$head"));
+       my @refs = command(qw/rev-list --no-merges/, "$gs..$head");
        my $last_rev;
        foreach my $d (reverse @refs) {
-               if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
+               if (!verify_ref("$d~1")) {
                        die "Commit $d\n",
                            "has no parent commit, and therefore ",
                            "nothing to diff against.\n",
@@ -633,7 +512,7 @@ sub dcommit {
        }
        return if $_dry_run;
        fetch();
-       my @diff = safe_qx('git-diff-tree', $head, $gs);
+       my @diff = command('diff-tree', $head, $gs, '--');
        my @finish;
        if (@diff) {
                @finish = qw/rebase/;
@@ -645,37 +524,11 @@ sub dcommit {
                      "Resetting to the latest $gs\n";
                @finish = qw/reset --mixed/;
        }
-       sys('git', @finish, $gs);
+       command_noisy(@finish, $gs);
 }
 
 sub show_ignore {
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       $_use_lib ? show_ignore_lib() : show_ignore_cmd();
-}
-
-sub show_ignore_cmd {
-       require File::Find or die $!;
-       if (defined $_revision) {
-               die "-r/--revision option doesn't work unless the Perl SVN ",
-                       "libraries are used\n";
-       }
-       chdir $SVN_WC or croak $!;
-       my %ign;
-       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
-               s#^\./##;
-               @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
-               }}, no_chdir=>1},'.');
-
-       print "\n# /\n";
-       foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
-       delete $ign{'.'};
-       foreach my $i (sort keys %ign) {
-               print "\n# ",$i,"\n";
-               foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
-       }
-}
-
-sub show_ignore_lib {
        my $repo;
        $SVN ||= libsvn_connect($SVN_URL);
        my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
@@ -689,7 +542,7 @@ sub graft_branches {
 
        if (%$grafts) {
                # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
+               chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
                rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
        }
 
@@ -707,11 +560,7 @@ sub graft_branches {
                        }
                }
                unless ($_no_graft_copy) {
-                       if ($_use_lib) {
-                               graft_file_copy_lib($grafts,$l_map,$u);
-                       } else {
-                               graft_file_copy_cmd($grafts,$l_map,$u);
-                       }
+                       graft_file_copy_lib($grafts,$l_map,$u);
                }
        }
        graft_tree_joins($grafts);
@@ -743,7 +592,7 @@ sub multi_init {
        unless (-d $GIT_SVN_DIR) {
                print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
                init($_trunk);
-               sys('git-repo-config', 'svn.trunk', $_trunk);
+               command_noisy('repo-config', 'svn.trunk', $_trunk);
        }
        complete_url_ls_init($url, $_branches, '--branches/-b', '');
        complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
@@ -781,11 +630,8 @@ sub show_log {
        }
 
        config_pager();
-       my $pid = open(my $log,'-|');
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
-       }
+       @args = (git_svn_log_cmd($r_min, $r_max), @args);
+       my $log = command_output_pipe(@args);
        run_pager();
        my (@k, $c, $d);
 
@@ -832,7 +678,7 @@ sub show_log {
                process_commit($_, $r_min, $r_max) foreach reverse @k;
        }
 out:
-       close $log;
+       eval { command_close_pipe($log) };
        print '-' x72,"\n" unless $_incremental || $_oneline;
 }
 
@@ -842,10 +688,6 @@ sub commit_diff_usage {
 }
 
 sub commit_diff {
-       if (!$_use_lib) {
-               print STDERR "commit-diff must be used with SVN libraries\n";
-               exit 1;
-       }
        my $ta = shift or commit_diff_usage();
        my $tb = shift or commit_diff_usage();
        if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
@@ -912,7 +754,7 @@ sub cmt_showable {
        return 1 if defined $c->{r};
        if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
                                $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-               my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
+               my @msg = command(qw/cat-file commit/, $c->{c});
                shift @msg while ($msg[0] ne "\n");
                shift @msg;
                @{$c->{l}} = grep !/^git-svn-id: /, @msg;
@@ -961,7 +803,7 @@ sub log_use_color {
 
 sub git_svn_log_cmd {
        my ($r_min, $r_max) = @_;
-       my @cmd = (qw/git-log --abbrev-commit --pretty=raw
+       my @cmd = (qw/log --abbrev-commit --pretty=raw
                        --default/, "refs/remotes/$GIT_SVN");
        push @cmd, '-r' unless $_non_recursive;
        push @cmd, qw/--raw --name-status/ if $_verbose;
@@ -1046,8 +888,7 @@ sub complete_url_ls_init {
                }
                $var = $url . $var;
        }
-       chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
-                               : safe_qx(qw/svn ls --non-interactive/, $var));
+       my @ls = libsvn_ls_fullurl($var);
        my $old = $GIT_SVN;
        defined(my $pid = fork) or croak $!;
        if (!$pid) {
@@ -1071,7 +912,7 @@ sub complete_url_ls_init {
        waitpid $pid, 0;
        croak $? if $?;
        my ($n) = ($switch =~ /^--(\w+)/);
-       sys('git-repo-config', "svn.$n", $var);
+       command_noisy('repo-config', "svn.$n", $var);
 }
 
 sub common_prefix {
@@ -1103,11 +944,8 @@ sub graft_tree_joins {
 
        git_svn_each(sub {
                my $i = shift;
-               defined(my $pid = open my $fh, '-|') or croak $!;
-               if (!$pid) {
-                       exec qw/git-rev-list --pretty=raw/,
-                                       "refs/remotes/$i" or croak $!;
-               }
+               my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
+               my ($fh, $ctx) = command_output_pipe(@args);
                while (<$fh>) {
                        next unless /^commit ($sha1)$/o;
                        my $c = $1;
@@ -1130,9 +968,7 @@ sub graft_tree_joins {
 
                        foreach my $p (@{$tree_map{$t}}) {
                                next if $p eq $c;
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $c, $p)
-                               };
+                               my $mb = eval { command('merge-base', $c, $p) };
                                next unless ($@ || $?);
                                if (defined $r_a) {
                                        # see if SVN says it's a relative
@@ -1161,41 +997,10 @@ sub graft_tree_joins {
                                # what should we do when $ct == $s ?
                        }
                }
-               close $fh or croak $?;
+               command_close_pipe($fh, $ctx);
        });
 }
 
-# this isn't funky-filename safe, but good enough for now...
-sub graft_file_copy_cmd {
-       my ($grafts, $l_map, $u) = @_;
-       my $paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$paths]);
-       $SVN_URL ||= $u.$pfx;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       unless ($pid) {
-               my @exec = qw/svn log -v/;
-               push @exec, "-r$_revision" if defined $_revision;
-               exec @exec, $u.$pfx or croak $!;
-       }
-       my ($r, $mp) = (undef, undef);
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       $mp = $r = undef;
-               } elsif (/^r(\d+) \| /) {
-                       $r = $1 unless defined $r;
-               } elsif (/^Changed paths:/) {
-                       $mp = 1;
-               } elsif ($mp && m#^   [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
-                       my ($p1, $p0, $r0) = ($1, $2, $3);
-                       my $c = find_graft_path_commit($paths, $p1, $r);
-                       next unless $c;
-                       find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
-               }
-       }
-}
-
 sub graft_file_copy_lib {
        my ($grafts, $l_map, $u) = @_;
        my $tree_paths = $l_map->{$u};
@@ -1286,28 +1091,14 @@ sub graft_merge_msg {
 
 sub read_uuid {
        return if $SVN_UUID;
-       if ($_use_lib) {
-               my $pool = SVN::Pool->new;
-               $SVN_UUID = $SVN->get_uuid($pool);
-               $pool->clear;
-       } else {
-               my $info = shift || svn_info('.');
-               $SVN_UUID = $info->{'Repository UUID'} or
-                                       croak "Repository UUID unreadable\n";
-       }
+       my $pool = SVN::Pool->new;
+       $SVN_UUID = $SVN->get_uuid($pool);
+       $pool->clear;
 }
 
-sub quiet_run {
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               open STDOUT, '>&', $null or croak $!;
-               exec @_ or croak $!;
-       }
-       waitpid $pid, 0;
-       return $?;
+sub verify_ref {
+       my ($ref) = @_;
+       eval { command_oneline([ 'rev-parse', $ref ], { STDERR => 0 }) };
 }
 
 sub repo_path_split {
@@ -1321,21 +1112,8 @@ sub repo_path_split {
                        return ($u, $full_url);
                }
        }
-       if ($_use_lib) {
-               my $tmp = libsvn_connect($full_url);
-               return ($tmp->{repos_root}, $tmp->{svn_path});
-       } else {
-               my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
-               $path =~ s#^/+##;
-               my @paths = split(m#/+#, $path);
-               while (quiet_run(qw/svn ls --non-interactive/, $url)) {
-                       my $n = shift @paths || last;
-                       $url .= "/$n";
-               }
-               push @repo_path_split_cache, qr/^(\Q$url\E)/;
-               $path = join('/',@paths);
-               return ($url, $path);
-       }
+       my $tmp = libsvn_connect($full_url);
+       return ($tmp->{repos_root}, $tmp->{svn_path});
 }
 
 sub setup_git_svn {
@@ -1351,41 +1129,17 @@ sub setup_git_svn {
 
 }
 
-sub assert_svn_wc_clean {
-       return if $_use_lib;
-       my ($svn_rev) = @_;
-       croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
-       my $lcr = svn_info('.')->{'Last Changed Rev'};
-       if ($svn_rev != $lcr) {
-               print STDERR "Checking for copy-tree ... ";
-               my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
-                                               "-r$lcr:$svn_rev")));
-               if (@diff) {
-                       croak "Nope!  Expected r$svn_rev, got r$lcr\n";
-               } else {
-                       print STDERR "OK!\n";
-               }
-       }
-       my @status = grep(!/^Performing status on external/,(`svn status`));
-       @status = grep(!/^\s*$/,@status);
-       @status = grep(!/^X/,@status) if $_no_ignore_ext;
-       if (scalar @status) {
-               print STDERR "Tree ($SVN_WC) is not clean:\n";
-               print STDERR $_ foreach @status;
-               croak;
-       }
-}
-
 sub get_tree_from_treeish {
        my ($treeish) = @_;
        croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
-       chomp(my $type = `git-cat-file -t $treeish`);
+       my $type = command_oneline(qw/cat-file -t/, $treeish);
        my $expected;
        while ($type eq 'tag') {
-               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+               ($treeish, $type) = command(qw/cat-file tag/, $treeish);
        }
        if ($type eq 'commit') {
-               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+               $expected = (grep /^tree /, command(qw/cat-file commit/,
+                                                   $treeish))[0];
                ($expected) = ($expected =~ /^tree ($sha1)$/);
                die "Unable to get tree from $treeish\n" unless $expected;
        } elsif ($type eq 'tree') {
@@ -1396,27 +1150,19 @@ sub get_tree_from_treeish {
        return $expected;
 }
 
-sub assert_tree {
-       return if $_use_lib;
-       my ($treeish) = @_;
-       my $expected = get_tree_from_treeish($treeish);
-
-       my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
-       if (-e $tmpindex) {
-               unlink $tmpindex or croak $!;
-       }
-       my $old_index = set_index($tmpindex);
-       index_changes(1);
-       chomp(my $tree = `git-write-tree`);
-       restore_index($old_index);
-       if ($tree ne $expected) {
-               croak "Tree mismatch, Got: $tree, Expected: $expected\n";
+sub get_diff {
+       my ($from, $treeish) = @_;
+       print "diff-tree $from $treeish\n";
+       my @diff_tree = qw(diff-tree -z -r);
+       if ($_cp_similarity) {
+               push @diff_tree, "-C$_cp_similarity";
+       } else {
+               push @diff_tree, '-C';
        }
-       unlink $tmpindex;
-}
-
-sub parse_diff_tree {
-       my $diff_fh = shift;
+       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+       push @diff_tree, "-l$_l" if defined $_l;
+       push @diff_tree, $from, $treeish;
+       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
        local $/ = "\0";
        my $state = 'meta';
        my @mods;
@@ -1452,170 +1198,10 @@ sub parse_diff_tree {
                        croak "Error parsing $_\n";
                }
        }
-       close $diff_fh or croak $?;
-
+       command_close_pipe($diff_fh, $ctx);
        return \@mods;
 }
 
-sub svn_check_prop_executable {
-       my $m = shift;
-       return if -l $m->{file_b};
-       if ($m->{mode_b} =~ /755$/) {
-               chmod((0755 &~ umask),$m->{file_b}) or croak $!;
-               if ($m->{mode_a} !~ /755$/) {
-                       sys(qw(svn propset svn:executable 1), $m->{file_b});
-               }
-               -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
-       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
-               sys(qw(svn propdel svn:executable), $m->{file_b});
-               chmod((0644 &~ umask),$m->{file_b}) or croak $!;
-               -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
-       }
-}
-
-sub svn_ensure_parent_path {
-       my $dir_b = dirname(shift);
-       svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
-       mkpath([$dir_b]) unless (-d $dir_b);
-       sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
-}
-
-sub precommit_check {
-       my $mods = shift;
-       my (%rm_file, %rmdir_check, %added_check);
-
-       my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'R') {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-                       # dir/$file => dir/file/$file
-                       my $dirname = dirname($m->{file_b});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_a}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_file_to_dir("$m->{file_a} => $m->{file_b}");
-                       }
-                       # baz/zzz => baz (baz is a file)
-                       $dirname = dirname($m->{file_a});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_b}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-               }
-               if ($m->{chg} =~ /^(D|R)$/) {
-                       my $t = $1 eq 'D' ? 'file_b' : 'file_a';
-                       $rm_file{ $m->{$t} } = 1;
-                       my $dirname = dirname( $m->{$t} );
-                       my $basename = basename( $m->{$t} );
-                       $rmdir_check{$dirname}->{$basename} = 1;
-               } elsif ($m->{chg} =~ /^(?:A|C)$/) {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file($m->{file_b});
-                       }
-                       my $dirname = dirname( $m->{file_b} );
-                       my $basename = basename( $m->{file_b} );
-                       $added_check{$dirname}->{$basename} = 1;
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($rm_file{$dirname}) {
-                                       err_file_to_dir($m->{file_b});
-                               }
-                               $dirname = dirname $dirname;
-                       }
-               }
-       }
-       return (\%rmdir_check, \%added_check);
-
-       sub err_dir_to_file {
-               my $file = shift;
-               print STDERR "Node change from directory to file ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-       sub err_file_to_dir {
-               my $file = shift;
-               print STDERR "Node change from file to directory ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-}
-
-
-sub get_diff {
-       my ($from, $treeish) = @_;
-       assert_tree($from);
-       print "diff-tree $from $treeish\n";
-       my $pid = open my $diff_fh, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @diff_tree = qw(git-diff-tree -z -r);
-               if ($_cp_similarity) {
-                       push @diff_tree, "-C$_cp_similarity";
-               } else {
-                       push @diff_tree, '-C';
-               }
-               push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-               push @diff_tree, "-l$_l" if defined $_l;
-               exec(@diff_tree, $from, $treeish) or croak $!;
-       }
-       return parse_diff_tree($diff_fh);
-}
-
-sub svn_checkout_tree {
-       my ($from, $treeish) = @_;
-       my $mods = get_diff($from->{commit}, $treeish);
-       return $mods unless (scalar @$mods);
-       my ($rm, $add) = precommit_check($mods);
-
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'C') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn cp),         $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'D') {
-                       sys(qw(svn rm --force), $m->{file_b});
-               } elsif ($m->{chg} eq 'R') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'M') {
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'T') {
-                       svn_check_prop_executable($m);
-                       apply_mod_line_blob($m);
-                       if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
-                               sys(qw(svn propdel svn:special), $m->{file_b});
-                       } else {
-                               sys(qw(svn propset svn:special *),$m->{file_b});
-                       }
-               } elsif ($m->{chg} eq 'A') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       apply_mod_line_blob($m);
-                       sys(qw(svn add), $m->{file_b});
-                       svn_check_prop_executable($m);
-               } else {
-                       croak "Invalid chg: $m->{chg}\n";
-               }
-       }
-
-       assert_tree($treeish);
-       if ($_rmdir) { # remove empty directories
-               handle_rmdir($rm, $add);
-       }
-       assert_tree($treeish);
-       return $mods;
-}
-
 sub libsvn_checkout_tree {
        my ($from, $treeish, $ed) = @_;
        my $mods = get_diff($from, $treeish);
@@ -1633,57 +1219,15 @@ sub libsvn_checkout_tree {
        return $mods;
 }
 
-# svn ls doesn't work with respect to the current working tree, but what's
-# in the repository.  There's not even an option for it... *sigh*
-# (added files don't show up and removed files remain in the ls listing)
-sub svn_ls_current {
-       my ($dir, $rm, $add) = @_;
-       chomp(my @ls = safe_qx('svn','ls',$dir));
-       my @ret = ();
-       foreach (@ls) {
-               s#/$##; # trailing slashes are evil
-               push @ret, $_ unless $rm->{$dir}->{$_};
-       }
-       if (exists $add->{$dir}) {
-               push @ret, keys %{$add->{$dir}};
-       }
-       return \@ret;
-}
-
-sub handle_rmdir {
-       my ($rm, $add) = @_;
-
-       foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
-               my $ls = svn_ls_current($dir, $rm, $add);
-               next if (scalar @$ls);
-               sys(qw(svn rm --force),$dir);
-
-               my $dn = dirname $dir;
-               $rm->{ $dn }->{ basename $dir } = 1;
-               $ls = svn_ls_current($dn, $rm, $add);
-               while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
-                       sys(qw(svn rm --force),$dn);
-                       $dir = basename $dn;
-                       $dn = dirname $dn;
-                       $rm->{ $dn }->{ $dir } = 1;
-                       $ls = svn_ls_current($dn, $rm, $add);
-               }
-       }
-}
-
 sub get_commit_message {
        my ($commit, $commit_msg) = (@_);
        my %log_msg = ( msg => '' );
        open my $msg, '>', $commit_msg or croak $!;
 
-       chomp(my $type = `git-cat-file -t $commit`);
+       my $type = command_oneline(qw/cat-file -t/, $commit);
        if ($type eq 'commit' || $type eq 'tag') {
-               my $pid = open my $msg_fh, '-|';
-               defined $pid or croak $!;
-
-               if ($pid == 0) {
-                       exec('git-cat-file', $type, $commit) or croak $!;
-               }
+               my ($msg_fh, $ctx) = command_output_pipe('cat-file',
+                                                        $type, $commit);
                my $in_msg = 0;
                while (<$msg_fh>) {
                        if (!$in_msg) {
@@ -1695,7 +1239,7 @@ sub get_commit_message {
                                print $msg $_ or croak $!;
                        }
                }
-               close $msg_fh or croak $?;
+               command_close_pipe($msg_fh, $ctx);
        }
        close $msg or croak $!;
 
@@ -1720,64 +1264,9 @@ sub set_svn_commit_env {
        }
 }
 
-sub svn_commit_tree {
-       my ($last, $commit) = @_;
-       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-       my $log_msg = get_commit_message($commit, $commit_msg);
-       my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
-       print "Committing $commit: $oneline\n";
-
-       set_svn_commit_env();
-       my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
-       my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
-       if (!defined $committed) {
-               my $out = join("\n",@ci_output);
-               print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
-                               $out, "\n\nAssuming English locale...";
-               ($committed) = ($out =~ /^Committed revision \d+\./sm);
-               defined $committed or die " FAILED!\n",
-                       "Commit output failed to parse committed revision!\n",
-               print STDERR " OK\n";
-       }
-
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
-               push @svn_up, "-r$committed";
-               sys(@svn_up);
-               my $info = svn_info('.');
-               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
-               if ($info->{'Last Changed Rev'} != $committed) {
-                       croak "$info->{'Last Changed Rev'} != $committed\n"
-               }
-               my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-               $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
-               $log_msg->{author} = $info->{'Last Changed Author'};
-               $log_msg->{revision} = $committed;
-               $log_msg->{msg} .= "\n";
-               $log_msg->{parents} = [ $last->{commit} ];
-               $log_msg->{commit} = git_commit($log_msg, $commit);
-               return $log_msg;
-       }
-       # resync immediately
-       push @svn_up, "-r$last->{revision}";
-       sys(@svn_up);
-       return fetch("$committed=$commit");
-}
-
 sub rev_list_raw {
-       my (@args) = @_;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
-       }
-       return { fh => $fh, t => { } };
+       my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
+       return { fh => $fh, ctx => $c, t => { } };
 }
 
 sub next_rev_list_entry {
@@ -1799,184 +1288,10 @@ sub next_rev_list_entry {
                        $x->{m} .= $_;
                }
        }
+       command_close_pipe($fh, $rl->{ctx});
        return ($x != $rl->{t}) ? $x : undef;
 }
 
-# read the entire log into a temporary file (which is removed ASAP)
-# and store the file handle + parser state
-sub svn_log_raw {
-       my (@log_args) = @_;
-       my $log_fh = IO::File->new_tmpfile or croak $!;
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $log_fh or croak $!;
-               exec (qw(svn log), @log_args) or croak $!
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-       seek $log_fh, 0, 0 or croak $!;
-       return { state => 'sep', fh => $log_fh };
-}
-
-sub next_log_entry {
-       my $log = shift; # retval of svn_log_raw()
-       my $ret = undef;
-       my $fh = $log->{fh};
-
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       if ($log->{state} eq 'msg') {
-                               if ($ret->{lines}) {
-                                       $ret->{msg} .= $_."\n";
-                                       unless(--$ret->{lines}) {
-                                               $log->{state} = 'sep';
-                                       }
-                               } else {
-                                       croak "Log parse error at: $_\n",
-                                               $ret->{revision},
-                                               "\n";
-                               }
-                               next;
-                       }
-                       if ($log->{state} ne 'sep') {
-                               croak "Log parse error at: $_\n",
-                                       "state: $log->{state}\n",
-                                       $ret->{revision},
-                                       "\n";
-                       }
-                       $log->{state} = 'rev';
-
-                       # if we have an empty log message, put something there:
-                       if ($ret) {
-                               $ret->{msg} ||= "\n";
-                               delete $ret->{lines};
-                               return $ret;
-                       }
-                       next;
-               }
-               if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
-                       my $rev = $1;
-                       my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
-                       ($lines) = ($lines =~ /(\d+)/);
-                       $date = '1970-01-01 00:00:00 +0000'
-                               if ($_ignore_nodate && $date eq '(no date)');
-                       my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-                       $ret = {        revision => $rev,
-                                       date => "$tz $Y-$m-$d $H:$M:$S",
-                                       author => $author,
-                                       lines => $lines,
-                                       msg => '' };
-                       if (defined $_authors && ! defined $users{$author}) {
-                               die "Author: $author not defined in ",
-                                               "$_authors file\n";
-                       }
-                       $log->{state} = 'msg_start';
-                       next;
-               }
-               # skip the first blank line of the message:
-               if ($log->{state} eq 'msg_start' && /^$/) {
-                       $log->{state} = 'msg';
-               } elsif ($log->{state} eq 'msg') {
-                       if ($ret->{lines}) {
-                               $ret->{msg} .= $_."\n";
-                               unless (--$ret->{lines}) {
-                                       $log->{state} = 'sep';
-                               }
-                       } else {
-                               croak "Log parse error at: $_\n",
-                                       $ret->{revision},"\n";
-                       }
-               }
-       }
-       return $ret;
-}
-
-sub svn_info {
-       my $url = shift || $SVN_URL;
-
-       my $pid = open my $info_fh, '-|';
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               exec(qw(svn info),$url) or croak $!;
-       }
-
-       my $ret = {};
-       # only single-lines seem to exist in svn info output
-       while (<$info_fh>) {
-               chomp $_;
-               if (m#^([^:]+)\s*:\s*(\S.*)$#) {
-                       $ret->{$1} = $2;
-                       push @{$ret->{-order}}, $1;
-               }
-       }
-       close $info_fh or croak $?;
-       return $ret;
-}
-
-sub sys { system(@_) == 0 or croak $? }
-
-sub do_update_index {
-       my ($z_cmd, $cmd, $no_text_base) = @_;
-
-       my $z = open my $p, '-|';
-       defined $z or croak $!;
-       unless ($z) { exec @$z_cmd or croak $! }
-
-       my $pid = open my $ui, '|-';
-       defined $pid or croak $!;
-       unless ($pid) {
-               exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
-       }
-       local $/ = "\0";
-       while (my $x = <$p>) {
-               chomp $x;
-               if (!$no_text_base && lstat $x && ! -l _ &&
-                               svn_propget_base('svn:keywords', $x)) {
-                       my $mode = -x _ ? 0755 : 0644;
-                       my ($v,$d,$f) = File::Spec->splitpath($x);
-                       my $tb = File::Spec->catfile($d, '.svn', 'tmp',
-                                               'text-base',"$f.svn-base");
-                       $tb =~ s#^/##;
-                       unless (-f $tb) {
-                               $tb = File::Spec->catfile($d, '.svn',
-                                               'text-base',"$f.svn-base");
-                               $tb =~ s#^/##;
-                       }
-                       my @s = stat($x);
-                       unlink $x or croak $!;
-                       copy($tb, $x);
-                       chmod(($mode &~ umask), $x) or croak $!;
-                       utime $s[8], $s[9], $x;
-               }
-               print $ui $x,"\0";
-       }
-       close $ui or croak $?;
-}
-
-sub index_changes {
-       return if $_use_lib;
-
-       if (!-f "$GIT_SVN_DIR/info/exclude") {
-               open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
-               print $fd '.svn',"\n";
-               close $fd or croak $!;
-       }
-       my $no_text_base = shift;
-       do_update_index([qw/git-diff-files --name-only -z/],
-                       'remove',
-                       $no_text_base);
-       do_update_index([qw/git-ls-files -z --others/,
-                               "--exclude-from=$GIT_SVN_DIR/info/exclude"],
-                       'add',
-                       $no_text_base);
-}
-
 sub s_to_file {
        my ($str, $file, $mode) = @_;
        open my $fd,'>',$file or croak $!;
@@ -2002,18 +1317,6 @@ sub assert_revision_unknown {
        }
 }
 
-sub trees_eq {
-       my ($x, $y) = @_;
-       my @x = safe_qx('git-cat-file','commit',$x);
-       my @y = safe_qx('git-cat-file','commit',$y);
-       if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
-                               || $y[0] !~ /^tree $sha1\n$/) {
-               print STDERR "Trees not equal: $y[0] != $x[0]\n";
-               return 0
-       }
-       return 1;
-}
-
 sub git_commit {
        my ($log_msg, @parents) = @_;
        assert_revision_unknown($log_msg->{revision});
@@ -2038,34 +1341,23 @@ sub git_commit {
        my $tree = $log_msg->{tree};
        if (!defined $tree) {
                my $index = set_index($GIT_SVN_INDEX);
-               index_changes();
-               chomp($tree = `git-write-tree`);
+               $tree = command_oneline('write-tree');
                croak $? if $?;
                restore_index($index);
        }
-
        # just in case we clobber the existing ref, we still want that ref
        # as our parent:
-       open my $null, '>', '/dev/null' or croak $!;
-       open my $stderr, '>&', \*STDERR or croak $!;
-       open STDERR, '>&', $null or croak $!;
-       if (my $cur = eval { safe_qx('git-rev-parse',
-                                    "refs/remotes/$GIT_SVN^0") }) {
+       if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) {
                chomp $cur;
                push @tmp_parents, $cur;
        }
-       open STDERR, '>&', $stderr or croak $!;
-       close $stderr or croak $!;
-       close $null or croak $!;
 
        if (exists $tree_map{$tree}) {
                foreach my $p (@{$tree_map{$tree}}) {
                        my $skip;
                        foreach (@tmp_parents) {
                                # see if a common parent is found
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $_, $p)
-                               };
+                               my $mb = eval { command('merge-base', $_, $p) };
                                next if ($@ || $?);
                                $skip = 1;
                                last;
@@ -2107,7 +1399,7 @@ sub git_commit {
        if ($commit !~ /^$sha1$/o) {
                die "Failed to commit, invalid sha1: $commit\n";
        }
-       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+       command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit);
        revdb_set($REVDB, $log_msg->{revision}, $commit);
 
        # this output is read via pipe, do not change:
@@ -2119,7 +1411,8 @@ sub git_commit {
 sub check_repack {
        if ($_repack && (--$_repack_nr == 0)) {
                $_repack_nr = $_repack;
-               sys("git repack $_repack_flags");
+               # repack doesn't use any arguments with spaces in them, does it?
+               command_noisy('repack', split(/\s+/, $_repack_flags));
        }
 }
 
@@ -2136,122 +1429,17 @@ sub set_commit_env {
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
 }
 
-sub apply_mod_line_blob {
-       my $m = shift;
-       if ($m->{mode_b} =~ /^120/) {
-               blob_to_symlink($m->{sha1_b}, $m->{file_b});
-       } else {
-               blob_to_file($m->{sha1_b}, $m->{file_b});
-       }
-}
-
-sub blob_to_symlink {
-       my ($blob, $link) = @_;
-       defined $link or croak "\$link not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $link || -f _) {
-               unlink $link or croak $!;
-       }
-
-       my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
-       symlink $dest, $link or croak $!;
-}
-
-sub blob_to_file {
-       my ($blob, $file) = @_;
-       defined $file or croak "\$file not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $file || -f _) {
-               unlink $file or croak $!;
-       }
-
-       open my $blob_fh, '>', $file or croak "$!: $file\n";
-       my $pid = fork;
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               open STDOUT, '>&', $blob_fh or croak $!;
-               exec('git-cat-file','blob',$blob) or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-
-       close $blob_fh or croak $!;
-}
-
-sub safe_qx {
-       my $pid = open my $child, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec(@_) or croak $!;
-       }
-       my @ret = (<$child>);
-       close $child or croak $?;
-       die $? if $?; # just in case close didn't error out
-       return wantarray ? @ret : join('',@ret);
-}
-
-sub svn_compat_check {
-       if ($_follow_parent) {
-               print STDERR 'E: --follow-parent functionality is only ',
-                               "available when SVN libraries are used\n";
-               exit 1;
-       }
-       my @co_help = safe_qx(qw(svn co -h));
-       unless (grep /ignore-externals/,@co_help) {
-               print STDERR "W: Installed svn version does not support ",
-                               "--ignore-externals\n";
-               $_no_ignore_ext = 1;
-       }
-       if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
-               $_svn_co_url_revs = 1;
-       }
-       if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
-               $_svn_pg_peg_revs = 1;
-       }
-
-       # I really, really hope nobody hits this...
-       unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
-               print STDERR <<'';
-W: The installed svn version does not support the --stop-on-copy flag in
-   the log command.
-   Lets hope the directory you're tracking is not a branch or tag
-   and was never moved within the repository...
-
-               $_no_stop_copy = 1;
-       }
-}
-
-# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
-# (and they won't honor URL@<rev> without -r<rev>, too!)
-sub svn_cmd_checkout {
-       my ($url, $rev, $dir) = @_;
-       my @cmd = ('svn','co', "-r$rev");
-       push @cmd, '--ignore-externals' unless $_no_ignore_ext;
-       $url .= "\@$rev" if $_svn_co_url_revs;
-       sys(@cmd, $url, $dir);
-}
-
 sub check_upgrade_needed {
        if (!-r $REVDB) {
                -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
                open my $fh, '>>',$REVDB or croak $!;
                close $fh;
        }
-       my $old = eval {
-               my $pid = open my $child, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       close STDERR;
-                       exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
-               }
-               my @ret = (<$child>);
-               close $child or croak $?;
-               die $? if $?; # just in case close didn't error out
-               return wantarray ? @ret : join('',@ret);
+       return unless eval {
+               command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
+                       {STDERR => 0});
        };
-       return unless $old;
-       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
        if ($@ || !$head) {
                print STDERR "Please run: $0 rebuild --upgrade\n";
                exit 1;
@@ -2263,12 +1451,8 @@ sub check_upgrade_needed {
 sub map_tree_joins {
        my %seen;
        foreach my $br (@_branch_from) {
-               my $pid = open my $pipe, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       exec(qw(git-rev-list --topo-order --pretty=raw), $br)
-                                                               or croak $!;
-               }
+               my $pipe = command_output_pipe(qw/rev-list
+                                           --topo-order --pretty=raw/, $br);
                while (<$pipe>) {
                        if (/^commit ($sha1)$/o) {
                                my $commit = $1;
@@ -2284,7 +1468,7 @@ sub map_tree_joins {
                                $seen{$commit} = 1;
                        }
                }
-               close $pipe; # we could be breaking the pipe early
+               eval { command_close_pipe($pipe) };
        }
 }
 
@@ -2296,7 +1480,7 @@ sub load_all_refs {
 
        # don't worry about rev-list on non-commit objects/tags,
        # it shouldn't blow up if a ref is a blob or tree...
-       chomp(@_branch_from = `git-rev-parse --symbolic --all`);
+       @_branch_from = command(qw/rev-parse --symbolic --all/);
 }
 
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
@@ -2322,15 +1506,9 @@ sub rload_authors {
        close $authors or croak $!;
 }
 
-sub svn_propget_base {
-       my ($p, $f) = @_;
-       $f .= '@BASE' if $_svn_pg_peg_revs;
-       return safe_qx(qw/svn propget/, $p, $f);
-}
-
 sub git_svn_each {
        my $sub = shift;
-       foreach (`git-rev-parse --symbolic --all`) {
+       foreach (command(qw/rev-parse --symbolic --all/)) {
                next unless s#^refs/remotes/##;
                chomp $_;
                next unless -f "$GIT_DIR/svn/$_/info/url";
@@ -2371,7 +1549,7 @@ sub migration_check {
                                "$GIT_SVN_DIR\n\t(required for this version ",
                                "($VERSION) of git-svn) does not.\n";
 
-       foreach my $x (`git-rev-parse --symbolic --all`) {
+       foreach my $x (command(qw/rev-parse --symbolic --all/)) {
                next unless $x =~ s#^refs/remotes/##;
                chomp $x;
                next unless -f "$GIT_DIR/$x/info/url";
@@ -2476,11 +1654,7 @@ sub write_grafts {
                my $p = $grafts->{$c};
                my %x; # real parents
                delete $p->{$c}; # commits are not self-reproducing...
-               my $pid = open my $ch, '-|';
-               defined $pid or croak $!;
-               if (!$pid) {
-                       exec(qw/git-cat-file commit/, $c) or croak $!;
-               }
+               my $ch = command_output_pipe(qw/cat-file commit/, $c);
                while (<$ch>) {
                        if (/^parent ($sha1)/) {
                                $x{$1} = $p->{$1} = 1;
@@ -2488,7 +1662,7 @@ sub write_grafts {
                                last unless /^\S/;
                        }
                }
-               close $ch; # breaking the pipe
+               eval { command_close_pipe($ch) }; # breaking the pipe
 
                # if real parents are the only ones in the grafts, drop it
                next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
@@ -2500,7 +1674,7 @@ sub write_grafts {
                        next if $del{$i} || $p->{$i} == 2;
                        foreach my $j (@jp) {
                                next if $i eq $j || $del{$j} || $p->{$j} == 2;
-                               $mb = eval { safe_qx('git-merge-base',$i,$j) };
+                               $mb = eval { command('merge-base', $i, $j) };
                                next unless $mb;
                                chomp $mb;
                                next if $x{$mb};
@@ -2571,15 +1745,12 @@ sub extract_metadata {
 
 sub cmt_metadata {
        return extract_metadata((grep(/^git-svn-id: /,
-               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+               command(qw/cat-file commit/, shift)))[-1]);
 }
 
 sub get_commit_time {
        my $cmt = shift;
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
-       }
+       my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt);
        while (<$fh>) {
                /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
                my ($s, $tz) = ($1, $2);
@@ -2588,7 +1759,7 @@ sub get_commit_time {
                } elsif ($tz =~ s/^\-//) {
                        $s -= tz_to_s_offset($tz);
                }
-               close $fh;
+               eval { command_close_pipe($fh) };
                return $s;
        }
        die "Can't get commit time for commit: $cmt\n";
@@ -2734,34 +1905,6 @@ sub show_commit_normal {
        }
 }
 
-sub libsvn_load {
-       return unless $_use_lib;
-       $_use_lib = eval {
-               require SVN::Core;
-               if ($SVN::Core::VERSION lt '1.1.0') {
-                       die "Need SVN::Core 1.1.0 or better ",
-                                       "(got $SVN::Core::VERSION) ",
-                                       "Falling back to command-line svn\n";
-               }
-               require SVN::Ra;
-               require SVN::Delta;
-               push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
-               push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
-               *SVN::Git::Fetcher::process_rm = *process_rm;
-               *SVN::Git::Fetcher::safe_qx = *safe_qx;
-               my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown.
-                                       $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown.
-                                       $SVN::Auth::SSL::CNMISMATCH.
-                                       $SVN::Auth::SSL::NOTYETVALID.
-                                       $SVN::Auth::SSL::EXPIRED.
-                                       $SVN::Auth::SSL::UNKNOWNCA.
-                                       $SVN::Auth::SSL::OTHER;
-               1;
-       };
-}
-
 sub _simple_prompt {
        my ($cred, $realm, $default_username, $may_save, $pool) = @_;
        $may_save = undef if $_no_auth_cache;
@@ -2965,7 +2108,7 @@ sub libsvn_get_file {
        my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
        if (exists $props->{'svn:special'}) {
                $mode = '120000';
-               my $link = `git-cat-file blob $hash`;
+               my $link = `git-cat-file blob $hash`; # no chomping symlinks
                $link =~ s/^link // or die "svn:special file with contents: <",
                                                $link, "> is not understood\n";
                defined($pid = open3($in, $out, '>&STDERR',
@@ -3069,19 +2212,17 @@ sub libsvn_log_entry {
 sub process_rm {
        my ($gui, $last_commit, $f, $q) = @_;
        # remove entire directories.
-       if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
-               defined(my $pid = open my $ls, '-|') or croak $!;
-               if (!$pid) {
-                       exec(qw/git-ls-tree -r --name-only -z/,
-                               $last_commit,'--',$f) or croak $!;
-               }
+       if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
+               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+                                                    -r --name-only -z/,
+                                                    $last_commit,'--',$f);
                local $/ = "\0";
                while (<$ls>) {
                        print $gui '0 ',0 x 40,"\t",$_ or croak $!;
                        print "\tD\t$_\n" unless $q;
                }
                print "\tD\t$f/\n" unless $q;
-               close $ls or croak $?;
+               command_close_pipe($ls, $ctx);
                return $SVN::Node::dir;
        } else {
                print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
@@ -3112,7 +2253,7 @@ sub libsvn_fetch_delta {
 
 sub libsvn_fetch_full {
        my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
+       my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
        my %amr;
        my $ut = { empty => {}, dir_prop => {}, file_prop => {} };
        my $p = $SVN->{svn_path};
@@ -3166,20 +2307,14 @@ sub libsvn_fetch_full {
                %{$ut->{dir_prop}->{''}} = %$props;
                $pool->clear;
        }
-       close $gui or croak $?;
+       command_close_pipe($gui, $ctx);
        libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut);
 }
 
 sub svn_grab_base_rev {
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
-                                                               or croak $!;
-       }
-       chomp(my $c = do { local $/; <$fh> });
-       close $fh;
+       my $c = eval { command_oneline([qw/rev-parse --verify/,
+                                       "refs/remotes/$GIT_SVN^0"],
+                                       { STDERR => 0 }) };
        if (defined $c && length $c) {
                my ($url, $rev, $uuid) = cmt_metadata($c);
                return ($rev, $c) if defined $rev;
@@ -3292,18 +2427,11 @@ sub revisions_eq {
        my ($path, $r0, $r1) = @_;
        return 1 if $r0 == $r1;
        my $nr = 0;
-       if ($_use_lib) {
-               # should be OK to use Pool here (r1 - r0) should be small
-               my $pool = SVN::Pool->new;
-               libsvn_get_log($SVN, [$path], $r0, $r1,
-                               0, 0, 1, sub {$nr++}, $pool);
-               $pool->clear;
-       } else {
-               my ($url, undef) = repo_path_split($SVN_URL);
-               my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
-               while (next_log_entry($svn_log)) { $nr++ }
-               close $svn_log->{fh};
-       }
+       # should be OK to use Pool here (r1 - r0) should be small
+       my $pool = SVN::Pool->new;
+       libsvn_get_log($SVN, [$path], $r0, $r1,
+                       0, 0, 1, sub {$nr++}, $pool);
+       $pool->clear;
        return 0 if ($nr > 1);
        return 1;
 }
@@ -3358,7 +2486,7 @@ sub libsvn_find_parent_branch {
        if (revisions_eq($branch_from, $r0, $r)) {
                unlink $GIT_SVN_INDEX;
                print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-               sys(qw/git-read-tree/, $parent);
+               command_noisy('read-tree', $parent);
                unless (libsvn_can_do_switch()) {
                        return libsvn_fetch_full($parent, $paths, $rev,
                                                $author, $date, $msg);
@@ -3367,7 +2495,7 @@ sub libsvn_find_parent_branch {
                # included with SVN 1.4.2 (the latest version at the moment),
                # so we can't rely on it.
                my $ra = libsvn_connect("$url/$branch_from");
-               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q});
+               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
                my $pool = SVN::Pool->new;
                my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url},
                                              $ed, $pool);
@@ -3413,9 +2541,10 @@ sub libsvn_new_tree {
                $ut = $ed;
        } else {
                $ut = { empty => {}, dir_prop => {}, file_prop => {} };
-               open my $gui, '| git-update-index -z --index-info' or croak $!;
+               my ($gui, $ctx) = command_input_pipe(qw/update-index
+                                                    -z --index-info/);
                libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut);
-               close $gui or croak $?;
+               command_close_pipe($gui, $ctx);
        }
        libsvn_log_entry($rev, $author, $date, $msg, [], $ut);
 }
@@ -3487,7 +2616,7 @@ sub libsvn_commit_cb {
                my $log = libsvn_log_entry($rev,$committer,$date,$msg);
                $log->{tree} = get_tree_from_treeish($c);
                my $cmt = git_commit($log, $cmt_last, $c);
-               my @diff = safe_qx('git-diff-tree', $cmt, $c);
+               my @diff = command('diff-tree', $cmt, $c);
                if (@diff) {
                        print STDERR "Trees differ: $cmt $c\n",
                                        join('',@diff),"\n";
@@ -3579,27 +2708,40 @@ sub revdb_get {
 sub copy_remote_ref {
        my $origin = $_cp_remote ? $_cp_remote : 'origin';
        my $ref = "refs/remotes/$GIT_SVN";
-       if (safe_qx('git-ls-remote', $origin, $ref)) {
-               sys(qw/git fetch/, $origin, "$ref:$ref");
+       if (command('ls-remote', $origin, $ref)) {
+               command_noisy('fetch', $origin, "$ref:$ref");
        } elsif ($_cp_remote && !$_upgrade) {
                die "Unable to find remote reference: ",
                                "refs/remotes/$GIT_SVN on $origin\n";
        }
 }
+
+{
+       my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Auth::SSL::CNMISMATCH.
+                               $SVN::Auth::SSL::NOTYETVALID.
+                               $SVN::Auth::SSL::EXPIRED.
+                               $SVN::Auth::SSL::UNKNOWNCA.
+                               $SVN::Auth::SSL::OTHER;
+}
+
 package SVN::Git::Fetcher;
 use vars qw/@ISA/;
 use strict;
 use warnings;
 use Carp qw/croak/;
 use IO::File qw//;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
        my ($class, $git_svn) = @_;
        my $self = SVN::Delta::Editor->new;
        bless $self, $class;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
-       $self->{gui} = $gui;
        $self->{c} = $git_svn->{c} if exists $git_svn->{c};
        $self->{q} = $git_svn->{q};
        $self->{empty} = {};
@@ -3607,6 +2749,8 @@ sub new {
        $self->{file_prop} = {};
        $self->{absent_dir} = {};
        $self->{absent_file} = {};
+       ($self->{gui}, $self->{ctx}) = command_input_pipe(
+                                            qw/update-index -z --index-info/);
        require Digest::MD5;
        $self;
 }
@@ -3629,7 +2773,7 @@ sub delete_entry {
 
 sub open_file {
        my ($self, $path, $pb, $rev) = @_;
-       my ($mode, $blob) = (safe_qx('git-ls-tree',$self->{c},'--',$path)
+       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
                             =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
        unless (defined $mode && defined $blob) {
                die "$path was not found in commit $self->{c} (r$rev)\n";
@@ -3764,13 +2908,13 @@ sub close_file {
 
 sub abort_edit {
        my $self = shift;
-       close $self->{gui};
+       eval { command_close_pipe($self->{gui}, $self->{ctx}) };
        $self->SUPER::abort_edit(@_);
 }
 
 sub close_edit {
        my $self = shift;
-       close $self->{gui} or croak $!;
+       command_close_pipe($self->{gui}, $self->{ctx});
        $self->{git_commit_ok} = 1;
        $self->SUPER::close_edit(@_);
 }
@@ -3781,6 +2925,8 @@ package SVN::Git::Editor;
 use warnings;
 use Carp qw/croak/;
 use IO::File;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 
 sub new {
        my $class = shift;
@@ -3830,10 +2976,8 @@ sub rmdirs {
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
 
-       defined(my $pid = open my $fh,'-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
-       }
+       my ($fh, $ctx) = command_output_pipe(
+                                  qw/ls-tree --name-only -r -z/, $self->{c});
        local $/ = "\0";
        while (<$fh>) {
                chomp;
@@ -3842,11 +2986,11 @@ sub rmdirs {
                        delete $rm->{join '/', @dn};
                }
                unless (%$rm) {
-                       close $fh;
+                       eval { command_close_pipe($fh) };
                        return;
                }
        }
-       close $fh;
+       command_close_pipe($fh, $ctx);
 
        my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
@@ -4021,13 +3165,7 @@ sub abort_edit {
 
 Data structures:
 
-$svn_log hashref (as returned by svn_log_raw)
-{
-       fh => file handle of the log file,
-       state => state of the log file parser (sep/msg/rev/msg_start...)
-}
-
-$log_msg hashref as returned by next_log_entry($svn_log)
+$log_msg hashref as returned by libsvn_log_entry()
 {
        msg => 'whitespace-formatted log entry
 ',                                             # trailing newline is preserved
@@ -4036,7 +3174,6 @@ sub abort_edit {
        author => 'committer name'
 };
 
-
 @mods = array of diff-index line hashes, each element represents one line
        of diff-index output
 
index d53f94cd9c618e9c2032d3e6ad7af9f3866e7707..e1bfa82f1ea1213fad8ff8c4b6ad2b17e6ca1f7e 100755 (executable)
@@ -40,11 +40,20 @@ do
        message="$1"
        if test "$#" = "0"; then
            die "error: option -m needs an argument"
-           exit 2
        else
            message_given=1
        fi
        ;;
+    -F)
+       annotate=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -F needs an argument"
+       else
+           message="$(cat "$1")"
+           message_given=1
+       fi
+       ;;
     -u)
        annotate=1
        signed=1
diff --git a/git.c b/git.c
index 73cf4d4e019c2c0169e6181b2c557cdeb3f962e7..e732a098fc164da10ed9f41113c7d32d1b022966 100644 (file)
--- a/git.c
+++ b/git.c
@@ -59,8 +59,10 @@ static int handle_options(const char*** argv, int* argc)
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        setup_pager();
                } else if (!strcmp(cmd, "--git-dir")) {
-                       if (*argc < 1)
-                               return -1;
+                       if (*argc < 2) {
+                               fprintf(stderr, "No directory given for --git-dir.\n" );
+                               usage(git_usage_string);
+                       }
                        setenv("GIT_DIR", (*argv)[1], 1);
                        (*argv)++;
                        (*argc)--;
@@ -245,6 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "push", cmd_push, RUN_SETUP },
                { "read-tree", cmd_read_tree, RUN_SETUP },
                { "repo-config", cmd_repo_config },
+               { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "rm", cmd_rm, RUN_SETUP },
index ebbc397ee8dce807bd9700aa72d33c80b13f02bf..d01d68934834ca1aae30c04b1f79b2cadb559d88 100755 (executable)
                #         => [content-encoding, suffix, program]
                'default' => ['x-gzip', 'gz', 'gzip']},
 
+       # Enable text search, which will list the commits which match author,
+       # committer or commit text to a given string.  Enabled by default.
+       'search' => {
+               'override' => 0,
+               'default' => [1]},
+
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
@@ -351,6 +357,9 @@ sub check_export_ok {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
                die_error(undef, "Invalid search parameter");
        }
+       if (length($searchtext) < 2) {
+               die_error(undef, "At least two characters are required for search parameter");
+       }
        $searchtext = quotemeta $searchtext;
 }
 
@@ -1139,8 +1148,9 @@ sub git_get_last_activity {
 
        $git_dir = "$projectroot/$path";
        open($fd, "-|", git_cmd(), 'for-each-ref',
-            '--format=%(refname) %(committer)',
+            '--format=%(committer)',
             '--sort=-committerdate',
+            '--count=1',
             'refs/heads') or return;
        my $most_recent = <$fd>;
        close $fd or return;
@@ -1726,6 +1736,9 @@ sub git_header_html {
                        print " / $action";
                }
                print "\n";
+       }
+       my ($have_search) = gitweb_check_feature('search');
+       if ((defined $project) && ($have_search)) {
                if (!defined $searchtext) {
                        $searchtext = "";
                }
@@ -2635,6 +2648,8 @@ sub git_shortlog_body {
        # uses global variable $project
        my ($revlist, $from, $to, $refs, $extra) = @_;
 
+       my $have_snapshot = gitweb_have_snapshot();
+
        $from = 0 unless defined $from;
        $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
 
@@ -2662,7 +2677,7 @@ sub git_shortlog_body {
                      $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
-               if (gitweb_have_snapshot()) {
+               if ($have_snapshot) {
                        print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
                }
                print "</td>\n" .
@@ -2837,6 +2852,58 @@ sub git_heads_body {
        print "</table>\n";
 }
 
+sub git_search_grep_body {
+       my ($greplist, $from, $to, $extra) = @_;
+       $from = 0 unless defined $from;
+       $to = $#{$greplist} if (!defined $to || $#{$greplist} < $to);
+
+       print "<table class=\"grep\" cellspacing=\"0\">\n";
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my $commit = $greplist->[$i];
+               my %co = parse_commit($commit);
+               if (!%co) {
+                       next;
+               }
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                     "<td>" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
+                              esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+               my $comment = $co{'comment'};
+               foreach my $line (@$comment) {
+                       if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+                               my $lead = esc_html($1) || "";
+                               $lead = chop_str($lead, 30, 10);
+                               my $match = esc_html($2) || "";
+                               my $trail = esc_html($3) || "";
+                               $trail = chop_str($trail, 30, 10);
+                               my $text = "$lead<span class=\"match\">$match</span>$trail";
+                               print chop_str($text, 80, 5) . "<br/>\n";
+                       }
+               }
+               print "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
+               print "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n" .
+                     "<td colspan=\"3\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 ## ======================================================================
 ## ======================================================================
 ## actions
@@ -2908,9 +2975,9 @@ sub git_project_index {
 
 sub git_summary {
        my $descr = git_get_project_description($project) || "none";
-       my $head = git_get_head_hash($project);
-       my %co = parse_commit($head);
+       my %co = parse_commit("HEAD");
        my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+       my $head = $co{'id'};
 
        my $owner = git_get_project_owner($project);
 
@@ -2957,7 +3024,7 @@ sub git_summary {
        # we need to request one more than 16 (0..15) to check if
        # those 16 are all
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
-               git_get_head_hash($project), "--"
+               $head, "--"
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
@@ -2983,6 +3050,7 @@ sub git_summary {
        if (@forklist) {
                git_print_header_div('forks');
                git_project_list_body(\@forklist, undef, 0, 15,
+                                     $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
                                      'noheader');
        }
@@ -4144,6 +4212,10 @@ sub git_history {
 }
 
 sub git_search {
+       my ($have_search) = gitweb_check_feature('search');
+       if (!$have_search) {
+               die_error('403 Permission denied', "Permission denied");
+       }
        if (!defined $searchtext) {
                die_error(undef, "Text field empty");
        }
@@ -4154,6 +4226,9 @@ sub git_search {
        if (!%co) {
                die_error(undef, "Unknown commit object");
        }
+       if (!defined $page) {
+               $page = 0;
+       }
 
        $searchtype ||= 'commit';
        if ($searchtype eq 'pickaxe') {
@@ -4166,66 +4241,68 @@ sub git_search {
        }
 
        git_header_html();
-       git_print_page_nav('','', $hash,$co{'tree'},$hash);
-       git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
-       print "<table cellspacing=\"0\">\n";
-       my $alternate = 1;
        if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
-               $/ = "\0";
+               my $greptype;
+               if ($searchtype eq 'commit') {
+                       $greptype = "--grep=";
+               } elsif ($searchtype eq 'author') {
+                       $greptype = "--author=";
+               } elsif ($searchtype eq 'committer') {
+                       $greptype = "--committer=";
+               }
                open my $fd, "-|", git_cmd(), "rev-list",
-                       "--header", "--parents", $hash, "--"
+                       ("--max-count=" . (100 * ($page+1))),
+                       ($greptype . $searchtext),
+                       $hash, "--"
                        or next;
-               while (my $commit_text = <$fd>) {
-                       if (!grep m/$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       my @commit_lines = split "\n", $commit_text;
-                       my %co = parse_commit(undef, \@commit_lines);
-                       if (!%co) {
-                               next;
-                       }
-                       if ($alternate) {
-                               print "<tr class=\"dark\">\n";
-                       } else {
-                               print "<tr class=\"light\">\n";
-                       }
-                       $alternate ^= 1;
-                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                             "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
-                             "<td>" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                                      esc_html(chop_str($co{'title'}, 50)) . "<br/>");
-                       my $comment = $co{'comment'};
-                       foreach my $line (@$comment) {
-                               if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
-                                       my $lead = esc_html($1) || "";
-                                       $lead = chop_str($lead, 30, 10);
-                                       my $match = esc_html($2) || "";
-                                       my $trail = esc_html($3) || "";
-                                       $trail = chop_str($trail, 30, 10);
-                                       my $text = "$lead<span class=\"match\">$match</span>$trail";
-                                       print chop_str($text, 80, 5) . "<br/>\n";
-                               }
-                       }
-                       print "</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                             " | " .
-                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
-                       print "</td>\n" .
-                             "</tr>\n";
-               }
+               my @revlist = map { chomp; $_ } <$fd>;
                close $fd;
+
+               my $paging_nav = '';
+               if ($page > 0) {
+                       $paging_nav .=
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                       "first");
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page-1),
+                                        -accesskey => "p", -title => "Alt-p"}, "prev");
+               } else {
+                       $paging_nav .= "first";
+                       $paging_nav .= " &sdot; prev";
+               }
+               if ($#revlist >= (100 * ($page+1)-1)) {
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               } else {
+                       $paging_nav .= " &sdot; next";
+               }
+               my $next_link = '';
+               if ($#revlist >= (100 * ($page+1)-1)) {
+                       $next_link =
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               }
+
+               git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+               git_search_grep_body(\@revlist, ($page * 100), $#revlist, $next_link);
        }
 
        if ($searchtype eq 'pickaxe') {
+               git_print_page_nav('','', $hash,$co{'tree'},$hash);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+               print "<table cellspacing=\"0\">\n";
+               my $alternate = 1;
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -4280,8 +4357,9 @@ sub git_search {
                        }
                }
                close $fd;
+
+               print "</table>\n";
        }
-       print "</table>\n";
        git_footer_html();
 }
 
index 894cbbdf53ccf4728276b46b9491f409f3b9e03b..ad91858bc43d046ea199f5a52c372b2a243c647f 100644 (file)
@@ -96,8 +96,8 @@ typedef struct {
 
 static int Verbose, Quiet;
 
-static void info( const char *, ... );
-static void warn( const char *, ... );
+static void imap_info( const char *, ... );
+static void imap_warn( const char *, ... );
 
 static char *next_arg( char ** );
 
@@ -297,7 +297,7 @@ buffer_gets( buffer_t * b, char **s )
 }
 
 static void
-info( const char *msg, ... )
+imap_info( const char *msg, ... )
 {
        va_list va;
 
@@ -310,7 +310,7 @@ info( const char *msg, ... )
 }
 
 static void
-warn( const char *msg, ... )
+imap_warn( const char *msg, ... )
 {
        va_list va;
 
@@ -903,7 +903,7 @@ imap_open_store( imap_server_conf_t *srvc )
        /* open connection to IMAP server */
 
        if (srvc->tunnel) {
-               info( "Starting tunnel '%s'... ", srvc->tunnel );
+               imap_info( "Starting tunnel '%s'... ", srvc->tunnel );
 
                if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
                        perror( "socketpair" );
@@ -926,31 +926,31 @@ imap_open_store( imap_server_conf_t *srvc )
 
                imap->buf.sock.fd = a[1];
 
-               info( "ok\n" );
+               imap_info( "ok\n" );
        } else {
                memset( &addr, 0, sizeof(addr) );
                addr.sin_port = htons( srvc->port );
                addr.sin_family = AF_INET;
 
-               info( "Resolving %s... ", srvc->host );
+               imap_info( "Resolving %s... ", srvc->host );
                he = gethostbyname( srvc->host );
                if (!he) {
                        perror( "gethostbyname" );
                        goto bail;
                }
-               info( "ok\n" );
+               imap_info( "ok\n" );
 
                addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
 
                s = socket( PF_INET, SOCK_STREAM, 0 );
 
-               info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
+               imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
                if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
                        close( s );
                        perror( "connect" );
                        goto bail;
                }
-               info( "ok\n" );
+               imap_info( "ok\n" );
 
                imap->buf.sock.fd = s;
 
@@ -979,7 +979,7 @@ imap_open_store( imap_server_conf_t *srvc )
 
        if (!preauth) {
 
-               info ("Logging in...\n");
+               imap_info ("Logging in...\n");
                if (!srvc->user) {
                        fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
                        goto bail;
@@ -1006,7 +1006,7 @@ imap_open_store( imap_server_conf_t *srvc )
                        fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
                        goto bail;
                }
-               warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+               imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
                if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
                        fprintf( stderr, "IMAP error: LOGIN failed\n" );
                        goto bail;
index 8787df5cc647dda24f09b077276cbe0aa054fc47..35be33aaf73e0b53cb9ece09bba5f1e12de4b99d 100644 (file)
@@ -114,6 +114,14 @@ void show_log(struct rev_info *opt, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
@@ -192,10 +200,20 @@ void show_log(struct rev_info *opt, const char *sep)
                        opt->diffopt.stat_sep = buffer;
                }
        } else {
-               printf("%s%s%s",
-                      diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
-                      opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
-                      diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+               fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
+                     stdout);
+               if (opt->commit_format != CMIT_FMT_ONELINE)
+                       fputs("commit ", stdout);
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+                     stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
index 1de273ea1e4f309b542c8ebfce706ba422e1cd74..ca4f19e34d4216fed7c8110ba8f75585c69f92c7 100644 (file)
@@ -649,8 +649,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                        char *name1, *name2;
                        int merge_status;
 
-                       name1 = xstrdup(mkpath("%s/%s", branch1, a->path));
-                       name2 = xstrdup(mkpath("%s/%s", branch2, b->path));
+                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
 
                        fill_mm(o->sha1, &orig);
                        fill_mm(a->sha1, &src1);
@@ -1263,6 +1263,18 @@ static struct commit *get_ref(const char *ref)
        return (struct commit *)object;
 }
 
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
 int main(int argc, char *argv[])
 {
        static const char *bases[2];
@@ -1272,7 +1284,7 @@ int main(int argc, char *argv[])
        struct commit *result, *h1, *h2;
 
        git_config(git_default_config); /* core.filemode */
-       original_index_file = getenv("GIT_INDEX_FILE");
+       original_index_file = getenv(INDEX_ENVIRONMENT);
 
        if (!original_index_file)
                original_index_file = xstrdup(git_path("index"));
@@ -1293,11 +1305,14 @@ int main(int argc, char *argv[])
 
        branch1 = argv[++i];
        branch2 = argv[++i];
-       printf("Merging %s with %s\n", branch1, branch2);
 
        h1 = get_ref(branch1);
        h2 = get_ref(branch2);
 
+       branch1 = better_branch_name(branch1);
+       branch2 = better_branch_name(branch2);
+       printf("Merging %s with %s\n", branch1, branch2);
+
        if (bases_count == 1) {
                struct commit *ancestor = get_ref(bases[0]);
                clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result);
index 5dc1e70260d45efded8f2329b6a4d8bf050c0537..e7eccd9180f855f7ff0df5c4515a6d98288fd127 100644 (file)
@@ -343,6 +343,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
 {
        struct commit_list *parent = commit->parents;
+       unsigned left_flag;
 
        if (commit->object.flags & ADDED)
                return;
@@ -387,6 +388,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
        if (revs->no_walk)
                return;
 
+       left_flag = (commit->object.flags & SYMMETRIC_LEFT);
        parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
@@ -394,6 +396,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                parent = parent->next;
 
                parse_commit(p);
+               p->object.flags |= left_flag;
                if (p->object.flags & SEEN)
                        continue;
                p->object.flags |= SEEN;
@@ -640,7 +643,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
-                               a->object.flags |= flags;
+                               a->object.flags |= flags | SYMMETRIC_LEFT;
                        } else
                                a->object.flags |= flags_exclude;
                        b->object.flags |= flags;
@@ -854,6 +857,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->boundary = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--left-right")) {
+                               revs->left_right = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--objects")) {
                                revs->tag_objects = 1;
                                revs->tree_objects = 1;
index 81f522c35d21397f432bdeed42dab1f0044df377..ec991e5c57039a57af7c63db483e5b108a25ad16 100644 (file)
@@ -9,6 +9,7 @@
 #define BOUNDARY       (1u<<5)
 #define BOUNDARY_SHOW  (1u<<6)
 #define ADDED          (1u<<7) /* Parents already parsed and added? */
+#define SYMMETRIC_LEFT (1u<<8)
 
 struct rev_info;
 struct log_info;
@@ -40,6 +41,7 @@ struct rev_info {
                        limited:1,
                        unpacked:1, /* see also ignore_packed below */
                        boundary:1,
+                       left_right:1,
                        parents:1;
 
        /* Diff flags */
index c9bd9a46905a1709668f6d37790cb2457940ac12..250a19019c1f494897c5e43437b93c3f6a80cace 100644 (file)
@@ -23,13 +23,9 @@ clean:
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_DELTA_FETCH=1 \
-                                       GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
+       $(MAKE) $(TSVN) GIT_SVN_DELTA_FETCH=1 \
+                               GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
+       $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
 .PHONY: $(T) clean
 .NOTPARALLEL:
index 63c670304f0241d93c8a1661080735ca4a14258a..99ada71349c7eaa0536bba1d622f7f7ffa56110e 100644 (file)
@@ -7,17 +7,18 @@ then
        exit
 fi
 
-GIT_DIR=$PWD/.git
-GIT_SVN_DIR=$GIT_DIR/svn/git-svn
-SVN_TREE=$GIT_SVN_DIR/svn-tree
-
-perl -e 'use SVN::Core' >/dev/null 2>&1
+perl -e 'use SVN::Core; $SVN::Core::VERSION gt "1.1.0" or die' >/dev/null 2>&1
 if test $? -ne 0
 then
-   echo 'Perl SVN libraries not found, tests requiring those will be skipped'
-   GIT_SVN_NO_LIB=1
+       test_expect_success 'Perl SVN libraries not found, skipping test' :
+       test_done
+       exit
 fi
 
+GIT_DIR=$PWD/.git
+GIT_SVN_DIR=$GIT_DIR/svn/git-svn
+SVN_TREE=$GIT_SVN_DIR/svn-tree
+
 svnadmin >/dev/null 2>&1
 if test $? -ne 1
 then
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
new file mode 100755 (executable)
index 0000000..5ee5b23
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='git-rerere
+'
+
+. ./test-lib.sh
+
+cat > a1 << EOF
+Whether 'tis nobler in the mind to suffer
+The slings and arrows of outrageous fortune,
+Or to take arms against a sea of troubles,
+And by opposing end them? To die: to sleep;
+No more; and by a sleep to say we end
+The heart-ache and the thousand natural shocks
+That flesh is heir to, 'tis a consummation
+Devoutly to be wish'd.
+EOF
+
+git add a1
+git commit -q -a -m initial
+
+git checkout -b first
+cat >> a1 << EOF
+To die, to sleep;
+To sleep: perchance to dream: ay, there's the rub;
+For in that sleep of death what dreams may come
+When we have shuffled off this mortal coil,
+Must give us pause: there's the respect
+That makes calamity of so long life;
+EOF
+git commit -q -a -m first
+
+git checkout -b second master
+git show first:a1 | sed 's/To die, t/To die! T/' > a1
+git commit -q -a -m second
+
+# activate rerere
+mkdir .git/rr-cache
+
+test_expect_failure 'conflicting merge' 'git pull . first'
+
+sha1=4f58849a60b4f969a2848966b6d02893b783e8fb
+rr=.git/rr-cache/$sha1
+test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
+
+test_expect_success 'no postimage or thisimage yet' \
+       "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+
+git show first:a1 > a1
+
+cat > expect << EOF
+--- a/a1
++++ b/a1
+@@ -6,11 +6,7 @@
+ The heart-ache and the thousand natural shocks
+ That flesh is heir to, 'tis a consummation
+ Devoutly to be wish'd.
+-<<<<<<<
+-To die! To sleep;
+-=======
+ To die, to sleep;
+->>>>>>>
+ To sleep: perchance to dream: ay, there's the rub;
+ For in that sleep of death what dreams may come
+ When we have shuffled off this mortal coil,
+EOF
+
+git rerere diff > out
+
+test_expect_success 'rerere diff' 'diff -u expect out'
+
+cat > expect << EOF
+a1
+EOF
+
+git rerere status > out
+
+test_expect_success 'rerere status' 'diff -u expect out'
+
+test_expect_success 'commit succeeds' \
+       "git commit -q -a -m 'prefer first over second'"
+
+test_expect_success 'recorded postimage' "test -f $rr/postimage"
+
+git checkout -b third master
+git show second^:a1 | sed 's/To die: t/To die! T/' > a1
+git commit -q -a -m third
+
+test_expect_failure 'another conflicting merge' 'git pull . first'
+
+git show first:a1 | sed 's/To die: t/To die! T/' > expect
+test_expect_success 'rerere kicked in' "! grep ======= a1"
+
+test_expect_success 'rerere prefers first change' 'diff -u a1 expect'
+
+rm $rr/postimage
+echo "$sha1    a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR
+
+test_expect_success 'rerere clear' 'git rerere clear'
+
+test_expect_success 'clear removed the directory' "test ! -d $rr"
+
+mkdir $rr
+echo Hello > $rr/preimage
+echo World > $rr/postimage
+
+sha2=4000000000000000000000000000000000000000
+rr2=.git/rr-cache/$sha2
+mkdir $rr2
+echo Hello > $rr2/preimage
+
+case "$(date -d @11111111 +%s 2>/dev/null)" in
+[1-9]*)
+       # it is a recent GNU date. good.
+       now=$(date +%s)
+       almost_15_days_ago=$(($now+60-15*86400))
+       just_over_15_days_ago=$(($now-1-15*86400))
+       almost_60_days_ago=$(($now+60-60*86400))
+       just_over_60_days_ago=$(($now-1-60*86400))
+       predate1="$(date -d "@$almost_60_days_ago" +%c)"
+       predate2="$(date -d "@$almost_15_days_ago" +%c)"
+       postdate1="$(date -d "@$just_over_60_days_ago" +%c)"
+       postdate2="$(date -d "@$just_over_15_days_ago" +%c)"
+       ;;
+*)
+       # it is not GNU date. oh, well.
+       predate1="$(date)"
+       predate2="$(date)"
+       postdate1='1 Oct 2006 00:00:00'
+       postdate2='1 Dec 2006 00:00:00'
+esac
+
+touch -m -d "$predate1" $rr/preimage
+touch -m -d "$predate2" $rr2/preimage
+
+test_expect_success 'garbage collection (part1)' 'git rerere gc'
+
+test_expect_success 'young records still live' \
+       "test -f $rr/preimage -a -f $rr2/preimage"
+
+touch -m -d "$postdate1" $rr/preimage
+touch -m -d "$postdate2" $rr2/preimage
+
+test_expect_success 'garbage collection (part2)' 'git rerere gc'
+
+test_expect_success 'old records rest in peace' \
+       "test ! -f $rr/preimage -a ! -f $rr2/preimage"
+
+test_done
+
+
index a11ab0ad41a006f9f20d8ec6172d34e6c7f92d41..90eeeba2a31949a84daeb34b1996eaa4818321e6 100755 (executable)
@@ -23,15 +23,14 @@ test_expect_success "clone and setup child repos" '
        git clone . two &&
        cd two &&
        git repo-config branch.master.remote one &&
-       {
-               echo "URL: ../one/.git/"
-               echo "Pull: refs/heads/master:refs/heads/one"
-       } >.git/remotes/one
+       git repo-config remote.one.url ../one/.git/ &&
+       git repo-config remote.one.fetch refs/heads/master:refs/heads/one &&
        cd .. &&
        git clone . three &&
        cd three &&
        git repo-config branch.master.remote two &&
        git repo-config branch.master.merge refs/heads/one &&
+       mkdir -p .git/remotes &&
        {
                echo "URL: ../two/.git/"
                echo "Pull: refs/heads/master:refs/heads/two"
index 964010e764a1f6340ae4a5cd300ef67cd6babfc4..69b18f7d8160d0acb2bf222a9478674cfc21634f 100644 (file)
@@ -59,18 +59,18 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 test_expect_failure "combined merge conflicts" "git merge -m final G"
 
 cat > expect << EOF
-<<<<<<< HEAD/a1
+<<<<<<< HEAD:a1
 F
 =======
 G
->>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1
+>>>>>>> G:a1
 EOF
 
 test_expect_success "result contains a conflict" "diff -u expect a1"
 
 git ls-files --stage > out
 cat > expect << EOF
-100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1      a1
+100644 da056ce14a2241509897fa68bb2b3b6e6194ef9e 1      a1
 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2      a1
 100644 fd7923529855d0b274795ae3349c5e0438333979 3      a1
 EOF
index f9de2323669ac0db21be10a6699b00810565393c..0edf19e48d20bf03dc567a3be73a03892f143632 100755 (executable)
@@ -56,7 +56,7 @@ git update-index --add --remove dir/a/b/c/d/e/file dir/file file
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch &&
      svn up $SVN_TREE &&
      test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
 
@@ -70,7 +70,7 @@ git update-index --add dir/file/file
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch' \
     || true
 
 
@@ -85,7 +85,7 @@ git update-index --add -- bar
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
     || true
 
 
@@ -100,7 +100,7 @@ git-update-index --add bar/zzz/yyy
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
     || true
 
 
@@ -115,7 +115,7 @@ git update-index --add -- dir
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
     || true
 
 
@@ -127,7 +127,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      svn up $SVN_TREE &&
      test ! -x $SVN_TREE/exec.sh"
 
@@ -138,7 +138,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      svn up $SVN_TREE &&
      test -x $SVN_TREE/exec.sh"
 
@@ -153,7 +153,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
             svn up $SVN_TREE &&
             test -L $SVN_TREE/exec.sh"
 
@@ -164,7 +164,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
             svn up $SVN_TREE &&
             test -x $SVN_TREE/bar/zzz &&
             test -L $SVN_TREE/exec-2.sh"
@@ -177,7 +177,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
             svn up $SVN_TREE &&
             test -f $SVN_TREE/exec-2.sh &&
             test ! -L $SVN_TREE/exec-2.sh &&
@@ -192,7 +192,7 @@ then
        git update-index exec-2.sh
        git commit -m 'éï∏'
        export LC_ALL="$GIT_SVN_LC_ALL"
-       test_expect_success "$name" "git-svn commit HEAD"
+       test_expect_success "$name" "git-svn set-tree HEAD"
        unset LC_ALL
 else
        echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
@@ -229,9 +229,7 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
 
-if test -z "$GIT_SVN_NO_LIB" || test "$GIT_SVN_NO_LIB" -eq 0; then
-       echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
-fi
+echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
 
 test_expect_success "$name" "diff -u a expected"
 
index a5a235f100709f503887e9a69c5b510e0b04534f..5543b07f16d54bb5aa6fab6ab9cde79837a3e68b 100755 (executable)
@@ -74,7 +74,7 @@ test_expect_success "$name" \
        'git checkout -b mybranch remotes/git-svn &&
        echo Hi again >> kw.c &&
        git commit -a -m "test keywoards ignoring" &&
-       git-svn commit remotes/git-svn..mybranch &&
+       git-svn set-tree remotes/git-svn..mybranch &&
        git pull . remotes/git-svn'
 
 expect='/* $Id$ */'
index d693d183c8e41a4c1e948e690f42a55c985543b3..572aaedc06986505ff7b16b5553632051dfa2c58 100755 (executable)
@@ -21,7 +21,7 @@ test_expect_success 'mirror via git-svn' "
 test_expect_success 'Try a commit on rmdir' "
        git rm -f deeply/nested/directory/number/2/another &&
        git commit -a -m 'remove another' &&
-       git-svn commit --rmdir HEAD &&
+       git-svn set-tree --rmdir HEAD &&
        svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1
        "
 
index 01488ff78acd8324a04a3f0c5e4788e60148a704..8d2e2fec395a328f4bf6a65af3c7eba5a98cc133 100755 (executable)
@@ -6,13 +6,6 @@
 test_description='git-svn --follow-parent fetching'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: --follow-parent needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index 746c8277d06a7d9d147df16d6c16efce65bf34a2..6323c7e3ace0dda555ee67b384a197a26bf89dd6 100755 (executable)
@@ -4,13 +4,6 @@
 test_description='git-svn commit-diff'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index 58698b3f29966593d1f7063b04e8d07322636572..59b6425ce4e5a265443311b7ed033fe6e8e73add 100755 (executable)
@@ -4,13 +4,6 @@
 test_description='git-svn commit-diff clobber'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff clobber needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index ac7be769b4d4f04908a64592a5c3ad8c43c27e60..f0f9cd6be01681c90d1914eaa8a40144e2280adf 100755 (executable)
@@ -208,8 +208,9 @@ test_done () {
 # t/ subdirectory and are run in trash subdirectory.
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
+GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 HOME=$(pwd)/trash
-export PATH GIT_EXEC_PATH HOME
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME
 
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
diff --git a/templates/remotes-- b/templates/remotes--
deleted file mode 100644 (file)
index fae8870..0000000
+++ /dev/null
@@ -1 +0,0 @@
-: this is just to ensure the directory exists.
diff --git a/usage.c b/usage.c
index 52c2e960560531b62a28a8a716e531093fd5e8a7..4dc5c77633747e0c2b3a4d04fddbd043f8f49e1e 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -29,12 +29,17 @@ static void error_builtin(const char *err, va_list params)
        report("error: ", err, params);
 }
 
+static void warn_builtin(const char *warn, va_list params)
+{
+       report("warning: ", warn, params);
+}
 
 /* If we are in a dlopen()ed .so write to a global variable would segfault
  * (ugh), so keep things static. */
 static void (*usage_routine)(const char *err) NORETURN = usage_builtin;
 static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin;
 static void (*error_routine)(const char *err, va_list params) = error_builtin;
+static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
 
 void set_usage_routine(void (*routine)(const char *err) NORETURN)
 {
@@ -51,6 +56,11 @@ void set_error_routine(void (*routine)(const char *err, va_list params))
        error_routine = routine;
 }
 
+void set_warn_routine(void (*routine)(const char *warn, va_list params))
+{
+       warn_routine = routine;
+}
+
 
 void usage(const char *err)
 {
@@ -75,3 +85,12 @@ int error(const char *err, ...)
        va_end(params);
        return -1;
 }
+
+void warn(const char *warn, ...)
+{
+       va_list params;
+
+       va_start(params, warn);
+       warn_routine(warn, params);
+       va_end(params);
+}
diff --git a/utf8.c b/utf8.c
new file mode 100644 (file)
index 0000000..8fa6257
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,278 @@
+#include "git-compat-util.h"
+#include "utf8.h"
+
+/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
+
+struct interval {
+  int first;
+  int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+       int min = 0;
+       int mid;
+
+       if (ucs < table[0].first || ucs > table[max].last)
+               return 0;
+       while (max >= min) {
+               mid = (min + max) / 2;
+               if (ucs > table[mid].last)
+                       min = mid + 1;
+               else if (ucs < table[mid].first)
+                       max = mid - 1;
+               else
+                       return 1;
+       }
+
+       return 0;
+}
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ *    - The null character (U+0000) has a column width of 0.
+ *
+ *    - Other C0/C1 control characters and DEL will lead to a return
+ *      value of -1.
+ *
+ *    - Non-spacing and enclosing combining characters (general
+ *      category code Mn or Me in the Unicode database) have a
+ *      column width of 0.
+ *
+ *    - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ *    - Other format characters (general category code Cf in the Unicode
+ *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ *      have a column width of 0.
+ *
+ *    - Spacing characters in the East Asian Wide (W) or East Asian
+ *      Full-width (F) category as defined in Unicode Technical
+ *      Report #11 have a column width of 2.
+ *
+ *    - All remaining characters (including all printable
+ *      ISO 8859-1 and WGL4 characters, Unicode control characters,
+ *      etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static int wcwidth(wchar_t ch)
+{
+       /*
+        * Sorted list of non-overlapping intervals of non-spacing characters,
+        * generated by
+        *   "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
+        */
+       static const struct interval combining[] = {
+               { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 },
+               { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
+               { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+               { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 },
+               { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
+               { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
+               { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
+               { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
+               { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
+               { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
+               { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
+               { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
+               { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
+               { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
+               { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 },
+               { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
+               { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
+               { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
+               { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
+               { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
+               { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+               { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+               { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+               { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+               { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+               { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+               { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+               { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+               { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+               { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 },
+               { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+               { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+               { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
+               { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
+               { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F },
+               { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F },
+               { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
+               { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 },
+               { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 },
+               { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
+               { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
+               { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
+       };
+
+       /* test for 8-bit control characters */
+       if (ch == 0)
+               return 0;
+       if (ch < 32 || (ch >= 0x7f && ch < 0xa0))
+               return -1;
+
+       /* binary search in table of non-spacing characters */
+       if (bisearch(ch, combining, sizeof(combining)
+                               / sizeof(struct interval) - 1))
+               return 0;
+
+       /*
+        * If we arrive here, ch is neither a combining nor a C0/C1
+        * control character.
+        */
+
+       return 1 +
+               (ch >= 0x1100 &&
+                    /* Hangul Jamo init. consonants */
+                (ch <= 0x115f ||
+                 ch == 0x2329 || ch == 0x232a ||
+                  /* CJK ... Yi */
+                 (ch >= 0x2e80 && ch <= 0xa4cf &&
+                  ch != 0x303f) ||
+                 /* Hangul Syllables */
+                 (ch >= 0xac00 && ch <= 0xd7a3) ||
+                 /* CJK Compatibility Ideographs */
+                 (ch >= 0xf900 && ch <= 0xfaff) ||
+                 /* CJK Compatibility Forms */
+                 (ch >= 0xfe30 && ch <= 0xfe6f) ||
+                 /* Fullwidth Forms */
+                 (ch >= 0xff00 && ch <= 0xff60) ||
+                 (ch >= 0xffe0 && ch <= 0xffe6) ||
+                 (ch >= 0x20000 && ch <= 0x2fffd) ||
+                 (ch >= 0x30000 && ch <= 0x3fffd)));
+}
+
+/*
+ * This function returns the number of columns occupied by the character
+ * pointed to by the variable start. The pointer is updated to point at
+ * the next character. If it was not valid UTF-8, the pointer is set to NULL.
+ */
+int utf8_width(const char **start)
+{
+       unsigned char *s = (unsigned char *)*start;
+       wchar_t ch;
+
+       if (*s < 0x80) {
+               /* 0xxxxxxx */
+               ch = *s;
+               *start += 1;
+       } else if ((s[0] & 0xe0) == 0xc0) {
+               /* 110XXXXx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] & 0xfe) == 0xc0)
+                       goto invalid;
+               ch = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);
+               *start += 2;
+       } else if ((s[0] & 0xf0) == 0xe0) {
+               /* 1110XXXX 10Xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||
+                               /* surrogate? */
+                               (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||
+                               /* U+FFFE or U+FFFF? */
+                               (s[0] == 0xef && s[1] == 0xbf &&
+                                (s[2] & 0xfe) == 0xbe))
+                       goto invalid;
+               ch = ((s[0] & 0x0f) << 12) |
+                       ((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
+               *start += 3;
+       } else if ((s[0] & 0xf8) == 0xf0) {
+               /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               (s[3] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||
+                               /* > U+10FFFF? */
+                               (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4)
+                       goto invalid;
+               ch = ((s[0] & 0x07) << 18) | ((s[1] & 0x3f) << 12) |
+                       ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
+               *start += 4;
+       } else {
+invalid:
+               *start = NULL;
+               return 0;
+       }
+
+       return wcwidth(ch);
+}
+
+int is_utf8(const char *text)
+{
+       while (*text) {
+               if (*text == '\n' || *text == '\t' || *text == '\r') {
+                       text++;
+                       continue;
+               }
+               utf8_width(&text);
+               if (!text)
+                       return 0;
+       }
+       return 1;
+}
+
+static void print_spaces(int count)
+{
+       static const char s[] = "                    ";
+       while (count >= sizeof(s)) {
+               fwrite(s, sizeof(s) - 1, 1, stdout);
+               count -= sizeof(s) - 1;
+       }
+       fwrite(s, count, 1, stdout);
+}
+
+/*
+ * Wrap the text, if necessary. The variable indent is the indent for the
+ * first line, indent2 is the indent for all other lines.
+ */
+void print_wrapped_text(const char *text, int indent, int indent2, int width)
+{
+       int w = indent, assume_utf8 = is_utf8(text);
+       const char *bol = text, *space = NULL;
+
+       for (;;) {
+               char c = *text;
+               if (!c || isspace(c)) {
+                       if (w < width || !space) {
+                               const char *start = bol;
+                               if (space)
+                                       start = space;
+                               else
+                                       print_spaces(indent);
+                               fwrite(start, text - start, 1, stdout);
+                               if (!c) {
+                                       putchar('\n');
+                                       return;
+                               } else if (c == '\t')
+                                       w |= 0x07;
+                               space = text;
+                               w++;
+                               text++;
+                       }
+                       else {
+                               putchar('\n');
+                               text = bol = space + 1;
+                               space = NULL;
+                               w = indent = indent2;
+                       }
+                       continue;
+               }
+               if (assume_utf8)
+                       w += utf8_width(&text);
+               else {
+                       w++;
+                       text++;
+               }
+       }
+}
diff --git a/utf8.h b/utf8.h
new file mode 100644 (file)
index 0000000..a0d7f59
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,8 @@
+#ifndef GIT_UTF8_H
+#define GIT_UTF8_H
+
+int utf8_width(const char **start);
+int is_utf8(const char *text);
+void print_wrapped_text(const char *text, int indent, int indent2, int len);
+
+#endif
index 08602f522183dc43787616f37cba9b8af4e3dade..6c1f99b149f0800a9c9ab1b45033bc1401a84766 100644 (file)
@@ -102,3 +102,22 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
        }
        return 0;
 }
+
+int read_mmfile(mmfile_t *ptr, const char *filename)
+{
+       struct stat st;
+       FILE *f;
+
+       if (stat(filename, &st))
+               return error("Could not stat %s", filename);
+       if ((f = fopen(filename, "rb")) == NULL)
+               return error("Could not open %s", filename);
+       ptr->ptr = xmalloc(st.st_size);
+       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
+               return error("Could not read %s", filename);
+       fclose(f);
+       ptr->size = st.st_size;
+       return 0;
+}
+
+
index 1346908bea31319aabeabdfd955e2ea9aab37456..1918808081c00daf20a16a1592c0ed9be6d79be4 100644 (file)
@@ -17,5 +17,6 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
 int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);
+int read_mmfile(mmfile_t *ptr, const char *filename);
 
 #endif