Merge branch 'js/rerere'
authorJunio C Hamano <junkio@cox.net>
Sun, 24 Dec 2006 10:10:55 +0000 (02:10 -0800)
committerJunio C Hamano <junkio@cox.net>
Sun, 24 Dec 2006 10:10:55 +0000 (02:10 -0800)
* js/rerere:
Make git-rerere a builtin
Add a test for git-rerere
move read_mmfile() into xdiff-interface

20 files changed:
Documentation/Makefile
Documentation/git-show-branch.txt
Documentation/install-doc-quick.sh [new file with mode: 0755]
Makefile
builtin-commit-tree.c
builtin-show-branch.c
compat/mmap.c
configure.ac
contrib/emacs/vc-git.el
git-checkout.sh
git-compat-util.h
git-merge.sh
git-parse-remote.sh
git-rebase.sh
git-svn.perl
git-tag.sh
git.c
gitweb/gitweb.perl
utf8.c [new file with mode: 0644]
utf8.h [new file with mode: 0644]
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 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
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 154bf4cf52db44088e123b208b2dd07713bcfde1..8827dee59ccee9365b19113e28034ef822776026 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
@@ -232,7 +229,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 \
@@ -251,7 +249,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 \
@@ -549,9 +548,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 +826,8 @@ install: all
 install-doc:
        $(MAKE) -C Documentation install
 
+quick-install-doc:
+       $(MAKE) -C Documentation quick-install
 
 
 
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 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 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 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"))))))))
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 a55b923894196b5c4a5af32cd773b24824f14cec..5d9eb2615b2e21979f547199121828aafbf02135 100644 (file)
@@ -11,7 +11,7 @@
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
-#if !defined(__APPLE__) && !defined(__FreeBSD)
+#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
@@ -87,10 +87,10 @@ extern void set_warn_routine(void (*routine)(const char *warn, 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 4ebfcf65d99f7743048df901b38969c28cc11edb..aec215e725826fb18a4d3d75408aa44acdd3e5de 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
index f163821d033cc0dc81dcd7ce0af13a7e75986db1..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" ;;
@@ -145,13 +125,6 @@ canon_refs_list_for_fetch () {
                        merge_branches=$(git-repo-config \
                            --get-all "branch.${curr_branch}.merge")
                fi
-               # If we are fetching only one branch, then first branch
-               # is the only thing that makes sense to merge anyway,
-               # so there is no point refusing that traditional rule.
-               if test $# != 1 && test "z$merge_branches" = z
-               then
-                       merge_branches=..this..would..never..match..
-               fi
        fi
        for ref
        do
@@ -203,7 +176,7 @@ canon_refs_list_for_fetch () {
 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 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 07748bc3e3f4091613c8e78647a471ac21ee46d2..4288a05c166b0b75bba387c099daa8622fea487a 100755 (executable)
                        { 'merge|m|M' => \$_merge,
                          'strategy|s=s' => \$_strategy,
                          'dry-run|n' => \$_dry_run,
-                       %cmt_opts } ],
+                       %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",
index 36cd6aa256db765aa741099b8d3c63b50f58048f..e1bfa82f1ea1213fad8ff8c4b6ad2b17e6ca1f7e 100755 (executable)
@@ -40,7 +40,6 @@ do
        message="$1"
        if test "$#" = "0"; then
            die "error: option -m needs an argument"
-           exit 2
        else
            message_given=1
        fi
@@ -50,7 +49,6 @@ do
        shift
        if test "$#" = "0"; then
            die "error: option -F needs an argument"
-           exit 2
        else
            message="$(cat "$1")"
            message_given=1
diff --git a/git.c b/git.c
index 7a01e02f2b21c029277564ad104c512cbc68b7ba..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)--;
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();
 }
 
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