test-file-watcher is a simple chat program to talk to file
watcher. Typically you would write something like this

  cat >expect <<EOF
  # send "hello". Oh and this is a comment!
  <hello
  # Wait for reply and print to stdout.
  # test-file-watcher does not care about anything after '>'
  >hello
  <index foo bar
  >ok
  EOF
  test-file-watcher . <expect >actual

and test-file-watcher will execute the commands and get responses. If
all go well, "actual" should be the same as "expect". '<' and '>'
denote send and receive packets respectively. '<<' and '>>' can be
used to send and receive a list of NUL-terminated paths.

$GIT_TEST_WATCHER enables a few more commands for testing purposes.
The most important one is 'test-mode' where system inotify is taken
out and inotify events could be injected via test-file-watcher.

There are two debug commands in file-watcher that's not used by the
test suite, but would help debugging: setenv and log. They can be used
to turn on GIT_TRACE_PACKET then any "log" command will show, which
functions as barrier between events file watcher.

GIT_TRACE_WATCHER can also be enabled (dynamically or at startup) to
track inotify events.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclo...@gmail.com>
---
 .gitignore                       |   1 +
 Makefile                         |   1 +
 file-watcher.c                   | 181 ++++++++++++++++++-
 t/t7513-file-watcher.sh (new +x) | 382 +++++++++++++++++++++++++++++++++++++++
 test-file-watcher.c (new)        |  96 ++++++++++
 5 files changed, 657 insertions(+), 4 deletions(-)
 create mode 100755 t/t7513-file-watcher.sh
 create mode 100644 test-file-watcher.c

diff --git a/.gitignore b/.gitignore
index 12c78f0..277f929 100644
--- a/.gitignore
+++ b/.gitignore
@@ -181,6 +181,7 @@
 /test-date
 /test-delta
 /test-dump-cache-tree
+/test-file-watcher
 /test-scrap-cache-tree
 /test-genrandom
 /test-index-version
diff --git a/Makefile b/Makefile
index 1c4d659..f0dc2cc 100644
--- a/Makefile
+++ b/Makefile
@@ -555,6 +555,7 @@ TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
 TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-file-watcher
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-line-buffer
diff --git a/file-watcher.c b/file-watcher.c
index 1e45b25..3ab0a11 100644
--- a/file-watcher.c
+++ b/file-watcher.c
@@ -65,7 +65,8 @@ struct connection {
 static struct connection **conns;
 static struct pollfd *pfd;
 static int conns_alloc, pfd_nr, pfd_alloc;
-static int inotify_fd;
+static int inotify_fd, test_mode;
+static int wd_counter = 1;
 
 /*
  * IN_DONT_FOLLOW does not matter now as we do not monitor
@@ -78,10 +79,19 @@ static struct dir *create_dir(struct dir *parent, const 
char *path,
                              const char *basename)
 {
        struct dir *d;
-       int wd = inotify_add_watch(inotify_fd, path, INOTIFY_MASKS);
+       int wd;
+       if (!test_mode)
+               wd = inotify_add_watch(inotify_fd, path, INOTIFY_MASKS);
+       else {
+               wd = wd_counter++;
+               if (wd > 8)
+                       wd = -1;
+       }
        if (wd < 0)
                return NULL;
 
+       trace_printf_key("GIT_TRACE_WATCHER", "inotify: watch %d %s\n",
+                        wd, path);
        d = xmalloc(sizeof(*d));
        memset(d, 0, sizeof(*d));
        d->wd = wd;
@@ -124,7 +134,9 @@ static void free_dir(struct dir *d, int topdown)
        if (d->repo)
                d->repo->root = NULL;
        wds[d->wd] = NULL;
-       inotify_rm_watch(inotify_fd, d->wd);
+       if (!test_mode)
+               inotify_rm_watch(inotify_fd, d->wd);
+       trace_printf_key("GIT_TRACE_WATCHER", "inotify: unwatch %d\n", d->wd);
        if (topdown) {
                int i;
                for (i = 0; i < d->nr_subdirs; i++)
@@ -265,6 +277,7 @@ static inline void queue_file_changed(struct file *f, 
struct strbuf *sb)
        int len = sb->len;
        strbuf_addf(sb, "%s%s", f->parent->parent ? "/" : "", f->name);
        string_list_append(&f->repo->updated, sb->buf);
+       trace_printf_key("GIT_TRACE_WATCHER", "watcher: changed %s\n", sb->buf);
        f->repo->updated_sorted = 0;
        strbuf_setlen(sb, len);
 }
@@ -324,6 +337,10 @@ static int do_handle_inotify(const struct inotify_event 
*event)
        struct dir *d;
        int pos;
 
+       trace_printf_key("GIT_TRACE_WATCHER", "inotify: event %08x wd %d %s\n",
+                        event->mask, event->wd,
+                        event->len ? event->name : "N/A");
+
        if (event->mask & (IN_Q_OVERFLOW | IN_UNMOUNT)) {
                int i;
                for (i = 0; i < nr_repos; i++)
@@ -385,6 +402,81 @@ static int handle_inotify(int fd)
        return ret;
 }
 
+struct constant {
+       const char *name;
+       int value;
+};
+
+#define CONSTANT(x) { #x, x }
+static const struct constant inotify_masks[] = {
+       CONSTANT(IN_DELETE_SELF),
+       CONSTANT(IN_MOVE_SELF),
+       CONSTANT(IN_ATTRIB),
+       CONSTANT(IN_DELETE),
+       CONSTANT(IN_MODIFY),
+       CONSTANT(IN_MOVED_FROM),
+       CONSTANT(IN_MOVED_TO),
+       CONSTANT(IN_Q_OVERFLOW),
+       CONSTANT(IN_UNMOUNT),
+       { NULL, 0 },
+};
+
+static void inject_inotify(const char *msg)
+{
+       char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
+       struct inotify_event *event = (struct inotify_event *)buf;
+       char *end, *p;
+       int i;
+       memset(event, 0, sizeof(*event));
+       event->wd = strtol(msg, &end, 0);
+       if (*end++ != ' ')
+               die("expect a space after watch descriptor");
+       p = end;
+       end = strchrnul(p, ' ');
+       if (*end)
+               strcpy(event->name, end + 1);
+       while (p < end) {
+               char *sep = strchrnul(p, '|');
+               if (sep > end)
+                       sep = end;
+               *sep = '\0';
+               for (i = 0; inotify_masks[i].name; i++)
+                       if (!strcmp(inotify_masks[i].name, p))
+                               break;
+               if (!inotify_masks[i].name)
+                       die("unrecognize event mask %s", p);
+               event->mask |= inotify_masks[i].value;
+               p = sep + 1;
+       }
+       do_handle_inotify(event);
+}
+
+static void dump_watches(struct dir *d, struct strbuf *sb, struct strbuf *out)
+{
+       int i, len = sb->len;
+       strbuf_addstr(sb, d->name);
+       strbuf_addf(out, "%s %d%c", sb->buf[0] ? sb->buf : ".", d->wd, '\0');
+       if (d->name[0])
+               strbuf_addch(sb, '/');
+       for (i = 0; i < d->nr_subdirs; i++)
+               dump_watches(d->subdirs[i], sb, out);
+       for (i = 0; i < d->nr_files; i++)
+               strbuf_addf(out, "%s%s%c", sb->buf, d->files[i]->name, '\0');
+       strbuf_setlen(sb, len);
+}
+
+static void dump_changes(struct repository *repo, struct strbuf *sb)
+{
+       int i;
+       if (!repo->updated_sorted) {
+               sort_string_list(&repo->updated);
+               repo->updated_sorted = 1;
+       }
+       for (i = 0; i < repo->updated.nr; i++)
+               strbuf_add(sb, repo->updated.items[i].string,
+                          strlen(repo->updated.items[i].string) + 1);
+}
+
 static void get_changed_list(int conn_id)
 {
        struct strbuf sb = STRBUF_INIT;
@@ -483,11 +575,13 @@ static void unchange(int conn_id, unsigned long size)
                        item = string_list_lookup(&repo->updated, p);
                        if (!item)
                                continue;
+                       trace_printf_key("GIT_TRACE_WATCHER", "watcher: 
unchange %s\n", p);
                        unsorted_string_list_delete_item(&repo->updated,
                                                         item - 
repo->updated.items, 0);
                }
                strbuf_release(&sb);
        }
+       trace_printf_key("GIT_TRACE_WATCHER", "watcher: unchange complete\n");
        memcpy(repo->index_signature, conn->new_index, 40);
        /*
         * If other connections on this repo are in some sort of
@@ -540,6 +634,13 @@ static void reset_watches(struct repository *repo)
 
 static void reset_repo(struct repository *repo, ino_t inode)
 {
+       if (test_mode)
+               /*
+                * test-mode is designed for single repo, we can
+                * safely reset wd counter because all wd should be
+                * deleted
+                */
+               wd_counter = 1;
        reset_watches(repo);
        string_list_clear(&repo->updated, 0);
        memcpy(repo->index_signature, invalid_signature, 40);
@@ -560,6 +661,7 @@ static int shutdown_connection(int id)
        return 0;
 }
 
+static void cleanup(void);
 static int handle_command(int conn_id)
 {
        int fd = conns[conn_id]->sock;
@@ -754,6 +856,71 @@ static int handle_command(int conn_id)
                }
                unchange(conn_id, n);
        }
+
+       /*
+        * Testing and debugging support
+        */
+       else if (!strcmp(msg, "test-mode") && getenv("GIT_TEST_WATCHER")) {
+               test_mode = 1;
+               packet_write(fd, "test mode on");
+       }
+       else if (starts_with(msg, "setenv ")) {
+               /* useful for setting GIT_TRACE_WATCHER or GIT_TRACE_PACKET */
+               char *sep = strchr(msg + 7, ' ');
+               if (!sep) {
+                       packet_write(fd, "error invalid setenv line %s", msg);
+                       return shutdown_connection(conn_id);
+               }
+               *sep = '\0';
+               setenv(msg + 7, sep + 1, 1);
+       }
+       else if (starts_with(msg, "log ")) {
+               ; /* do nothing, if GIT_TRACE_PACKET is on, it's already logged 
*/
+       }
+       else if (!strcmp(msg, "die") && getenv("GIT_TEST_WATCHER")) {
+               /*
+                * The client will wait for "see you" before it may
+                * run another daemon with the same path. So there's
+                * no racing on unlink() and listen() on the same
+                * socket path.
+                */
+               cleanup();
+               packet_write(fd, "see you");
+               close(fd);
+               exit(0);
+       }
+       else if (starts_with(msg, "dump ") && getenv("GIT_TEST_WATCHER")) {
+               struct strbuf sb = STRBUF_INIT;
+               struct strbuf out = STRBUF_INIT;
+               const char *reply = NULL;
+               if (!strcmp(msg + 5, "watches")) {
+                       if (conns[conn_id]->repo) {
+                               if (conns[conn_id]->repo->root)
+                                       
dump_watches(conns[conn_id]->repo->root, &sb, &out);
+                       } else {
+                               int i;
+                               for (i = 0; i < nr_repos; i++) {
+                                       strbuf_addf(&out, "%s%c", 
repos[i]->work_tree, '\0');
+                                       if (repos[i]->root)
+                                               dump_watches(repos[i]->root, 
&sb, &out);
+                                       strbuf_reset(&out);
+                                       strbuf_reset(&sb);
+                               }
+                       }
+                       reply = "watching";
+               } else if (!strcmp(msg + 5, "changes")) {
+                       dump_changes(conns[conn_id]->repo, &out);
+                       reply = "changed";
+               }
+               packet_write(fd, "%s %d", reply, (int)out.len);
+               if (out.len)
+                       write_in_full(fd, out.buf, out.len);
+               strbuf_release(&out);
+               strbuf_release(&sb);
+       }
+       else if (starts_with(msg, "inotify ") && getenv("GIT_TEST_WATCHER")) {
+               inject_inotify(msg + 8);
+       }
        else {
                packet_write(fd, "error unrecognized command %s", msg);
                return shutdown_connection(conn_id);
@@ -848,11 +1015,13 @@ int main(int argc, const char **argv)
 {
        struct strbuf sb = STRBUF_INIT;
        int i, new_nr, fd, quit = 0, nr_common;
-       int daemon = 0;
+       int daemon = 0, check_support = 0;
        time_t last_checked;
        struct option options[] = {
                OPT_BOOL(0, "detach", &daemon,
                         N_("run in background")),
+               OPT_BOOL(0, "check-support", &check_support,
+                        N_("return zero file watcher is available")),
                OPT_END()
        };
 
@@ -865,6 +1034,10 @@ int main(int argc, const char **argv)
 
        argc = parse_options(argc, argv, NULL, options,
                             file_watcher_usage, 0);
+
+       if (check_support)
+               return 0;
+
        if (argc < 1)
                die(_("socket path missing"));
        else if (argc > 1)
diff --git a/t/t7513-file-watcher.sh b/t/t7513-file-watcher.sh
new file mode 100755
index 0000000..bf64fc4
--- /dev/null
+++ b/t/t7513-file-watcher.sh
@@ -0,0 +1,382 @@
+#!/bin/sh
+
+test_description='File watcher daemon tests'
+
+. ./test-lib.sh
+
+if git file-watcher --check-support && test_have_prereq POSIXPERM; then
+       :                               # good
+else
+       skip_all="file-watcher not supported on this system"
+       test_done
+fi
+
+kill_it() {
+       test-file-watcher "$1" <<EOF >/dev/null
+<die
+>see you
+EOF
+}
+
+GIT_TEST_WATCHER=1
+export GIT_TEST_WATCHER
+
+test_expect_success 'test-file-watcher can kill the daemon' '
+       chmod 700 . &&
+       git file-watcher --detach . &&
+       cat >expect <<EOF &&
+<die
+>see you
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       ! test -S socket
+'
+
+test_expect_success 'exchange hello' '
+       git file-watcher --detach . &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<die
+>see you
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'normal index sequence' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>inconsistent
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       cat >expect2 <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+# inconsistent again because new-index has not been issued yet
+>inconsistent
+<new-index $SIG
+<<unchange
+<<
+EOF
+       test-file-watcher . >actual2 <expect2 &&
+       test_cmp expect2 actual2 &&
+       cat >expect3 <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>ok
+<die
+>see you
+EOF
+       test-file-watcher . >actual3 <expect3 &&
+       test_cmp expect3 actual3
+'
+
+test_expect_success 'unaccepted index: hello not sent' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<index $SIG $TRASH_DIRECTORY
+>error why did you not greet me? go away
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       kill_it .
+'
+
+test_expect_success 'unaccepted index: signature too short' '
+       git file-watcher --detach . &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index 1234 $TRASH_DIRECTORY
+>error invalid index line index 1234 $TRASH_DIRECTORY
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       kill_it .
+'
+
+test_expect_success 'unaccepted index: worktree unavailable' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY/non-existent
+>error work tree does not exist: No such file or directory
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       kill_it .
+'
+
+test_expect_success 'watch foo and abc/bar' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>inconsistent
+<test-mode
+>test mode on
+<<watch
+<<foo
+<<abc/bar
+<<
+>watched 2
+<dump watches
+>>watching
+>>. 1
+>>abc 2
+>>abc/bar
+>>foo
+<new-index $SIG
+<<unchange
+<<
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'modify abc/bar' '
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>ok
+<inotify 2 IN_MODIFY bar
+<dump watches
+>>watching
+>>. 1
+>>foo
+<dump changes
+>>changed
+>>abc/bar
+<die
+>see you
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'delete abc makes abc/bar changed' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>inconsistent
+<test-mode
+>test mode on
+<<watch
+<<foo/abc/bar
+<<
+>watched 1
+<dump watches
+>>watching
+>>. 1
+>>foo 2
+>>foo/abc 3
+>>foo/abc/bar
+<inotify 2 IN_DELETE_SELF
+<dump watches
+>>watching
+<dump changes
+>>changed
+>>foo/abc/bar
+<new-index $SIG
+<<unchange
+<<
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'get changed list' '
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>ok
+<get-changed
+>>changed
+>>foo/abc/bar
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'incomplete new-index request' '
+       SIG=0123456789012345678901234567890123456789 &&
+       SIG2=9123456789012345678901234567890123456780 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>ok
+<new-index $SIG2
+<dump changes
+>>changed
+>>foo/abc/bar
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'delete abc/bar from changed list' '
+       SIG=0123456789012345678901234567890123456789 &&
+       SIG2=9123456789012345678901234567890123456780 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>ok
+<new-index $SIG2
+<<unchange
+<<foo/abc/bar
+<<
+<dump changes
+>>changed
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'file-watcher index updated after new-index' '
+       SIG2=9123456789012345678901234567890123456780 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG2 $TRASH_DIRECTORY
+>ok
+<die
+>see you
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+# When test-mode is on, file-watch only accepts 8 directories
+test_expect_success 'watch too many directories' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>inconsistent
+# Do not call inotify_add_watch()
+<test-mode
+>test mode on
+# First batch should be all ok
+<<watch
+<<dir1/foo
+<<dir2/foo
+<<dir3/foo
+<<dir4/foo
+<<
+>watched 4
+# Second batch hits the limit
+<<watch
+<<dir5/foo
+<<dir6/foo
+<<dir7/foo
+<<dir8/foo
+<<dir9/foo
+<<
+>watched 3
+# The third batch is already registered, should accept too
+<<watch
+<<dir5/foo
+<<dir6/foo
+<<dir7/foo
+<<
+>watched 3
+# Try again, see if it still rejects
+<<watch
+<<dir8/foo
+<<dir9/foo
+<<
+>watched 0
+<dump watches
+>>watching
+>>. 1
+>>dir1 2
+>>dir1/foo
+>>dir2 3
+>>dir2/foo
+>>dir3 4
+>>dir3/foo
+>>dir4 5
+>>dir4/foo
+>>dir5 6
+>>dir5/foo
+>>dir6 7
+>>dir6/foo
+>>dir7 8
+>>dir7/foo
+<die
+>see you
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'event overflow' '
+       git file-watcher --detach . &&
+       SIG=0123456789012345678901234567890123456789 &&
+       cat >expect <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+>inconsistent
+<test-mode
+>test mode on
+<<watch
+<<foo
+<<abc/bar
+<<
+>watched 2
+<inotify 2 IN_MODIFY bar
+<dump watches
+>>watching
+>>. 1
+>>foo
+<dump changes
+>>changed
+>>abc/bar
+<inotify -1 IN_Q_OVERFLOW
+<dump watches
+>>watching
+<dump changes
+>>changed
+EOF
+       test-file-watcher . >actual <expect &&
+       test_cmp expect actual &&
+       cat >expect2 <<EOF &&
+<hello
+>hello
+<index $SIG $TRASH_DIRECTORY
+# Must be inconsistent because of IN_Q_OVERFLOW
+>inconsistent
+<die
+>see you
+EOF
+       test-file-watcher . >actual2 <expect2 &&
+       test_cmp expect2 actual2
+'
+
+test_done
diff --git a/test-file-watcher.c b/test-file-watcher.c
new file mode 100644
index 0000000..ffff198
--- /dev/null
+++ b/test-file-watcher.c
@@ -0,0 +1,96 @@
+#include "cache.h"
+#include "unix-socket.h"
+#include "pkt-line.h"
+#include "strbuf.h"
+
+int main(int ac, char **av)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct strbuf packed = STRBUF_INIT;
+       char *packing = NULL;
+       int last_command_is_reply = 0;
+       int fd;
+
+       strbuf_addf(&sb, "%s/socket", av[1]);
+       fd = unix_stream_connect(sb.buf);
+       if (fd < 0)
+               die_errno("connect");
+       strbuf_reset(&sb);
+
+       /*
+        * test-file-watcher crashes sometimes, make sure to flush
+        */
+       setbuf(stdout, NULL);
+
+       while (!strbuf_getline(&sb, stdin, '\n')) {
+               if (sb.buf[0] == '#') {
+                       puts(sb.buf);
+                       continue;
+               }
+               if (sb.buf[0] == '>') {
+                       if (last_command_is_reply)
+                               continue;
+                       last_command_is_reply = 1;
+               } else
+                       last_command_is_reply = 0;
+
+               if (sb.buf[0] == '<' && sb.buf[1] == '<') {
+                       puts(sb.buf);
+                       if (!packing) {
+                               packing = xstrdup(sb.buf + 2);
+                               strbuf_reset(&packed);
+                               continue;
+                       }
+                       if (!sb.buf[2]) {
+                               packet_write(fd, "%s %d", packing, 
(int)packed.len);
+                               if (packed.len)
+                                       write_in_full(fd, packed.buf, 
packed.len);
+                               free(packing);
+                               packing = NULL;
+                       } else
+                               strbuf_add(&packed, sb.buf + 2, sb.len - 2 + 1);
+                       continue;
+               }
+               if (sb.buf[0] == '<') {
+                       packet_write(fd, "%s", sb.buf + 1);
+                       puts(sb.buf);
+                       continue;
+               }
+               if (sb.buf[0] == '>' && sb.buf[1] == '>') {
+                       int len;
+                       char *p, *reply = packet_read_line(fd, &len);
+                       if (!starts_with(reply, sb.buf + 2) ||
+                           reply[sb.len - 2] != ' ') {
+                               printf(">%s\n", reply);
+                               continue;
+                       } else {
+                               p = reply + sb.len - 2;
+                               printf(">>%.*s\n", (int)(p - reply), reply);
+                               len = atoi(p + 1);
+                               if (!len)
+                                       continue;
+                       }
+                       strbuf_reset(&packed);
+                       strbuf_grow(&packed, len);
+                       if (read_in_full(fd, packed.buf, len) <= 0)
+                               return 1;
+                       strbuf_setlen(&packed, len);
+                       for (p = packed.buf; p - packed.buf < packed.len; p += 
len + 1) {
+                               len = strlen(p);
+                               printf(">>%s\n", p);
+                       }
+                       continue;
+               }
+               if (sb.buf[0] == '>') {
+                       int len;
+                       char *reply = packet_read_line(fd, &len);
+                       if (!reply)
+                               puts(">");
+                       else
+                               printf(">%s\n", reply);
+                       continue;
+               }
+               die("unrecognize command %s", sb.buf);
+       }
+       return 0;
+}
-- 
1.8.5.2.240.g8478abd

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to