Merge branch 'jc/am'
authorJunio C Hamano <junkio@cox.net>
Mon, 18 Sep 2006 01:20:02 +0000 (18:20 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 18 Sep 2006 01:20:02 +0000 (18:20 -0700)
* jc/am:
Fix git-am safety checks

36 files changed:
.gitignore
Documentation/config.txt
Documentation/git-apply.txt
Documentation/git-archive.txt [new file with mode: 0644]
Documentation/git-repack.txt
Documentation/git-upload-archive.txt [new file with mode: 0644]
Makefile
archive.h [new file with mode: 0644]
builtin-apply.c
builtin-archive.c [new file with mode: 0644]
builtin-runstatus.c [new file with mode: 0644]
builtin-tar-tree.c
builtin-upload-archive.c [new file with mode: 0644]
builtin-zip-tree.c
builtin.h
color.c [new file with mode: 0644]
color.h [new file with mode: 0644]
daemon.c
diff.c
diff.h
dir.c
dir.h
fetch-clone.c
fetch-pack.c
generate-cmdlist.sh
git-commit.sh
git.c
gitweb/gitweb.perl
sideband.c [new file with mode: 0644]
sideband.h [new file with mode: 0644]
t/t1400-update-ref.sh
t/t3403-rebase-skip.sh
t/t4104-apply-boundary.sh [new file with mode: 0755]
upload-pack.c
wt-status.c [new file with mode: 0644]
wt-status.h [new file with mode: 0644]
index 0d608fe12ada909959a524ec60c4a6aa491bd3fc..a3d9c7a11dc8e2807db377090fd1d4144aad0056 100644 (file)
@@ -8,6 +8,7 @@ git-apply
 git-applymbox
 git-applypatch
 git-archimport
+git-archive
 git-bisect
 git-branch
 git-cat-file
@@ -94,6 +95,7 @@ git-rev-list
 git-rev-parse
 git-revert
 git-rm
+git-runstatus
 git-send-email
 git-send-pack
 git-sh-setup
@@ -118,6 +120,7 @@ git-unpack-objects
 git-update-index
 git-update-ref
 git-update-server-info
+git-upload-archive
 git-upload-pack
 git-upload-tar
 git-var
index ce722a2db07e2a41cfdebc235538cf4ca2a170ad..844cae4cf024b17206021d1c55819922cf99277d 100644 (file)
@@ -225,6 +225,20 @@ showbranch.default::
        The default set of branches for gitlink:git-show-branch[1].
        See gitlink:git-show-branch[1].
 
+status.color::
+       A boolean to enable/disable color in the output of
+       gitlink:git-status[1]. May be set to `true` (or `always`),
+       `false` (or `never`) or `auto`, in which case colors are used
+       only when the output is to a terminal. Defaults to false.
+
+status.color.<slot>::
+       Use customized color for status colorization. `<slot>` is
+       one of `header` (the header text of the status message),
+       `updated` (files which are updated but not committed),
+       `changed` (files which are changed but not updated in the index),
+       or `untracked` (files which are not tracked by git). The values of
+       these variables may be specified as in diff.color.<slot>.
+
 tar.umask::
        By default, gitlink:git-tar-tree[1] sets file and directories modes
        to 0666 or 0777. While this is both useful and acceptable for projects
index 0a6f7b32192df8a8a2266b0d302271a62c612b1f..d9137c74898f839a8ee5e79daf1080b2ec882dfd 100644 (file)
@@ -95,6 +95,16 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
+--unidiff-zero::
+       By default, gitlink:git-apply[1] expects that the patch being
+       applied is a unified diff with at least one line of context.
+       This provides good safety measures, but breaks down when
+       applying a diff generated with --unified=0. To bypass these
+       checks use '--unidiff-zero'.
++
+Note, for the reasons stated above usage of context-free patches are
+discouraged.
+
 --apply::
        If you use any of the options marked "Turns off
        'apply'" above, gitlink:git-apply[1] reads and outputs the
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
new file mode 100644 (file)
index 0000000..913528d
--- /dev/null
@@ -0,0 +1,100 @@
+git-archive(1)
+==============
+
+NAME
+----
+git-archive - Creates a archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+             [--remote=<repo>] <tree-ish> [path...]
+
+DESCRIPTION
+-----------
+Creates an archive of the specified format containing the tree
+structure for the named tree.  If <prefix> is specified it is
+prepended to the filenames in the archive.
+
+'git-archive' behaves differently when given a tree ID versus when
+given a commit ID or tag ID.  In the first case the current time is
+used as modification time of each file in the archive.  In the latter
+case the commit time as recorded in the referenced commit object is
+used instead.  Additionally the commit ID is stored in a global
+extended pax header if the tar format is used; it can be extracted
+using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
+comment.
+
+OPTIONS
+-------
+
+--format=<fmt>::
+       Format of the resulting archive: 'tar', 'zip'...
+
+--list::
+       Show all available formats.
+
+--prefix=<prefix>/::
+       Prepend <prefix>/ to each filename in the archive.
+
+<extra>::
+       This can be any options that the archiver backend understand.
+
+--remote=<repo>::
+       Instead of making a tar archive from local repository,
+       retrieve a tar archive from a remote repository.
+
+<tree-ish>::
+       The tree or commit to produce an archive for.
+
+path::
+       If one or more paths are specified, include only these in the
+       archive, otherwise include all files and subdirectories.
+
+CONFIGURATION
+-------------
+By default, file and directories modes are set to 0666 or 0777 in tar
+archives.  It is possible to change this by setting the "umask" variable
+in the repository configuration as follows :
+
+[tar]
+        umask = 002    ;# group friendly
+
+The special umask value "user" indicates that the user's current umask
+will be used instead. The default value remains 0, which means world
+readable/writable files and directories.
+
+EXAMPLES
+--------
+git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
+
+       Create a tar archive that contains the contents of the
+       latest commit on the current branch, and extracts it in
+       `/var/tmp/junk` directory.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
+
+       Create a compressed tarball for v1.4.0 release.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
+
+       Create a compressed tarball for v1.4.0 release, but without a
+       global extended pax header.
+
+git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
+
+       Put everything in the current head's Documentation/ directory
+       into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
+
+Author
+------
+Written by Franck Bui-Huu and Rene Scharfe.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 951622774af70e92590740c27fe021223f29442a..49f7e0a4a446b8e393b76f39136f28c74c085dff 100644 (file)
@@ -9,7 +9,7 @@ objects into pack files.
 
 SYNOPSIS
 --------
-'git-repack' [-a] [-d] [-f] [-l] [-n] [-q]
+'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
 
 DESCRIPTION
 -----------
@@ -56,6 +56,16 @@ OPTIONS
         Do not update the server information with
         `git update-server-info`.
 
+--window=[N], --depth=[N]::
+       These two options affects how the objects contained in the pack are
+       stored using delta compression. The objects are first internally
+       sorted by type, size and optionally names and compared against the
+       other objects within `--window` to see if using delta compression saves
+       space. `--depth` limits the maximum delta depth; making it too deep
+       affects the performance on the unpacker side, because delta data needs
+       to be applied that many times to get to the necessary object.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
new file mode 100644 (file)
index 0000000..388bb53
--- /dev/null
@@ -0,0 +1,37 @@
+git-upload-archive(1)
+====================
+
+NAME
+----
+git-upload-archive - Send archive
+
+
+SYNOPSIS
+--------
+'git-upload-archive' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-archive --remote' and sends a generated archive to the
+other end over the git protocol.
+
+This command is usually not invoked directly by the end user.  The UI
+for the protocol is on the 'git-archive' side, and the program pair
+is meant to be used to get an archive from a remote repository.
+
+OPTIONS
+-------
+<directory>::
+       The repository to get a tar archive from.
+
+Author
+------
+Written by Franck Bui-Huu.
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 7b3114f3aac8f040b2d7f291e62a980e282b722e..69915d865168817f72c084f15930e41bf5b9ad45 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -126,6 +126,8 @@ GITWEB_CONFIG = gitweb_config.perl
 GITWEB_HOME_LINK_STR = projects
 GITWEB_SITENAME =
 GITWEB_PROJECTROOT = /pub/git
+GITWEB_EXPORT_OK =
+GITWEB_STRICT_EXPORT =
 GITWEB_BASE_URL =
 GITWEB_LIST =
 GITWEB_HOMETEXT = indextext.html
@@ -232,8 +234,8 @@ LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
 
 LIB_H = \
-       blob.h cache.h commit.h csum-file.h delta.h \
-       diff.h object.h pack.h pkt-line.h quote.h refs.h \
+       archive.h blob.h cache.h commit.h csum-file.h delta.h \
+       diff.h object.h pack.h pkt-line.h quote.h refs.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
 
@@ -245,17 +247,19 @@ DIFF_OBJS = \
 LIB_OBJS = \
        blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \
-       object.o pack-check.o patch-delta.o path.o pkt-line.o \
+       object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
        quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o \
-       alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS)
+       alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
+       color.o wt-status.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
        builtin-apply.o \
+       builtin-archive.o \
        builtin-cat-file.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
@@ -285,6 +289,7 @@ BUILTIN_OBJS = \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-rm.o \
+       builtin-runstatus.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
@@ -292,6 +297,7 @@ BUILTIN_OBJS = \
        builtin-unpack-objects.o \
        builtin-update-index.o \
        builtin-update-ref.o \
+       builtin-upload-archive.o \
        builtin-upload-tar.o \
        builtin-verify-pack.o \
        builtin-write-tree.o \
@@ -631,6 +637,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
            -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
            -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
            -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
+           -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
+           -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
            -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
            -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
            -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
diff --git a/archive.h b/archive.h
new file mode 100644 (file)
index 0000000..16dcdb8
--- /dev/null
+++ b/archive.h
@@ -0,0 +1,47 @@
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+#define MAX_EXTRA_ARGS 32
+#define MAX_ARGS       (MAX_EXTRA_ARGS + 32)
+
+struct archiver_args {
+       const char *base;
+       struct tree *tree;
+       const unsigned char *commit_sha1;
+       time_t time;
+       const char **pathspec;
+       unsigned int verbose : 1;
+       void *extra;
+};
+
+typedef int (*write_archive_fn_t)(struct archiver_args *);
+
+typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv);
+
+struct archiver {
+       const char *name;
+       struct archiver_args args;
+       write_archive_fn_t write_archive;
+       parse_extra_args_fn_t parse_extra;
+};
+
+extern struct archiver archivers[];
+
+extern int parse_archive_args(int argc,
+                             const char **argv,
+                             struct archiver *ar);
+
+extern void parse_treeish_arg(const char **treeish,
+                             struct archiver_args *ar_args,
+                             const char *prefix);
+
+extern void parse_pathspec_arg(const char **pathspec,
+                              struct archiver_args *args);
+/*
+ * Archive-format specific backends.
+ */
+extern int write_tar_archive(struct archiver_args *);
+extern int write_zip_archive(struct archiver_args *);
+extern void *parse_extra_zip_args(int argc, const char **argv);
+
+#endif /* ARCHIVE_H */
index 6e0864ce27619d1153575876e2a617a392906aed..25e90d8d29a8f55b20a98425edccfe49cad4c8e2 100644 (file)
@@ -27,6 +27,7 @@ static const char *prefix;
 static int prefix_length = -1;
 static int newfd = -1;
 
+static int unidiff_zero;
 static int p_value = 1;
 static int check_index;
 static int write_index;
@@ -854,11 +855,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
 }
 
 /*
- * Parse a unified diff. Note that this really needs
- * to parse each fragment separately, since the only
- * way to know the difference between a "---" that is
- * part of a patch, and a "---" that starts the next
- * patch is to look at the line counts..
+ * Parse a unified diff. Note that this really needs to parse each
+ * fragment separately, since the only way to know the difference
+ * between a "---" that is part of a patch, and a "---" that starts
+ * the next patch is to look at the line counts..
  */
 static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
 {
@@ -875,31 +875,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
        leading = 0;
        trailing = 0;
 
-       if (patch->is_new < 0) {
-               patch->is_new =  !oldlines;
-               if (!oldlines)
-                       patch->old_name = NULL;
-       }
-       if (patch->is_delete < 0) {
-               patch->is_delete = !newlines;
-               if (!newlines)
-                       patch->new_name = NULL;
-       }
-
-       if (patch->is_new && oldlines)
-               return error("new file depends on old contents");
-       if (patch->is_delete != !newlines) {
-               if (newlines)
-                       return error("deleted file still has contents");
-               fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
-       }
-
        /* Parse the thing.. */
        line += len;
        size -= len;
        linenr++;
        added = deleted = 0;
-       for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+       for (offset = len;
+            0 < size;
+            offset += len, size -= len, line += len, linenr++) {
                if (!oldlines && !newlines)
                        break;
                len = linelen(line, size);
@@ -972,12 +955,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
 
        patch->lines_added += added;
        patch->lines_deleted += deleted;
+
+       if (0 < patch->is_new && oldlines)
+               return error("new file depends on old contents");
+       if (0 < patch->is_delete && newlines)
+               return error("deleted file still has contents");
        return offset;
 }
 
 static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
 {
        unsigned long offset = 0;
+       unsigned long oldlines = 0, newlines = 0, context = 0;
        struct fragment **fragp = &patch->fragments;
 
        while (size > 4 && !memcmp(line, "@@ -", 4)) {
@@ -988,9 +977,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                len = parse_fragment(line, size, patch, fragment);
                if (len <= 0)
                        die("corrupt patch at line %d", linenr);
-
                fragment->patch = line;
                fragment->size = len;
+               oldlines += fragment->oldlines;
+               newlines += fragment->newlines;
+               context += fragment->leading + fragment->trailing;
 
                *fragp = fragment;
                fragp = &fragment->next;
@@ -999,6 +990,46 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                line += len;
                size -= len;
        }
+
+       /*
+        * If something was removed (i.e. we have old-lines) it cannot
+        * be creation, and if something was added it cannot be
+        * deletion.  However, the reverse is not true; --unified=0
+        * patches that only add are not necessarily creation even
+        * though they do not have any old lines, and ones that only
+        * delete are not necessarily deletion.
+        *
+        * Unfortunately, a real creation/deletion patch do _not_ have
+        * any context line by definition, so we cannot safely tell it
+        * apart with --unified=0 insanity.  At least if the patch has
+        * more than one hunk it is not creation or deletion.
+        */
+       if (patch->is_new < 0 &&
+           (oldlines || (patch->fragments && patch->fragments->next)))
+               patch->is_new = 0;
+       if (patch->is_delete < 0 &&
+           (newlines || (patch->fragments && patch->fragments->next)))
+               patch->is_delete = 0;
+       if (!unidiff_zero || context) {
+               /* If the user says the patch is not generated with
+                * --unified=0, or if we have seen context lines,
+                * then not having oldlines means the patch is creation,
+                * and not having newlines means the patch is deletion.
+                */
+               if (patch->is_new < 0 && !oldlines)
+                       patch->is_new = 1;
+               if (patch->is_delete < 0 && !newlines)
+                       patch->is_delete = 1;
+       }
+
+       if (0 < patch->is_new && oldlines)
+               die("new file %s depends on old contents", patch->new_name);
+       if (0 < patch->is_delete && newlines)
+               die("deleted file %s still has contents", patch->old_name);
+       if (!patch->is_delete && !newlines && context)
+               fprintf(stderr, "** warning: file %s becomes empty but "
+                       "is not deleted\n", patch->new_name);
+
        return offset;
 }
 
@@ -1556,9 +1587,19 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
        /*
         * If we don't have any leading/trailing data in the patch,
         * we want it to match at the beginning/end of the file.
+        *
+        * But that would break if the patch is generated with
+        * --unified=0; sane people wouldn't do that to cause us
+        * trouble, but we try to please not so sane ones as well.
         */
-       match_beginning = !leading && (frag->oldpos == 1);
-       match_end = !trailing;
+       if (unidiff_zero) {
+               match_beginning = (!leading && !frag->oldpos);
+               match_end = 0;
+       }
+       else {
+               match_beginning = !leading && (frag->oldpos == 1);
+               match_end = !trailing;
+       }
 
        lines = 0;
        pos = frag->newpos;
@@ -1804,7 +1845,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        patch->result = desc.buffer;
        patch->resultsize = desc.size;
 
-       if (patch->is_delete && patch->resultsize)
+       if (0 < patch->is_delete && patch->resultsize)
                return error("removal patch leaves file contents");
 
        return 0;
@@ -1876,7 +1917,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
                                old_name, st_mode, patch->old_mode);
        }
 
-       if (new_name && prev_patch && prev_patch->is_delete &&
+       if (new_name && prev_patch && 0 < prev_patch->is_delete &&
            !strcmp(prev_patch->old_name, new_name))
                /* A type-change diff is always split into a patch to
                 * delete old, immediately followed by a patch to
@@ -1889,7 +1930,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
        else
                ok_if_exists = 0;
 
-       if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+       if (new_name &&
+           ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
                if (check_index &&
                    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
                    !ok_if_exists)
@@ -1906,7 +1948,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
                                return error("%s: %s", new_name, strerror(errno));
                }
                if (!patch->new_mode) {
-                       if (patch->is_new)
+                       if (0 < patch->is_new)
                                patch->new_mode = S_IFREG | 0644;
                        else
                                patch->new_mode = patch->old_mode;
@@ -1957,7 +1999,7 @@ static void show_index_list(struct patch *list)
                const char *name;
 
                name = patch->old_name ? patch->old_name : patch->new_name;
-               if (patch->is_new)
+               if (0 < patch->is_new)
                        sha1_ptr = null_sha1;
                else if (get_sha1(patch->old_sha1_prefix, sha1))
                        die("sha1 information is lacking or useless (%s).",
@@ -2543,6 +2585,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
                        apply_in_reverse = 1;
                        continue;
                }
+               if (!strcmp(arg, "--unidiff-zero")) {
+                       unidiff_zero = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--reject")) {
                        apply = apply_with_reject = apply_verbosely = 1;
                        continue;
diff --git a/builtin-archive.c b/builtin-archive.c
new file mode 100644 (file)
index 0000000..6dabdee
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "commit.h"
+#include "tree-walk.h"
+#include "exec_cmd.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static const char archive_usage[] = \
+"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
+
+struct archiver archivers[] = {
+       {
+               .name           = "tar",
+               .write_archive  = write_tar_archive,
+       },
+       {
+               .name           = "zip",
+               .write_archive  = write_zip_archive,
+               .parse_extra    = parse_extra_zip_args,
+       },
+};
+
+static int run_remote_archiver(const char *remote, int argc,
+                              const char **argv)
+{
+       char *url, buf[LARGE_PACKET_MAX];
+       int fd[2], i, len, rv;
+       pid_t pid;
+       const char *exec = "git-upload-archive";
+       int exec_at = 0;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strncmp("--exec=", arg, 7)) {
+                       if (exec_at)
+                               die("multiple --exec specified");
+                       exec = arg + 7;
+                       exec_at = i;
+                       break;
+               }
+       }
+
+       url = xstrdup(remote);
+       pid = git_connect(fd, url, exec);
+       if (pid < 0)
+               return pid;
+
+       for (i = 1; i < argc; i++) {
+               if (i == exec_at)
+                       continue;
+               packet_write(fd[1], "argument %s\n", argv[i]);
+       }
+       packet_flush(fd[1]);
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (!len)
+               die("git-archive: expected ACK/NAK, got EOF");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (strcmp(buf, "ACK")) {
+               if (len > 5 && !strncmp(buf, "NACK ", 5))
+                       die("git-archive: NACK %s", buf + 5);
+               die("git-archive: protocol error");
+       }
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (len)
+               die("git-archive: expected a flush");
+
+       /* Now, start reading from fd[0] and spit it out to stdout */
+       rv = recv_sideband("archive", fd[0], 1, 2, buf, sizeof(buf));
+       close(fd[0]);
+       rv |= finish_connect(pid);
+
+       return !!rv;
+}
+
+static int init_archiver(const char *name, struct archiver *ar)
+{
+       int rv = -1, i;
+
+       for (i = 0; i < ARRAY_SIZE(archivers); i++) {
+               if (!strcmp(name, archivers[i].name)) {
+                       memcpy(ar, &archivers[i], sizeof(struct archiver));
+                       rv = 0;
+                       break;
+               }
+       }
+       return rv;
+}
+
+void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args)
+{
+       ar_args->pathspec = get_pathspec(ar_args->base, pathspec);
+}
+
+void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
+                      const char *prefix)
+{
+       const char *name = argv[0];
+       const unsigned char *commit_sha1;
+       time_t archive_time;
+       struct tree *tree;
+       struct commit *commit;
+       unsigned char sha1[20];
+
+       if (get_sha1(name, sha1))
+               die("Not a valid object name");
+
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (commit) {
+               commit_sha1 = commit->object.sha1;
+               archive_time = commit->date;
+       } else {
+               commit_sha1 = NULL;
+               archive_time = time(NULL);
+       }
+
+       tree = parse_tree_indirect(sha1);
+       if (tree == NULL)
+               die("not a tree object");
+
+       if (prefix) {
+               unsigned char tree_sha1[20];
+               unsigned int mode;
+               int err;
+
+               err = get_tree_entry(tree->object.sha1, prefix,
+                                    tree_sha1, &mode);
+               if (err || !S_ISDIR(mode))
+                       die("current working directory is untracked");
+
+               free(tree);
+               tree = parse_tree_indirect(tree_sha1);
+       }
+       ar_args->tree = tree;
+       ar_args->commit_sha1 = commit_sha1;
+       ar_args->time = archive_time;
+}
+
+int parse_archive_args(int argc, const char **argv, struct archiver *ar)
+{
+       const char *extra_argv[MAX_EXTRA_ARGS];
+       int extra_argc = 0;
+       const char *format = NULL; /* might want to default to "tar" */
+       const char *base = "";
+       int verbose = 0;
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) {
+                       for (i = 0; i < ARRAY_SIZE(archivers); i++)
+                               printf("%s\n", archivers[i].name);
+                       exit(0);
+               }
+               if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--format=", 9)) {
+                       format = arg + 9;
+                       continue;
+               }
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       base = arg + 9;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (arg[0] == '-') {
+                       if (extra_argc > MAX_EXTRA_ARGS - 1)
+                               die("Too many extra options");
+                       extra_argv[extra_argc++] = arg;
+                       continue;
+               }
+               break;
+       }
+
+       /* We need at least one parameter -- tree-ish */
+       if (argc - 1 < i)
+               usage(archive_usage);
+       if (!format)
+               die("You must specify an archive format");
+       if (init_archiver(format, ar) < 0)
+               die("Unknown archive format '%s'", format);
+
+       if (extra_argc) {
+               if (!ar->parse_extra)
+                       die("'%s' format does not handle %s",
+                           ar->name, extra_argv[0]);
+               ar->args.extra = ar->parse_extra(extra_argc, extra_argv);
+       }
+       ar->args.verbose = verbose;
+       ar->args.base = base;
+
+       return i;
+}
+
+static const char *extract_remote_arg(int *ac, const char **av)
+{
+       int ix, iy, cnt = *ac;
+       int no_more_options = 0;
+       const char *remote = NULL;
+
+       for (ix = iy = 1; ix < cnt; ix++) {
+               const char *arg = av[ix];
+               if (!strcmp(arg, "--"))
+                       no_more_options = 1;
+               if (!no_more_options) {
+                       if (!strncmp(arg, "--remote=", 9)) {
+                               if (remote)
+                                       die("Multiple --remote specified");
+                               remote = arg + 9;
+                               continue;
+                       }
+                       if (arg[0] != '-')
+                               no_more_options = 1;
+               }
+               if (ix != iy)
+                       av[iy] = arg;
+               iy++;
+       }
+       if (remote) {
+               av[--cnt] = NULL;
+               *ac = cnt;
+       }
+       return remote;
+}
+
+int cmd_archive(int argc, const char **argv, const char *prefix)
+{
+       struct archiver ar;
+       int tree_idx;
+       const char *remote = NULL;
+
+       remote = extract_remote_arg(&argc, argv);
+       if (remote)
+               return run_remote_archiver(remote, argc, argv);
+
+       setlinebuf(stderr);
+
+       memset(&ar, 0, sizeof(ar));
+       tree_idx = parse_archive_args(argc, argv, &ar);
+       if (prefix == NULL)
+               prefix = setup_git_directory();
+
+       argv += tree_idx;
+       parse_treeish_arg(argv, &ar.args, prefix);
+       parse_pathspec_arg(argv + 1, &ar.args);
+
+       return ar.write_archive(&ar.args);
+}
diff --git a/builtin-runstatus.c b/builtin-runstatus.c
new file mode 100644 (file)
index 0000000..303c556
--- /dev/null
@@ -0,0 +1,36 @@
+#include "wt-status.h"
+#include "cache.h"
+
+extern int wt_status_use_color;
+
+static const char runstatus_usage[] =
+"git-runstatus [--color|--nocolor] [--amend] [--verbose]";
+
+int cmd_runstatus(int argc, const char **argv, const char *prefix)
+{
+       struct wt_status s;
+       int i;
+
+       git_config(git_status_config);
+       wt_status_prepare(&s);
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp(argv[i], "--color"))
+                       wt_status_use_color = 1;
+               else if (!strcmp(argv[i], "--nocolor"))
+                       wt_status_use_color = 0;
+               else if (!strcmp(argv[i], "--amend")) {
+                       s.amend = 1;
+                       s.reference = "HEAD^1";
+               }
+               else if (!strcmp(argv[i], "--verbose"))
+                       s.verbose = 1;
+               else if (!strcmp(argv[i], "--untracked"))
+                       s.untracked = 1;
+               else
+                       usage(runstatus_usage);
+       }
+
+       wt_status_print(&s);
+       return s.commitable ? 0 : 1;
+}
index fa666f78c5b5e44617495abb2716eded8407626c..437eb726a9b207353545726f379ab8638c0eecb9 100644 (file)
@@ -3,12 +3,12 @@
  */
 #include <time.h>
 #include "cache.h"
-#include "tree-walk.h"
 #include "commit.h"
 #include "strbuf.h"
 #include "tar.h"
 #include "builtin.h"
 #include "pkt-line.h"
+#include "archive.h"
 
 #define RECORDSIZE     (512)
 #define BLOCKSIZE      (RECORDSIZE * 20)
@@ -21,6 +21,7 @@ static unsigned long offset;
 
 static time_t archive_time;
 static int tar_umask;
+static int verbose;
 
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
@@ -168,6 +169,8 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
                mode = 0100666;
                sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
        } else {
+               if (verbose)
+                       fprintf(stderr, "%.*s\n", path->len, path->buf);
                if (S_ISDIR(mode)) {
                        *header.typeflag = TYPEFLAG_DIR;
                        mode = (mode | 0777) & ~tar_umask;
@@ -244,37 +247,6 @@ static void write_global_extended_header(const unsigned char *sha1)
        free(ext_header.buf);
 }
 
-static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
-{
-       int pathlen = path->len;
-       struct name_entry entry;
-
-       while (tree_entry(tree, &entry)) {
-               void *eltbuf;
-               char elttype[20];
-               unsigned long eltsize;
-
-               eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
-               if (!eltbuf)
-                       die("cannot read %s", sha1_to_hex(entry.sha1));
-
-               path->len = pathlen;
-               strbuf_append_string(path, entry.path);
-               if (S_ISDIR(entry.mode))
-                       strbuf_append_string(path, "/");
-
-               write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
-
-               if (S_ISDIR(entry.mode)) {
-                       struct tree_desc subtree;
-                       subtree.buf = eltbuf;
-                       subtree.size = eltsize;
-                       traverse_tree(&subtree, path);
-               }
-               free(eltbuf);
-       }
-}
-
 static int git_tar_config(const char *var, const char *value)
 {
        if (!strcmp(var, "tar.umask")) {
@@ -291,50 +263,95 @@ static int git_tar_config(const char *var, const char *value)
 
 static int generate_tar(int argc, const char **argv, const char *prefix)
 {
-       unsigned char sha1[20], tree_sha1[20];
-       struct commit *commit;
-       struct tree_desc tree;
-       struct strbuf current_path;
-       void *buffer;
-
-       current_path.buf = xmalloc(PATH_MAX);
-       current_path.alloc = PATH_MAX;
-       current_path.len = current_path.eof = 0;
+       struct archiver_args args;
+       int result;
+       char *base = NULL;
 
        git_config(git_tar_config);
 
-       switch (argc) {
-       case 3:
-               strbuf_append_string(&current_path, argv[2]);
-               strbuf_append_string(&current_path, "/");
-               /* FALLTHROUGH */
-       case 2:
-               if (get_sha1(argv[1], sha1))
-                       die("Not a valid object name %s", argv[1]);
-               break;
-       default:
+       memset(&args, 0, sizeof(args));
+       if (argc != 2 && argc != 3)
                usage(tar_tree_usage);
+       if (argc == 3) {
+               int baselen = strlen(argv[2]);
+               base = xmalloc(baselen + 2);
+               memcpy(base, argv[2], baselen);
+               base[baselen] = '/';
+               base[baselen + 1] = '\0';
        }
+       args.base = base;
+       parse_treeish_arg(argv + 1, &args, NULL);
 
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (commit) {
-               write_global_extended_header(commit->object.sha1);
-               archive_time = commit->date;
-       } else
-               archive_time = time(NULL);
-
-       tree.buf = buffer = read_object_with_reference(sha1, tree_type,
-                                                      &tree.size, tree_sha1);
-       if (!tree.buf)
-               die("not a reference to a tag, commit or tree object: %s",
-                   sha1_to_hex(sha1));
-
-       if (current_path.len > 0)
-               write_entry(tree_sha1, &current_path, 040777, NULL, 0);
-       traverse_tree(&tree, &current_path);
-       write_trailer();
+       result = write_tar_archive(&args);
+       free(base);
+
+       return result;
+}
+
+static int write_tar_entry(const unsigned char *sha1,
+                           const char *base, int baselen,
+                           const char *filename, unsigned mode, int stage)
+{
+       static struct strbuf path;
+       int filenamelen = strlen(filename);
+       void *buffer;
+       char type[20];
+       unsigned long size;
+
+       if (!path.alloc) {
+               path.buf = xmalloc(PATH_MAX);
+               path.alloc = PATH_MAX;
+               path.len = path.eof = 0;
+       }
+       if (path.alloc < baselen + filenamelen) {
+               free(path.buf);
+               path.buf = xmalloc(baselen + filenamelen);
+               path.alloc = baselen + filenamelen;
+       }
+       memcpy(path.buf, base, baselen);
+       memcpy(path.buf + baselen, filename, filenamelen);
+       path.len = baselen + filenamelen;
+       if (S_ISDIR(mode)) {
+               strbuf_append_string(&path, "/");
+               buffer = NULL;
+               size = 0;
+       } else {
+               buffer = read_sha1_file(sha1, type, &size);
+               if (!buffer)
+                       die("cannot read %s", sha1_to_hex(sha1));
+       }
+
+       write_entry(sha1, &path, mode, buffer, size);
        free(buffer);
-       free(current_path.buf);
+
+       return READ_TREE_RECURSIVE;
+}
+
+int write_tar_archive(struct archiver_args *args)
+{
+       int plen = args->base ? strlen(args->base) : 0;
+
+       git_config(git_tar_config);
+
+       archive_time = args->time;
+       verbose = args->verbose;
+
+       if (args->commit_sha1)
+               write_global_extended_header(args->commit_sha1);
+
+       if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+               char *base = xstrdup(args->base);
+               int baselen = strlen(base);
+
+               while (baselen > 0 && base[baselen - 1] == '/')
+                       base[--baselen] = '\0';
+               write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+               free(base);
+       }
+       read_tree_recursive(args->tree, args->base, plen, 0,
+                           args->pathspec, write_tar_entry);
+       write_trailer();
+
        return 0;
 }
 
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
new file mode 100644 (file)
index 0000000..0596865
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ */
+#include <time.h>
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include <sys/wait.h>
+#include <sys/poll.h>
+
+static const char upload_archive_usage[] =
+       "git-upload-archive <repo>";
+
+static const char deadchild[] =
+"git-upload-archive: archiver died with error";
+
+static const char lostchild[] =
+"git-upload-archive: archiver process was lost";
+
+
+static int run_upload_archive(int argc, const char **argv, const char *prefix)
+{
+       struct archiver ar;
+       const char *sent_argv[MAX_ARGS];
+       const char *arg_cmd = "argument ";
+       char *p, buf[4096];
+       int treeish_idx;
+       int sent_argc;
+       int len;
+
+       if (argc != 2)
+               usage(upload_archive_usage);
+
+       if (strlen(argv[1]) > sizeof(buf))
+               die("insanely long repository name");
+
+       strcpy(buf, argv[1]); /* enter-repo smudges its argument */
+
+       if (!enter_repo(buf, 0))
+               die("not a git archive");
+
+       /* put received options in sent_argv[] */
+       sent_argc = 1;
+       sent_argv[0] = "git-upload-archive";
+       for (p = buf;;) {
+               /* This will die if not enough free space in buf */
+               len = packet_read_line(0, p, (buf + sizeof buf) - p);
+               if (len == 0)
+                       break;  /* got a flush */
+               if (sent_argc > MAX_ARGS - 2)
+                       die("Too many options (>29)");
+
+               if (p[len-1] == '\n') {
+                       p[--len] = 0;
+               }
+               if (len < strlen(arg_cmd) ||
+                   strncmp(arg_cmd, p, strlen(arg_cmd)))
+                       die("'argument' token or flush expected");
+
+               len -= strlen(arg_cmd);
+               memmove(p, p + strlen(arg_cmd), len);
+               sent_argv[sent_argc++] = p;
+               p += len;
+               *p++ = 0;
+       }
+       sent_argv[sent_argc] = NULL;
+
+       /* parse all options sent by the client */
+       treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar);
+
+       parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix);
+       parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args);
+
+       return ar.write_archive(&ar.args);
+}
+
+static void error_clnt(const char *fmt, ...)
+{
+       char buf[1024];
+       va_list params;
+       int len;
+
+       va_start(params, fmt);
+       len = vsprintf(buf, fmt, params);
+       va_end(params);
+       send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
+       die("sent error to the client: %s", buf);
+}
+
+static void process_input(int child_fd, int band)
+{
+       char buf[16384];
+       ssize_t sz = read(child_fd, buf, sizeof(buf));
+       if (sz < 0) {
+               if (errno != EINTR)
+                       error_clnt("read error: %s\n", strerror(errno));
+               return;
+       }
+       send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
+}
+
+int cmd_upload_archive(int argc, const char **argv, const char *prefix)
+{
+       pid_t writer;
+       int fd1[2], fd2[2];
+       /*
+        * Set up sideband subprocess.
+        *
+        * We (parent) monitor and read from child, sending its fd#1 and fd#2
+        * multiplexed out to our fd#1.  If the child dies, we tell the other
+        * end over channel #3.
+        */
+       if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+               int err = errno;
+               packet_write(1, "NACK pipe failed on the remote side\n");
+               die("upload-archive: %s", strerror(err));
+       }
+       writer = fork();
+       if (writer < 0) {
+               int err = errno;
+               packet_write(1, "NACK fork failed on the remote side\n");
+               die("upload-archive: %s", strerror(err));
+       }
+       if (!writer) {
+               /* child - connect fd#1 and fd#2 to the pipe */
+               dup2(fd1[1], 1);
+               dup2(fd2[1], 2);
+               close(fd1[1]); close(fd2[1]);
+               close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
+
+               exit(run_upload_archive(argc, argv, prefix));
+       }
+
+       /* parent - read from child, multiplex and send out to fd#1 */
+       close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
+       packet_write(1, "ACK\n");
+       packet_flush(1);
+
+       while (1) {
+               struct pollfd pfd[2];
+               int status;
+
+               pfd[0].fd = fd1[0];
+               pfd[0].events = POLLIN;
+               pfd[1].fd = fd2[0];
+               pfd[1].events = POLLIN;
+               if (poll(pfd, 2, -1) < 0) {
+                       if (errno != EINTR) {
+                               error("poll failed resuming: %s",
+                                     strerror(errno));
+                               sleep(1);
+                       }
+                       continue;
+               }
+               if (pfd[0].revents & POLLIN)
+                       /* Data stream ready */
+                       process_input(pfd[0].fd, 1);
+               if (pfd[1].revents & POLLIN)
+                       /* Status stream ready */
+                       process_input(pfd[1].fd, 2);
+               /* Always finish to read data when available */
+               if ((pfd[0].revents | pfd[1].revents) & POLLIN)
+                       continue;
+
+               if (waitpid(writer, &status, 0) < 0)
+                       error_clnt("%s", lostchild);
+               else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+                       error_clnt("%s", deadchild);
+               packet_flush(1);
+               break;
+       }
+       return 0;
+}
index 1c1f6830c1a8b9303eec3b1c11cfb1805d3049e4..52d4b7a17e39831964fe7c9c9c0cb374d0d61a62 100644 (file)
@@ -8,10 +8,12 @@
 #include "tree.h"
 #include "quote.h"
 #include "builtin.h"
+#include "archive.h"
 
 static const char zip_tree_usage[] =
 "git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]";
 
+static int verbose;
 static int zip_date;
 static int zip_time;
 
@@ -163,6 +165,8 @@ static int write_zip_entry(const unsigned char *sha1,
        crc = crc32(0, Z_NULL, 0);
 
        path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);
+       if (verbose)
+               fprintf(stderr, "%s\n", path);
        if (pathlen > 0xffff) {
                error("path too long (%d chars, SHA1: %s): %s", pathlen,
                      sha1_to_hex(sha1), path);
@@ -351,3 +355,44 @@ int cmd_zip_tree(int argc, const char **argv, const char *prefix)
 
        return 0;
 }
+
+int write_zip_archive(struct archiver_args *args)
+{
+       int plen = strlen(args->base);
+
+       dos_time(&args->time, &zip_date, &zip_time);
+
+       zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
+       zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
+       verbose = args->verbose;
+
+       if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+               char *base = xstrdup(args->base);
+               int baselen = strlen(base);
+
+               while (baselen > 0 && base[baselen - 1] == '/')
+                       base[--baselen] = '\0';
+               write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+               free(base);
+       }
+       read_tree_recursive(args->tree, args->base, plen, 0,
+                           args->pathspec, write_zip_entry);
+       write_zip_trailer(args->commit_sha1);
+
+       free(zip_dir);
+
+       return 0;
+}
+
+void *parse_extra_zip_args(int argc, const char **argv)
+{
+       for (; argc > 0; argc--, argv++) {
+               const char *arg = argv[0];
+
+               if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0')
+                       zlib_compression_level = arg[1] - '0';
+               else
+                       die("Unknown argument for zip format: %s", arg);
+       }
+       return NULL;
+}
index 398eafbf995e4bf7a999ea9ff0e573c55e39790b..ccade94e26b34f9005b1ed2575b4c11cb6ceab22 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -14,6 +14,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_apply(int argc, const char **argv, const char *prefix);
+extern int cmd_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@@ -46,6 +47,7 @@ extern int cmd_repo_config(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);
+extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
@@ -55,6 +57,7 @@ extern int cmd_zip_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
diff --git a/color.c b/color.c
new file mode 100644 (file)
index 0000000..d8c8399
--- /dev/null
+++ b/color.c
@@ -0,0 +1,176 @@
+#include "color.h"
+#include "cache.h"
+#include "git-compat-util.h"
+
+#include <stdarg.h>
+
+#define COLOR_RESET "\033[m"
+
+static int parse_color(const char *name, int len)
+{
+       static const char * const color_names[] = {
+               "normal", "black", "red", "green", "yellow",
+               "blue", "magenta", "cyan", "white"
+       };
+       char *end;
+       int i;
+       for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+               const char *str = color_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return i - 1;
+       }
+       i = strtol(name, &end, 10);
+       if (*name && !*end && i >= -1 && i <= 255)
+               return i;
+       return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+       static const int attr_values[] = { 1, 2, 4, 5, 7 };
+       static const char * const attr_names[] = {
+               "bold", "dim", "ul", "blink", "reverse"
+       };
+       int i;
+       for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+               const char *str = attr_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return attr_values[i];
+       }
+       return -1;
+}
+
+void color_parse(const char *value, const char *var, char *dst)
+{
+       const char *ptr = value;
+       int attr = -1;
+       int fg = -2;
+       int bg = -2;
+
+       if (!strcasecmp(value, "reset")) {
+               strcpy(dst, "\033[m");
+               return;
+       }
+
+       /* [fg [bg]] [attr] */
+       while (*ptr) {
+               const char *word = ptr;
+               int val, len = 0;
+
+               while (word[len] && !isspace(word[len]))
+                       len++;
+
+               ptr = word + len;
+               while (*ptr && isspace(*ptr))
+                       ptr++;
+
+               val = parse_color(word, len);
+               if (val >= -1) {
+                       if (fg == -2) {
+                               fg = val;
+                               continue;
+                       }
+                       if (bg == -2) {
+                               bg = val;
+                               continue;
+                       }
+                       goto bad;
+               }
+               val = parse_attr(word, len);
+               if (val < 0 || attr != -1)
+                       goto bad;
+               attr = val;
+       }
+
+       if (attr >= 0 || fg >= 0 || bg >= 0) {
+               int sep = 0;
+
+               *dst++ = '\033';
+               *dst++ = '[';
+               if (attr >= 0) {
+                       *dst++ = '0' + attr;
+                       sep++;
+               }
+               if (fg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (fg < 8) {
+                               *dst++ = '3';
+                               *dst++ = '0' + fg;
+                       } else {
+                               dst += sprintf(dst, "38;5;%d", fg);
+                       }
+               }
+               if (bg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (bg < 8) {
+                               *dst++ = '4';
+                               *dst++ = '0' + bg;
+                       } else {
+                               dst += sprintf(dst, "48;5;%d", bg);
+                       }
+               }
+               *dst++ = 'm';
+       }
+       *dst = 0;
+       return;
+bad:
+       die("bad config value '%s' for variable '%s'", value, var);
+}
+
+int git_config_colorbool(const char *var, const char *value)
+{
+       if (!value)
+               return 1;
+       if (!strcasecmp(value, "auto")) {
+               if (isatty(1) || (pager_in_use && pager_use_color)) {
+                       char *term = getenv("TERM");
+                       if (term && strcmp(term, "dumb"))
+                               return 1;
+               }
+               return 0;
+       }
+       if (!strcasecmp(value, "never"))
+               return 0;
+       if (!strcasecmp(value, "always"))
+               return 1;
+       return git_config_bool(var, value);
+}
+
+static int color_vprintf(const char *color, const char *fmt,
+               va_list args, const char *trail)
+{
+       int r = 0;
+
+       if (*color)
+               r += printf("%s", color);
+       r += vprintf(fmt, args);
+       if (*color)
+               r += printf("%s", COLOR_RESET);
+       if (trail)
+               r += printf("%s", trail);
+       return r;
+}
+
+
+
+int color_printf(const char *color, const char *fmt, ...)
+{
+       va_list args;
+       int r;
+       va_start(args, fmt);
+       r = color_vprintf(color, fmt, args, NULL);
+       va_end(args);
+       return r;
+}
+
+int color_printf_ln(const char *color, const char *fmt, ...)
+{
+       va_list args;
+       int r;
+       va_start(args, fmt);
+       r = color_vprintf(color, fmt, args, "\n");
+       va_end(args);
+       return r;
+}
diff --git a/color.h b/color.h
new file mode 100644 (file)
index 0000000..88bb8ff
--- /dev/null
+++ b/color.h
@@ -0,0 +1,12 @@
+#ifndef COLOR_H
+#define COLOR_H
+
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+#define COLOR_MAXLEN 24
+
+int git_config_colorbool(const char *var, const char *value);
+void color_parse(const char *var, const char *value, char *dst);
+int color_printf(const char *color, const char *fmt, ...);
+int color_printf_ln(const char *color, const char *fmt, ...);
+
+#endif /* COLOR_H */
index b14d8083bb14bd28a824c4417712842b1f70d829..a2954a0451316197621aed152c194d398aaee624 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -325,7 +325,14 @@ static int upload_pack(void)
        return -1;
 }
 
+static int upload_archive(void)
+{
+       execl_git_cmd("upload-archive", ".", NULL);
+       return -1;
+}
+
 static struct daemon_service daemon_service[] = {
+       { "upload-archive", "uploadarch", upload_archive, 0, 1 },
        { "upload-pack", "uploadpack", upload_pack, 1, 1 },
 };
 
diff --git a/diff.c b/diff.c
index 6638865709888cc0ad2860ca1389a098b6f402f1..443e24861ba1857822dbe8e3c831f8037e4e832e 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -10,6 +10,7 @@
 #include "diffcore.h"
 #include "delta.h"
 #include "xdiff-interface.h"
+#include "color.h"
 
 static int use_size_cache;
 
@@ -17,8 +18,7 @@ static int diff_detect_rename_default;
 static int diff_rename_limit_default = -1;
 static int diff_use_color_default;
 
-/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
-static char diff_colors[][24] = {
+static char diff_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
        "",             /* normal */
        "\033[1m",      /* bold */
@@ -45,119 +45,6 @@ static int parse_diff_color_slot(const char *var, int ofs)
        die("bad config variable '%s'", var);
 }
 
-static int parse_color(const char *name, int len)
-{
-       static const char * const color_names[] = {
-               "normal", "black", "red", "green", "yellow",
-               "blue", "magenta", "cyan", "white"
-       };
-       char *end;
-       int i;
-       for (i = 0; i < ARRAY_SIZE(color_names); i++) {
-               const char *str = color_names[i];
-               if (!strncasecmp(name, str, len) && !str[len])
-                       return i - 1;
-       }
-       i = strtol(name, &end, 10);
-       if (*name && !*end && i >= -1 && i <= 255)
-               return i;
-       return -2;
-}
-
-static int parse_attr(const char *name, int len)
-{
-       static const int attr_values[] = { 1, 2, 4, 5, 7 };
-       static const char * const attr_names[] = {
-               "bold", "dim", "ul", "blink", "reverse"
-       };
-       int i;
-       for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
-               const char *str = attr_names[i];
-               if (!strncasecmp(name, str, len) && !str[len])
-                       return attr_values[i];
-       }
-       return -1;
-}
-
-static void parse_diff_color_value(const char *value, const char *var, char *dst)
-{
-       const char *ptr = value;
-       int attr = -1;
-       int fg = -2;
-       int bg = -2;
-
-       if (!strcasecmp(value, "reset")) {
-               strcpy(dst, "\033[m");
-               return;
-       }
-
-       /* [fg [bg]] [attr] */
-       while (*ptr) {
-               const char *word = ptr;
-               int val, len = 0;
-
-               while (word[len] && !isspace(word[len]))
-                       len++;
-
-               ptr = word + len;
-               while (*ptr && isspace(*ptr))
-                       ptr++;
-
-               val = parse_color(word, len);
-               if (val >= -1) {
-                       if (fg == -2) {
-                               fg = val;
-                               continue;
-                       }
-                       if (bg == -2) {
-                               bg = val;
-                               continue;
-                       }
-                       goto bad;
-               }
-               val = parse_attr(word, len);
-               if (val < 0 || attr != -1)
-                       goto bad;
-               attr = val;
-       }
-
-       if (attr >= 0 || fg >= 0 || bg >= 0) {
-               int sep = 0;
-
-               *dst++ = '\033';
-               *dst++ = '[';
-               if (attr >= 0) {
-                       *dst++ = '0' + attr;
-                       sep++;
-               }
-               if (fg >= 0) {
-                       if (sep++)
-                               *dst++ = ';';
-                       if (fg < 8) {
-                               *dst++ = '3';
-                               *dst++ = '0' + fg;
-                       } else {
-                               dst += sprintf(dst, "38;5;%d", fg);
-                       }
-               }
-               if (bg >= 0) {
-                       if (sep++)
-                               *dst++ = ';';
-                       if (bg < 8) {
-                               *dst++ = '4';
-                               *dst++ = '0' + bg;
-                       } else {
-                               dst += sprintf(dst, "48;5;%d", bg);
-                       }
-               }
-               *dst++ = 'm';
-       }
-       *dst = 0;
-       return;
-bad:
-       die("bad config value '%s' for variable '%s'", value, var);
-}
-
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
@@ -171,22 +58,7 @@ int git_diff_ui_config(const char *var, const char *value)
                return 0;
        }
        if (!strcmp(var, "diff.color")) {
-               if (!value)
-                       diff_use_color_default = 1; /* bool */
-               else if (!strcasecmp(value, "auto")) {
-                       diff_use_color_default = 0;
-                       if (isatty(1) || (pager_in_use && pager_use_color)) {
-                               char *term = getenv("TERM");
-                               if (term && strcmp(term, "dumb"))
-                                       diff_use_color_default = 1;
-                       }
-               }
-               else if (!strcasecmp(value, "never"))
-                       diff_use_color_default = 0;
-               else if (!strcasecmp(value, "always"))
-                       diff_use_color_default = 1;
-               else
-                       diff_use_color_default = git_config_bool(var, value);
+               diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
@@ -201,7 +73,7 @@ int git_diff_ui_config(const char *var, const char *value)
        }
        if (!strncmp(var, "diff.color.", 11)) {
                int slot = parse_diff_color_slot(var, 11);
-               parse_diff_color_value(value, var, diff_colors[slot]);
+               color_parse(value, var, diff_colors[slot]);
                return 0;
        }
        return git_default_config(var, value);
@@ -2593,6 +2465,9 @@ void diff_flush(struct diff_options *options)
                }
        }
 
+       if (output_format & DIFF_FORMAT_CALLBACK)
+               options->format_callback(q, options, options->format_callback_data);
+
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
 free_queue:
diff --git a/diff.h b/diff.h
index b007240a5dda9a9e1e54f22813c0d250b812fa3e..b60a02e627415d4edaf5056e7f95b519b7fe8fc0 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -8,6 +8,7 @@
 
 struct rev_info;
 struct diff_options;
+struct diff_queue_struct;
 
 typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
@@ -20,6 +21,9 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
                    const unsigned char *sha1,
                    const char *base, const char *path);
 
+typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
+               struct diff_options *options, void *data);
+
 #define DIFF_FORMAT_RAW                0x0001
 #define DIFF_FORMAT_DIFFSTAT   0x0002
 #define DIFF_FORMAT_SUMMARY    0x0004
@@ -35,6 +39,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
  */
 #define DIFF_FORMAT_NO_OUTPUT  0x0080
 
+#define DIFF_FORMAT_CALLBACK   0x0100
+
 struct diff_options {
        const char *filter;
        const char *orderfile;
@@ -68,6 +74,8 @@ struct diff_options {
        int *pathlens;
        change_fn_t change;
        add_remove_fn_t add_remove;
+       diff_format_fn_t format_callback;
+       void *format_callback_data;
 };
 
 enum color_diff {
diff --git a/dir.c b/dir.c
index 5a40d8ff8c99c80bf8f0484425db6022c6f7d8e9..e2f472ba7f5d3e5146f110dd3eaae9e5494854e2 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -397,3 +397,10 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
+
+int
+file_exists(const char *f)
+{
+  struct stat sb;
+  return stat(f, &sb) == 0;
+}
diff --git a/dir.h b/dir.h
index 56a1b7fce224b489aa4ac91766cfb47a3097f197..313f8ab64e7e3a6be1d22335eb54872c929d857e 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *, const char *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *which);
+extern int file_exists(const char *);
 
 #endif
index c5cf4776fabb8f9f09028dd1f0cfaf1d55e1397c..b632ca0438b378944b37a7d64fd508ea3f7f470f 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "pkt-line.h"
+#include "sideband.h"
 #include <sys/wait.h>
 #include <sys/time.h>
 
@@ -114,36 +115,13 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
                die("%s: unable to fork off sideband demultiplexer", me);
        if (!side_pid) {
                /* subprocess */
+               char buf[LARGE_PACKET_MAX];
+
                close(fd[0]);
                if (xd[0] != xd[1])
                        close(xd[1]);
-               while (1) {
-                       char buf[1024];
-                       int len = packet_read_line(xd[0], buf, sizeof(buf));
-                       if (len == 0)
-                               break;
-                       if (len < 1)
-                               die("%s: protocol error: no band designator",
-                                   me);
-                       len--;
-                       switch (buf[0] & 0xFF) {
-                       case 3:
-                               safe_write(2, "remote: ", 8);
-                               safe_write(2, buf+1, len);
-                               safe_write(2, "\n", 1);
-                               exit(1);
-                       case 2:
-                               safe_write(2, "remote: ", 8);
-                               safe_write(2, buf+1, len);
-                               continue;
-                       case 1:
-                               safe_write(fd[1], buf+1, len);
-                               continue;
-                       default:
-                               die("%s: protocol error: bad band #%d",
-                                   me, (buf[0] & 0xFF));
-                       }
-               }
+               if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf)))
+                       exit(1);
                exit(0);
        }
        close(xd[0]);
index 1b4d8272dce834d10b16ecbcea7ae2fa974fc856..e8708aa802b8e09d8044bb99dbccb0fecdb14481 100644 (file)
@@ -166,10 +166,11 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                }
 
                if (!fetching)
-                       packet_write(fd[1], "want %s%s%s%s\n",
+                       packet_write(fd[1], "want %s%s%s%s%s\n",
                                     sha1_to_hex(remote),
                                     (multi_ack ? " multi_ack" : ""),
-                                    (use_sideband ? " side-band" : ""),
+                                    (use_sideband == 2 ? " side-band-64k" : ""),
+                                    (use_sideband == 1 ? " side-band" : ""),
                                     (use_thin_pack ? " thin-pack" : ""));
                else
                        packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -426,7 +427,12 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
                        fprintf(stderr, "Server supports multi_ack\n");
                multi_ack = 1;
        }
-       if (server_supports("side-band")) {
+       if (server_supports("side-band-64k")) {
+               if (verbose)
+                       fprintf(stderr, "Server supports side-band-64k\n");
+               use_sideband = 2;
+       }
+       else if (server_supports("side-band")) {
                if (verbose)
                        fprintf(stderr, "Server supports side-band\n");
                use_sideband = 1;
index ec1eda20de8d510a9acfd981150a5f1a438a0fe4..5450918be339bf101a6562d518c0f4cc7b37c0f2 100755 (executable)
@@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {"
 sort <<\EOF |
 add
 apply
+archive
 bisect
 branch
 checkout
index 4cf3fab05cd3c3367173c86a65665ddfc1d89805..5a4c659b6fb5f0b23c6a10b0e3bd69893d9c380a 100755 (executable)
@@ -60,26 +60,6 @@ report () {
 }
 
 run_status () {
-    (
-       # We always show status for the whole tree.
-       cd "$TOP"
-
-       IS_INITIAL="$initial_commit"
-       REFERENCE=HEAD
-       case "$amend" in
-       t)
-               # If we are amending the initial commit, there
-               # is no HEAD^1.
-               if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1
-               then
-                       REFERENCE="HEAD^1"
-                       IS_INITIAL=
-               else
-                       IS_INITIAL=t
-               fi
-               ;;
-       esac
-
        # If TMP_INDEX is defined, that means we are doing
        # "--only" partial commit, and that index file is used
        # to build the tree for the commit.  Otherwise, if
@@ -96,85 +76,14 @@ run_status () {
            export GIT_INDEX_FILE
        fi
 
-       case "$branch" in
-       refs/heads/master) ;;
-       *)  echo "# On branch $branch" ;;
-       esac
-
-       if test -z "$IS_INITIAL"
-       then
-           git-diff-index -M --cached --name-status \
-               --diff-filter=MDTCRA $REFERENCE |
-           sed -e '
-                   s/\\/\\\\/g
-                   s/ /\\ /g
-           ' |
-           report "Updated but not checked in" "will commit"
-           committable="$?"
-       else
-           echo '#
-# Initial commit
-#'
-           git-ls-files |
-           sed -e '
-                   s/\\/\\\\/g
-                   s/ /\\ /g
-                   s/^/A /
-           ' |
-           report "Updated but not checked in" "will commit"
-
-           committable="$?"
-       fi
-
-       git-diff-files  --name-status |
-       sed -e '
-               s/\\/\\\\/g
-               s/ /\\ /g
-       ' |
-       report "Changed but not updated" \
-           "use git-update-index to mark for commit"
-
-        option=""
-        if test -z "$untracked_files"; then
-            option="--directory --no-empty-directory"
-        fi
-       hdr_shown=
-       if test -f "$GIT_DIR/info/exclude"
-       then
-           git-ls-files --others $option \
-               --exclude-from="$GIT_DIR/info/exclude" \
-               --exclude-per-directory=.gitignore
-       else
-           git-ls-files --others $option \
-               --exclude-per-directory=.gitignore
-       fi |
-       while read line; do
-           if [ -z "$hdr_shown" ]; then
-               echo '#'
-               echo '# Untracked files:'
-               echo '#   (use "git add" to add to commit)'
-               echo '#'
-               hdr_shown=1
-           fi
-           echo "#     $line"
-       done
-
-       if test -n "$verbose" -a -z "$IS_INITIAL"
-       then
-           git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE
-       fi
-       case "$committable" in
-       0)
-               case "$amend" in
-               t)
-                       echo "# No changes" ;;
-               *)
-                       echo "nothing to commit" ;;
-               esac
-               exit 1 ;;
-       esac
-       exit 0
-    )
+  case "$status_only" in
+    t) color= ;;
+    *) color=--nocolor ;;
+  esac
+  git-runstatus ${color} \
+                ${verbose:+--verbose} \
+                ${amend:+--amend} \
+               ${untracked_files:+--untracked}
 }
 
 trap '
diff --git a/git.c b/git.c
index 8c182a5f5581d345e7bf69116f42fa3af089b2d5..44ab0de94d84e3b09d753b1da85679efcfd793a0 100644 (file)
--- a/git.c
+++ b/git.c
@@ -220,6 +220,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
        } commands[] = {
                { "add", cmd_add, RUN_SETUP },
                { "apply", cmd_apply },
+               { "archive", cmd_archive },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
@@ -252,6 +253,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "rm", cmd_rm, RUN_SETUP },
+               { "runstatus", cmd_runstatus, RUN_SETUP },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
@@ -261,6 +263,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "update-ref", cmd_update_ref, RUN_SETUP },
+               { "upload-archive", cmd_upload_archive },
                { "upload-tar", cmd_upload_tar },
                { "version", cmd_version },
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
index a81c8d4cf2d605232ae49aed36c3b8bd0f7ccfee..c77270c7cd7ef478f708dbbc6165ac420891ca5a 100755 (executable)
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
+# show repository only if this file exists
+# (only effective if this variable evaluates to true)
+our $export_ok = "++GITWEB_EXPORT_OK++";
+
+# only allow viewing of repositories also shown on the overview page
+our $strict_export = "++GITWEB_STRICT_EXPORT++";
+
 # list of git base URLs used for URL to where fetch project from,
 # i.e. full URL is "$git_base_url/$project"
 our @git_base_url_list = ("++GITWEB_BASE_URL++");
@@ -182,9 +189,6 @@ sub feature_pickaxe {
 # version of the core git binary
 our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 
-# path to the current git repository
-our $git_dir;
-
 $projects_list ||= $projectroot;
 
 # ======================================================================
@@ -196,23 +200,16 @@ sub feature_pickaxe {
        }
 }
 
-our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
-if (defined $project) {
-       $project =~ s|^/||;
-       $project =~ s|/$||;
-       $project = undef unless $project;
-}
+our $project = $cgi->param('p');
 if (defined $project) {
-       if (!validate_input($project)) {
-               die_error(undef, "Invalid project parameter");
-       }
-       if (!(-d "$projectroot/$project")) {
-               die_error(undef, "No such directory");
-       }
-       if (!(-e "$projectroot/$project/HEAD")) {
+       if (!validate_input($project) ||
+           !(-d "$projectroot/$project") ||
+           !(-e "$projectroot/$project/HEAD") ||
+           ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
+           ($strict_export && !project_in_list($project))) {
+               undef $project;
                die_error(undef, "No such project");
        }
-       $git_dir = "$projectroot/$project";
 }
 
 our $file_name = $cgi->param('f');
@@ -259,7 +256,7 @@ sub feature_pickaxe {
 
 our $page = $cgi->param('pg');
 if (defined $page) {
-       if ($page =~ m/[^0-9]$/) {
+       if ($page =~ m/[^0-9]/) {
                die_error(undef, "Invalid page parameter");
        }
 }
@@ -272,6 +269,43 @@ sub feature_pickaxe {
        $searchtext = quotemeta $searchtext;
 }
 
+# now read PATH_INFO and use it as alternative to parameters
+sub evaluate_path_info {
+       return if defined $project;
+       my $path_info = $ENV{"PATH_INFO"};
+       return if !$path_info;
+       $path_info =~ s,(^/|/$),,gs;
+       $path_info = validate_input($path_info);
+       return if !$path_info;
+       $project = $path_info;
+       while ($project && !-e "$projectroot/$project/HEAD") {
+               $project =~ s,/*[^/]*$,,;
+       }
+       if (!$project ||
+           ($export_ok && !-e "$projectroot/$project/$export_ok") ||
+           ($strict_export && !project_in_list($project))) {
+               undef $project;
+               return;
+       }
+       # do not change any parameters if an action is given using the query string
+       return if $action;
+       if ($path_info =~ m,^$project/([^/]+)/(.+)$,) {
+               # we got "project.git/branch/filename"
+               $action    ||= "blob_plain";
+               $hash_base ||= validate_input($1);
+               $file_name ||= validate_input($2);
+       } elsif ($path_info =~ m,^$project/([^/]+)$,) {
+               # we got "project.git/branch"
+               $action ||= "shortlog";
+               $hash   ||= validate_input($1);
+       }
+}
+evaluate_path_info();
+
+# path to the current git repository
+our $git_dir;
+$git_dir = "$projectroot/$project" if $project;
+
 # dispatch
 my %actions = (
        "blame" => \&git_blame2,
@@ -405,6 +439,12 @@ sub untabify {
        return $line;
 }
 
+sub project_in_list {
+       my $project = shift;
+       my @list = git_get_projects_list();
+       return @list && scalar(grep { $_->{'path'} eq $project } @list);
+}
+
 ## ----------------------------------------------------------------------
 ## HTML aware string manipulation
 
@@ -717,7 +757,8 @@ sub git_get_projects_list {
 
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
-                               if (-e "$projectroot/$subdir/HEAD") {
+                               if (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
+                                   -e "$projectroot/$subdir/$export_ok")) {
                                        push @list, { path => $subdir };
                                        $File::Find::prune = 1;
                                }
@@ -738,7 +779,8 @@ sub git_get_projects_list {
                        if (!defined $path) {
                                next;
                        }
-                       if (-e "$projectroot/$path/HEAD") {
+                       if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
+                           -e "$projectroot/$path/$export_ok")) {
                                my $pr = {
                                        path => $path,
                                        owner => decode("utf8", $owner, Encode::FB_DEFAULT),
@@ -2527,11 +2569,7 @@ sub git_heads {
 }
 
 sub git_blob_plain {
-       # blobs defined by non-textual hash id's can be cached
        my $expires;
-       if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
-               $expires = "+1d";
-       }
 
        if (!defined $hash) {
                if (defined $file_name) {
@@ -2541,7 +2579,11 @@ sub git_blob_plain {
                } else {
                        die_error(undef, "No file name defined");
                }
+       } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+               # blobs defined by non-textual hash id's can be cached
+               $expires = "+1d";
        }
+
        my $type = shift;
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(undef, "Couldn't cat $file_name, $hash");
@@ -2569,11 +2611,7 @@ sub git_blob_plain {
 }
 
 sub git_blob {
-       # blobs defined by non-textual hash id's can be cached
        my $expires;
-       if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
-               $expires = "+1d";
-       }
 
        if (!defined $hash) {
                if (defined $file_name) {
@@ -2583,7 +2621,11 @@ sub git_blob {
                } else {
                        die_error(undef, "No file name defined");
                }
+       } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+               # blobs defined by non-textual hash id's can be cached
+               $expires = "+1d";
        }
+
        my ($have_blame) = gitweb_check_feature('blame');
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(undef, "Couldn't cat $file_name, $hash");
diff --git a/sideband.c b/sideband.c
new file mode 100644 (file)
index 0000000..1b14ff8
--- /dev/null
@@ -0,0 +1,74 @@
+#include "pkt-line.h"
+#include "sideband.h"
+
+/*
+ * Receive multiplexed output stream over git native protocol.
+ * in_stream is the input stream from the remote, which carries data
+ * in pkt_line format with band designator.  Demultiplex it into out
+ * and err and return error appropriately.  Band #1 carries the
+ * primary payload.  Things coming over band #2 is not necessarily
+ * error; they are usually informative message on the standard error
+ * stream, aka "verbose").  A message over band #3 is a signal that
+ * the remote died unexpectedly.  A flush() concludes the stream.
+ */
+int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz)
+{
+       while (1) {
+               int len = packet_read_line(in_stream, buf, bufsz);
+               if (len == 0)
+                       break;
+               if (len < 1) {
+                       len = sprintf(buf, "%s: protocol error: no band designator\n", me);
+                       safe_write(err, buf, len);
+                       return SIDEBAND_PROTOCOL_ERROR;
+               }
+               len--;
+               switch (buf[0] & 0xFF) {
+               case 3:
+                       safe_write(err, "remote: ", 8);
+                       safe_write(err, buf+1, len);
+                       safe_write(err, "\n", 1);
+                       return SIDEBAND_REMOTE_ERROR;
+               case 2:
+                       safe_write(err, "remote: ", 8);
+                       safe_write(err, buf+1, len);
+                       continue;
+               case 1:
+                       safe_write(out, buf+1, len);
+                       continue;
+               default:
+                       len = sprintf(buf + 1,
+                                     "%s: protocol error: bad band #%d\n",
+                                     me, buf[0] & 0xFF);
+                       safe_write(err, buf+1, len);
+                       return SIDEBAND_PROTOCOL_ERROR;
+               }
+       }
+       return 0;
+}
+
+/*
+ * fd is connected to the remote side; send the sideband data
+ * over multiplexed packet stream.
+ */
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max)
+{
+       ssize_t ssz = sz;
+       const char *p = data;
+
+       while (sz) {
+               unsigned n;
+               char hdr[5];
+
+               n = sz;
+               if (packet_max - 5 < n)
+                       n = packet_max - 5;
+               sprintf(hdr, "%04x", n + 5);
+               hdr[4] = band;
+               safe_write(fd, hdr, 5);
+               safe_write(fd, p, n);
+               p += n;
+               sz -= n;
+       }
+       return ssz;
+}
diff --git a/sideband.h b/sideband.h
new file mode 100644 (file)
index 0000000..4872106
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef SIDEBAND_H
+#define SIDEBAND_H
+
+#define SIDEBAND_PROTOCOL_ERROR -2
+#define SIDEBAND_REMOTE_ERROR -1
+
+#define DEFAULT_PACKET_MAX 1000
+#define LARGE_PACKET_MAX 65520
+
+int recv_sideband(const char *me, int in_stream, int out, int err, char *, int);
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
+
+#endif
index ddc80bbeae7703b8c4a2ddcf12d8b1ec9c6bbe46..b3b920edb146c3ad1ed8c795150c162a9e3f0e17 100755 (executable)
@@ -19,51 +19,51 @@ n=$n_dir/fixes
 
 test_expect_success \
        "create $m" \
-       'git-update-ref $m $A &&
-        test $A = $(cat .git/$m)'
+       "git-update-ref $m $A &&
+        test $A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "create $m" \
-       'git-update-ref $m $B $A &&
-        test $B = $(cat .git/$m)'
+       "git-update-ref $m $B $A &&
+        test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
 test_expect_success \
        "fail to create $n" \
-       'touch .git/$n_dir
+       "touch .git/$n_dir
         git-update-ref $n $A >out 2>err
-        test $? = 1 &&
+        test "'$? = 1 &&
         test "" = "$(cat out)" &&
         grep "error: unable to resolve reference" err &&
-        grep $n err'
+        grep '"$n err"
 rm -f .git/$n_dir out err
 
 test_expect_success \
        "create $m (by HEAD)" \
-       'git-update-ref HEAD $A &&
-        test $A = $(cat .git/$m)'
+       "git-update-ref HEAD $A &&
+        test $A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "create $m (by HEAD)" \
-       'git-update-ref HEAD $B $A &&
-        test $B = $(cat .git/$m)'
+       "git-update-ref HEAD $B $A &&
+        test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
 test_expect_failure \
        '(not) create HEAD with old sha1' \
-       'git-update-ref HEAD $A $B'
+       "git-update-ref HEAD $A $B"
 test_expect_failure \
        "(not) prior created .git/$m" \
-       'test -f .git/$m'
+       "test -f .git/$m"
 rm -f .git/$m
 
 test_expect_success \
        "create HEAD" \
-       'git-update-ref HEAD $A'
+       "git-update-ref HEAD $A"
 test_expect_failure \
        '(not) change HEAD with wrong SHA1' \
-       'git-update-ref HEAD $B $Z'
+       "git-update-ref HEAD $B $Z"
 test_expect_failure \
        "(not) changed .git/$m" \
-       'test $B = $(cat .git/$m)'
+       "test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
 mkdir -p .git/logs/refs/heads
@@ -71,18 +71,18 @@ touch .git/logs/refs/heads/master
 test_expect_success \
        "create $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-        git-update-ref HEAD $A -m "Initial Creation" &&
-        test $A = $(cat .git/$m)'
+        git-update-ref HEAD '"$A"' -m "Initial Creation" &&
+        test '"$A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "update $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:31" \
-        git-update-ref HEAD $B $A -m "Switch" &&
-        test $B = $(cat .git/$m)'
+        git-update-ref HEAD'" $B $A "'-m "Switch" &&
+        test '"$B"' = $(cat .git/'"$m"')'
 test_expect_success \
        "set $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:41" \
-        git-update-ref HEAD $A &&
-        test $A = $(cat .git/$m)'
+        git-update-ref HEAD'" $A &&
+        test $A"' = $(cat .git/'"$m"')'
 
 cat >expect <<EOF
 $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000      Initial Creation
@@ -91,7 +91,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
 EOF
 test_expect_success \
        "verifying $m's log" \
-       'diff expect .git/logs/$m'
+       "diff expect .git/logs/$m"
 rm -rf .git/$m .git/logs expect
 
 test_expect_success \
@@ -102,18 +102,18 @@ test_expect_success \
 test_expect_success \
        "create $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:32" \
-        git-update-ref HEAD $A -m "Initial Creation" &&
-        test $A = $(cat .git/$m)'
+        git-update-ref HEAD'" $A "'-m "Initial Creation" &&
+        test '"$A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "update $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:33" \
-        git-update-ref HEAD $B $A -m "Switch" &&
-        test $B = $(cat .git/$m)'
+        git-update-ref HEAD'" $B $A "'-m "Switch" &&
+        test '"$B"' = $(cat .git/'"$m"')'
 test_expect_success \
        "set $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:43" \
-        git-update-ref HEAD $A &&
-        test $A = $(cat .git/$m)'
+        git-update-ref HEAD '"$A &&
+        test $A"' = $(cat .git/'"$m"')'
 
 cat >expect <<EOF
 $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000      Initial Creation
@@ -140,50 +140,50 @@ test_expect_success \
        'Query "master@{May 25 2005}" (before history)' \
        'rm -f o e
         git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
-        test $C = $(cat o) &&
-        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+        test '"$C"' = $(cat o) &&
+        test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
 test_expect_success \
        "Query master@{2005-05-25} (before history)" \
        'rm -f o e
         git-rev-parse --verify master@{2005-05-25} >o 2>e &&
-        test $C = $(cat o) &&
-        echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+        test '"$C"' = $(cat o) &&
+        echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
 test_expect_success \
        'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
        'rm -f o e
         git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
-        test $C = $(cat o) &&
-        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+        test '"$C"' = $(cat o) &&
+        test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
 test_expect_success \
        'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
        'rm -f o e
         git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
-        test $A = $(cat o) &&
+        test '"$A"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
        'rm -f o e
         git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
-        test $B = $(cat o) &&
-        test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+        test '"$B"' = $(cat o) &&
+        test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
        'rm -f o e
         git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
-        test $Z = $(cat o) &&
+        test '"$Z"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
        'rm -f o e
         git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
-        test $E = $(cat o) &&
+        test '"$E"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-28}" (past end of history)' \
        'rm -f o e
         git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
-        test $D = $(cat o) &&
-        test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+        test '"$D"' = $(cat o) &&
+        test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
 
 
 rm -f .git/$m .git/logs/$m expect
@@ -221,7 +221,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000      c
 EOF
 test_expect_success \
        'git-commit logged updates' \
-       'diff expect .git/logs/$m'
+       "diff expect .git/logs/$m"
 unset h_TEST h_OTHER h_FIXED h_MERGED
 
 test_expect_success \
index 8ab63c52765d6608dba3b4fcf0b80833af240a67..bb2531536160bb0a6510cc316d8d74a1da2312e7 100755 (executable)
@@ -37,7 +37,9 @@ test_expect_success setup '
        git branch skip-merge skip-reference
        '
 
-test_expect_failure 'rebase with git am -3 (default)' 'git rebase master'
+test_expect_failure 'rebase with git am -3 (default)' '
+       git rebase master
+'
 
 test_expect_success 'rebase --skip with am -3' '
        git reset --hard HEAD &&
diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh
new file mode 100755 (executable)
index 0000000..2ff800c
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply boundary tests
+
+'
+. ./test-lib.sh
+
+L="c d e f g h i j k l m n o p q r s t u v w x"
+
+test_expect_success setup '
+       for i in b '"$L"' y
+       do
+               echo $i
+       done >victim &&
+       cat victim >original &&
+       git update-index --add victim &&
+
+       : add to the head
+       for i in a b '"$L"' y
+       do
+               echo $i
+       done >victim &&
+       cat victim >add-a-expect &&
+       git diff victim >add-a-patch.with &&
+       git diff --unified=0 >add-a-patch.without &&
+
+       : modify at the head
+       for i in a '"$L"' y
+       do
+               echo $i
+       done >victim &&
+       cat victim >mod-a-expect &&
+       git diff victim >mod-a-patch.with &&
+       git diff --unified=0 >mod-a-patch.without &&
+
+       : remove from the head
+       for i in '"$L"' y
+       do
+               echo $i
+       done >victim &&
+       cat victim >del-a-expect &&
+       git diff victim >del-a-patch.with
+       git diff --unified=0 >del-a-patch.without &&
+
+       : add to the tail
+       for i in b '"$L"' y z
+       do
+               echo $i
+       done >victim &&
+       cat victim >add-z-expect &&
+       git diff victim >add-z-patch.with &&
+       git diff --unified=0 >add-z-patch.without &&
+
+       : modify at the tail
+       for i in a '"$L"' y
+       do
+               echo $i
+       done >victim &&
+       cat victim >mod-z-expect &&
+       git diff victim >mod-z-patch.with &&
+       git diff --unified=0 >mod-z-patch.without &&
+
+       : remove from the tail
+       for i in b '"$L"'
+       do
+               echo $i
+       done >victim &&
+       cat victim >del-z-expect &&
+       git diff victim >del-z-patch.with
+       git diff --unified=0 >del-z-patch.without &&
+
+       : done
+'
+
+for with in with without
+do
+       case "$with" in
+       with) u= ;;
+       without) u='--unidiff-zero ' ;;
+       esac
+       for kind in add-a add-z mod-a mod-z del-a del-z
+       do
+               test_expect_success "apply $kind-patch $with context" '
+                       cat original >victim &&
+                       git update-index victim &&
+                       git apply --index '"$u$kind-patch.$with"' || {
+                               cat '"$kind-patch.$with"'
+                               (exit 1)
+                       } &&
+                       diff -u '"$kind"'-expect victim
+               '
+       done
+done
+
+for kind in add-a add-z mod-a mod-z del-a del-z
+do
+       rm -f $kind-ng.without
+       sed     -e "s/^diff --git /diff /" \
+               -e '/^index /d' \
+               <$kind-patch.without >$kind-ng.without
+       test_expect_success "apply non-git $kind-patch without context" '
+               cat original >victim &&
+               git update-index victim &&
+               git apply --unidiff-zero --index '"$kind-ng.without"' || {
+                       cat '"$kind-ng.without"'
+                       (exit 1)
+               } &&
+               diff -u '"$kind"'-expect victim
+       '
+done
+
+test_done
index 51ce936b060d34a2f759dba4f7aeeecf47a59a4b..b673d8cb9748de0758d1c5269178e70a018f43a1 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "sideband.h"
 #include "tag.h"
 #include "object.h"
 #include "commit.h"
@@ -19,6 +20,9 @@ static int use_thin_pack;
 static struct object_array have_obj;
 static struct object_array want_obj;
 static unsigned int timeout;
+/* 0 for no sideband,
+ * otherwise maximum packet size (up to 65520 bytes).
+ */
 static int use_sideband;
 
 static void reset_timeout(void)
@@ -33,45 +37,18 @@ static int strip(char *line, int len)
        return len;
 }
 
-#define PACKET_MAX 1000
 static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
 {
-       ssize_t ssz;
-       const char *p;
-
-       if (!data) {
-               if (!use_sideband)
-                       return 0;
-               packet_flush(1);
-       }
-
-       if (!use_sideband) {
-               if (fd == 3)
-                       /* emergency quit */
-                       fd = 2;
-               if (fd == 2) {
-                       xwrite(fd, data, sz);
-                       return sz;
-               }
-               return safe_write(fd, data, sz);
-       }
-       p = data;
-       ssz = sz;
-       while (sz) {
-               unsigned n;
-               char hdr[5];
-
-               n = sz;
-               if (PACKET_MAX - 5 < n)
-                       n = PACKET_MAX - 5;
-               sprintf(hdr, "%04x", n + 5);
-               hdr[4] = fd;
-               safe_write(1, hdr, 5);
-               safe_write(1, p, n);
-               p += n;
-               sz -= n;
+       if (use_sideband)
+               return send_sideband(1, fd, data, sz, use_sideband);
+       if (fd == 3)
+               /* emergency quit */
+               fd = 2;
+       if (fd == 2) {
+               xwrite(fd, data, sz);
+               return sz;
        }
-       return ssz;
+       return safe_write(fd, data, sz);
 }
 
 static void create_pack_file(void)
@@ -308,7 +285,8 @@ static void create_pack_file(void)
                                goto fail;
                        fprintf(stderr, "flushed.\n");
                }
-               send_client_data(1, NULL, 0);
+               if (use_sideband)
+                       packet_flush(1);
                return;
        }
  fail:
@@ -413,8 +391,10 @@ static void receive_needs(void)
                        multi_ack = 1;
                if (strstr(line+45, "thin-pack"))
                        use_thin_pack = 1;
-               if (strstr(line+45, "side-band"))
-                       use_sideband = 1;
+               if (strstr(line+45, "side-band-64k"))
+                       use_sideband = LARGE_PACKET_MAX;
+               else if (strstr(line+45, "side-band"))
+                       use_sideband = DEFAULT_PACKET_MAX;
 
                /* We have sent all our refs already, and the other end
                 * should have chosen out of them; otherwise they are
@@ -436,7 +416,7 @@ static void receive_needs(void)
 
 static int send_ref(const char *refname, const unsigned char *sha1)
 {
-       static const char *capabilities = "multi_ack thin-pack side-band";
+       static const char *capabilities = "multi_ack thin-pack side-band side-band-64k";
        struct object *o = parse_object(sha1);
 
        if (!o)
diff --git a/wt-status.c b/wt-status.c
new file mode 100644 (file)
index 0000000..4b74e68
--- /dev/null
@@ -0,0 +1,276 @@
+#include "wt-status.h"
+#include "color.h"
+#include "cache.h"
+#include "object.h"
+#include "dir.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "diffcore.h"
+
+int wt_status_use_color = 0;
+static char wt_status_colors[][COLOR_MAXLEN] = {
+       "",         /* WT_STATUS_HEADER: normal */
+       "\033[32m", /* WT_STATUS_UPDATED: green */
+       "\033[31m", /* WT_STATUS_CHANGED: red */
+       "\033[31m", /* WT_STATUS_UNTRACKED: red */
+};
+
+static int parse_status_slot(const char *var, int offset)
+{
+       if (!strcasecmp(var+offset, "header"))
+               return WT_STATUS_HEADER;
+       if (!strcasecmp(var+offset, "updated"))
+               return WT_STATUS_UPDATED;
+       if (!strcasecmp(var+offset, "changed"))
+               return WT_STATUS_CHANGED;
+       if (!strcasecmp(var+offset, "untracked"))
+               return WT_STATUS_UNTRACKED;
+       die("bad config variable '%s'", var);
+}
+
+static const char* color(int slot)
+{
+       return wt_status_use_color ? wt_status_colors[slot] : "";
+}
+
+void wt_status_prepare(struct wt_status *s)
+{
+       unsigned char sha1[20];
+       const char *head;
+
+       s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0;
+
+       head = resolve_ref(git_path("HEAD"), sha1, 0);
+       s->branch = head ?
+                   strdup(head + strlen(get_git_dir()) + 1) :
+                   NULL;
+
+       s->reference = "HEAD";
+       s->amend = 0;
+       s->verbose = 0;
+       s->commitable = 0;
+       s->untracked = 0;
+}
+
+static void wt_status_print_header(const char *main, const char *sub)
+{
+       const char *c = color(WT_STATUS_HEADER);
+       color_printf_ln(c, "# %s:", main);
+       color_printf_ln(c, "#   (%s)", sub);
+       color_printf_ln(c, "#");
+}
+
+static void wt_status_print_trailer(void)
+{
+       color_printf_ln(color(WT_STATUS_HEADER), "#");
+}
+
+static void wt_status_print_filepair(int t, struct diff_filepair *p)
+{
+       const char *c = color(t);
+       color_printf(color(WT_STATUS_HEADER), "#\t");
+       switch (p->status) {
+       case DIFF_STATUS_ADDED:
+               color_printf(c, "new file: %s", p->one->path); break;
+       case DIFF_STATUS_COPIED:
+               color_printf(c, "copied: %s -> %s",
+                               p->one->path, p->two->path);
+               break;
+       case DIFF_STATUS_DELETED:
+               color_printf(c, "deleted: %s", p->one->path); break;
+       case DIFF_STATUS_MODIFIED:
+               color_printf(c, "modified: %s", p->one->path); break;
+       case DIFF_STATUS_RENAMED:
+               color_printf(c, "renamed: %s -> %s",
+                               p->one->path, p->two->path);
+               break;
+       case DIFF_STATUS_TYPE_CHANGED:
+               color_printf(c, "typechange: %s", p->one->path); break;
+       case DIFF_STATUS_UNKNOWN:
+               color_printf(c, "unknown: %s", p->one->path); break;
+       case DIFF_STATUS_UNMERGED:
+               color_printf(c, "unmerged: %s", p->one->path); break;
+       default:
+               die("bug: unhandled diff status %c", p->status);
+       }
+       printf("\n");
+}
+
+static void wt_status_print_updated_cb(struct diff_queue_struct *q,
+               struct diff_options *options,
+               void *data)
+{
+       struct wt_status *s = data;
+       int shown_header = 0;
+       int i;
+       if (q->nr) {
+       }
+       for (i = 0; i < q->nr; i++) {
+               if (q->queue[i]->status == 'U')
+                       continue;
+               if (!shown_header) {
+                       wt_status_print_header("Updated but not checked in",
+                                       "will commit");
+                       s->commitable = 1;
+                       shown_header = 1;
+               }
+               wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+       }
+       if (shown_header)
+               wt_status_print_trailer();
+}
+
+static void wt_status_print_changed_cb(struct diff_queue_struct *q,
+                        struct diff_options *options,
+                        void *data)
+{
+       int i;
+       if (q->nr)
+               wt_status_print_header("Changed but not updated",
+                               "use git-update-index to mark for commit");
+       for (i = 0; i < q->nr; i++)
+               wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+       if (q->nr)
+               wt_status_print_trailer();
+}
+
+void wt_status_print_initial(struct wt_status *s)
+{
+       int i;
+       read_cache();
+       if (active_nr) {
+               s->commitable = 1;
+               wt_status_print_header("Updated but not checked in",
+                               "will commit");
+       }
+       for (i = 0; i < active_nr; i++) {
+               color_printf(color(WT_STATUS_HEADER), "#\t");
+               color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
+                               active_cache[i]->name);
+       }
+       if (active_nr)
+               wt_status_print_trailer();
+}
+
+static void wt_status_print_updated(struct wt_status *s)
+{
+       struct rev_info rev;
+       const char *argv[] = { NULL, NULL, NULL };
+       argv[1] = s->reference;
+       init_revisions(&rev, NULL);
+       setup_revisions(2, argv, &rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = wt_status_print_updated_cb;
+       rev.diffopt.format_callback_data = s;
+       rev.diffopt.detect_rename = 1;
+       run_diff_index(&rev, 1);
+}
+
+static void wt_status_print_changed(struct wt_status *s)
+{
+       struct rev_info rev;
+       const char *argv[] = { NULL, NULL };
+       init_revisions(&rev, "");
+       setup_revisions(1, argv, &rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = wt_status_print_changed_cb;
+       rev.diffopt.format_callback_data = s;
+       run_diff_files(&rev, 0);
+}
+
+static void wt_status_print_untracked(const struct wt_status *s)
+{
+       struct dir_struct dir;
+       const char *x;
+       int i;
+       int shown_header = 0;
+
+       memset(&dir, 0, sizeof(dir));
+
+       dir.exclude_per_dir = ".gitignore";
+       if (!s->untracked) {
+               dir.show_other_directories = 1;
+               dir.hide_empty_directories = 1;
+       }
+       x = git_path("info/exclude");
+       if (file_exists(x))
+               add_excludes_from_file(&dir, x);
+
+       read_directory(&dir, ".", "", 0);
+       for(i = 0; i < dir.nr; i++) {
+               /* check for matching entry, which is unmerged; lifted from
+                * builtin-ls-files:show_other_files */
+               struct dir_entry *ent = dir.entries[i];
+               int pos = cache_name_pos(ent->name, ent->len);
+               struct cache_entry *ce;
+               if (0 <= pos)
+                       die("bug in wt_status_print_untracked");
+               pos = -pos - 1;
+               if (pos < active_nr) {
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) == ent->len &&
+                           !memcmp(ce->name, ent->name, ent->len))
+                               continue;
+               }
+               if (!shown_header) {
+                       wt_status_print_header("Untracked files",
+                               "use \"git add\" to add to commit");
+                       shown_header = 1;
+               }
+               color_printf(color(WT_STATUS_HEADER), "#\t");
+               color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
+                               ent->len, ent->name);
+       }
+}
+
+static void wt_status_print_verbose(struct wt_status *s)
+{
+       struct rev_info rev;
+       const char *argv[] = { NULL, NULL, NULL };
+       argv[1] = s->reference;
+       init_revisions(&rev, NULL);
+       setup_revisions(2, argv, &rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+       rev.diffopt.detect_rename = 1;
+       run_diff_index(&rev, 1);
+}
+
+void wt_status_print(struct wt_status *s)
+{
+       if (s->branch && strcmp(s->branch, "refs/heads/master"))
+               color_printf_ln(color(WT_STATUS_HEADER),
+                       "# On branch %s", s->branch);
+
+       if (s->is_initial) {
+               color_printf_ln(color(WT_STATUS_HEADER), "#");
+               color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
+               color_printf_ln(color(WT_STATUS_HEADER), "#");
+               wt_status_print_initial(s);
+       }
+       else {
+               wt_status_print_updated(s);
+               discard_cache();
+       }
+
+       wt_status_print_changed(s);
+       wt_status_print_untracked(s);
+
+       if (s->verbose && !s->is_initial)
+               wt_status_print_verbose(s);
+       if (!s->commitable)
+               printf("%s\n", s->amend ? "# No changes" : "nothing to commit");
+}
+
+int git_status_config(const char *k, const char *v)
+{
+       if (!strcmp(k, "status.color")) {
+               wt_status_use_color = git_config_colorbool(k, v);
+               return 0;
+       }
+       if (!strncmp(k, "status.color.", 13)) {
+               int slot = parse_status_slot(k, 13);
+               color_parse(v, k, wt_status_colors[slot]);
+       }
+       return git_default_config(k, v);
+}
diff --git a/wt-status.h b/wt-status.h
new file mode 100644 (file)
index 0000000..0a5a5b7
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef STATUS_H
+#define STATUS_H
+
+enum color_wt_status {
+       WT_STATUS_HEADER,
+       WT_STATUS_UPDATED,
+       WT_STATUS_CHANGED,
+       WT_STATUS_UNTRACKED,
+};
+
+struct wt_status {
+       int is_initial;
+       char *branch;
+       const char *reference;
+       int commitable;
+       int verbose;
+       int amend;
+       int untracked;
+};
+
+int git_status_config(const char *var, const char *value);
+void wt_status_prepare(struct wt_status *s);
+void wt_status_print(struct wt_status *s);
+
+#endif /* STATUS_H */