Merge branch 'fc/transport-helper-error-reporting'
authorJunio C Hamano <gitster@pobox.com>
Wed, 29 May 2013 21:20:16 +0000 (14:20 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 29 May 2013 21:20:16 +0000 (14:20 -0700)
Update transport helper to report errors and maintain ref hierarchy
used to keep track of remote helper state better.

* fc/transport-helper-error-reporting:
transport-helper: fix remote helper namespace regression
test: remote-helper: add missing and
t5801: "VAR=VAL shell_func args" is forbidden
transport-helper: update remote helper namespace
transport-helper: trivial code shuffle
transport-helper: warn when refspec is not used
transport-helper: clarify pushing without refspecs
transport-helper: update refspec documentation
transport-helper: clarify *:* refspec
transport-helper: improve push messages
transport-helper: mention helper name when it dies
transport-helper: report errors properly

1  2 
Documentation/gitremote-helpers.txt
git-remote-testgit
t/t5801-remote-helpers.sh
transport-helper.c
index da746419b355a4b5d49b23ed51e3d4aa89d8c2c1,4d26e37d368601a6bc5e4f00ee00d0d9de8485dd..0827f691396ba698d3976eba1eddedd9e7ff3e9e
@@@ -159,11 -159,11 +159,11 @@@ Miscellaneous capabilitie
        carried out.
  
  'refspec' <refspec>::
-       This modifies the 'import' capability, allowing the produced
-       fast-import stream to modify refs in a private namespace
-       instead of writing to refs/heads or refs/remotes directly.
+       For remote helpers that implement 'import' or 'export', this capability
+       allows the refs to be constrained to a private namespace, instead of
+       writing to refs/heads or refs/remotes directly.
        It is recommended that all importers providing the 'import'
-       capability use this.
+       capability use this. It's mandatory for 'export'.
  +
  A helper advertising the capability
  `refspec refs/heads/*:refs/svn/origin/branches/*`
@@@ -174,8 -174,8 +174,8 @@@ ref
  This capability can be advertised multiple times.  The first
  applicable refspec takes precedence.  The left-hand of refspecs
  advertised with this capability must cover all refs reported by
- the list command.  If a helper does not need a specific 'refspec'
capability then it should advertise `refspec *:*`.
+ the list command.  If no 'refspec' capability is advertised,
there is an implied `refspec *:*`.
  
  'bidi-import'::
        This modifies the 'import' capability.
        marks specified in <file> before processing any input. For details,
        read up on '--import-marks=<file>' in linkgit:git-fast-export[1].
  
 +'signed-tags'::
 +      This modifies the 'export' capability, instructing Git to pass
 +      '--signed-tags=verbatim' to linkgit:git-fast-export[1].  In the
 +      absence of this capability, Git will use '--signed-tags=warn-strip'.
  
  
  
diff --combined git-remote-testgit
index e7ed3a33e6d7180e65f06a882342fa2d7efb4d03,ff36d1d39d1c9bd1fce1843ba6a6eee7f023f131..2b865deeb161db0a88814afcd03a61aca88d8c89
@@@ -38,7 -38,6 +38,7 @@@ d
                        echo "*import-marks $gitmarks"
                        echo "*export-marks $gitmarks"
                fi
 +              test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
                echo
                ;;
        list)
                        echo "feature import-marks=$gitmarks"
                        echo "feature export-marks=$gitmarks"
                fi
+               if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+               then
+                       echo "feature done"
+                       exit 1
+               fi
                echo "feature done"
                git fast-export "${testgitmarks_args[@]}" $refs |
                sed -e "s#refs/heads/#${prefix}/heads/#g"
                echo "done"
                ;;
        export)
+               if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+               then
+                       # consume input so fast-export doesn't get SIGPIPE;
+                       # git would also notice that case, but we want
+                       # to make sure we are exercising the later
+                       # error checks
+                       while read line; do
+                               test "done" = "$line" && break
+                       done
+                       exit 1
+               fi
                before=$(git for-each-ref --format='%(refname) %(objectname)')
  
                git fast-import "${testgitmarks_args[@]}" --quiet
                while read ref a b
                do
                        test $a == $b && continue
-                       echo "ok $ref"
+                       if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR"
+                       then
+                               echo "ok $ref"
+                       else
+                               echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR"
+                       fi
                done
  
                echo
index dbb02e2afd16b2ca53f18107f5f07af37f217c9b,443e228ec5d34d293e1fdcf60ee4074c57e04549..16faa23154a7711ac7408d4a6df64e1623c28bcb
@@@ -6,7 -6,6 +6,7 @@@
  test_description='Test remote-helper import and export commands'
  
  . ./test-lib.sh
 +. "$TEST_DIRECTORY"/lib-gpg.sh
  
  if ! type "${BASH-bash}" >/dev/null 2>&1; then
        skip_all='skipping remote-testgit tests, bash not available'
@@@ -101,39 -100,28 +101,28 @@@ test_expect_failure 'push new branch wi
  
  test_expect_success 'cloning without refspec' '
        GIT_REMOTE_TESTGIT_REFSPEC="" \
-       git clone "testgit::${PWD}/server" local2 &&
+       git clone "testgit::${PWD}/server" local2 2>error &&
+       grep "This remote helper should implement refspec capability" error &&
        compare_refs local2 HEAD server HEAD
  '
  
  test_expect_success 'pulling without refspecs' '
        (cd local2 &&
        git reset --hard &&
-       GIT_REMOTE_TESTGIT_REFSPEC="" git pull) &&
+       GIT_REMOTE_TESTGIT_REFSPEC="" git pull 2>../error) &&
+       grep "This remote helper should implement refspec capability" error &&
        compare_refs local2 HEAD server HEAD
  '
  
- test_expect_failure 'pushing without refspecs' '
+ test_expect_success 'pushing without refspecs' '
        test_when_finished "(cd local2 && git reset --hard origin)" &&
        (cd local2 &&
        echo content >>file &&
        git commit -a -m ten &&
-       GIT_REMOTE_TESTGIT_REFSPEC="" git push) &&
-       compare_refs local2 HEAD server HEAD
- '
- test_expect_success 'pulling with straight refspec' '
-       (cd local2 &&
-       GIT_REMOTE_TESTGIT_REFSPEC="*:*" git pull) &&
-       compare_refs local2 HEAD server HEAD
- '
- test_expect_failure 'pushing with straight refspec' '
-       test_when_finished "(cd local2 && git reset --hard origin)" &&
-       (cd local2 &&
-       echo content >>file &&
-       git commit -a -m eleven &&
-       GIT_REMOTE_TESTGIT_REFSPEC="*:*" git push) &&
-       compare_refs local2 HEAD server HEAD
+       GIT_REMOTE_TESTGIT_REFSPEC="" &&
+       export GIT_REMOTE_TESTGIT_REFSPEC &&
+       test_must_fail git push 2>../error) &&
+       grep "remote-helper doesn.t support push; refspec needed" error
  '
  
  test_expect_success 'pulling without marks' '
@@@ -167,25 -155,52 +156,71 @@@ test_expect_success 'push ref with exis
        compare_refs local dup server dup
  '
  
 +test_expect_success GPG 'push signed tag' '
 +      (cd local &&
 +      git checkout master &&
 +      git tag -s -m signed-tag signed-tag &&
 +      git push origin signed-tag
 +      ) &&
 +      compare_refs local signed-tag^{} server signed-tag^{} &&
 +      test_must_fail compare_refs local signed-tag server signed-tag
 +'
 +
 +test_expect_success GPG 'push signed tag with signed-tags capability' '
 +      (cd local &&
 +      git checkout master &&
 +      git tag -s -m signed-tag signed-tag-2 &&
 +      GIT_REMOTE_TESTGIT_SIGNED_TAGS=1 git push origin signed-tag-2
 +      ) &&
 +      compare_refs local signed-tag-2 server signed-tag-2
 +'
 +
+ test_expect_success 'push update refs' '
+       (cd local &&
+       git checkout -b update master &&
+       echo update >>file &&
+       git commit -a -m update &&
+       git push origin update &&
+       git rev-parse --verify remotes/origin/update >expect &&
+       git rev-parse --verify testgit/origin/heads/update >actual &&
+       test_cmp expect actual
+       )
+ '
+ test_expect_success 'push update refs failure' '
+       (cd local &&
+       git checkout update &&
+       echo "update fail" >>file &&
+       git commit -a -m "update fail" &&
+       git rev-parse --verify testgit/origin/heads/update >expect &&
+       GIT_REMOTE_TESTGIT_PUSH_ERROR="non-fast forward" &&
+       export GIT_REMOTE_TESTGIT_PUSH_ERROR &&
+       test_expect_code 1 git push origin update &&
+       git rev-parse --verify testgit/origin/heads/update >actual &&
+       test_cmp expect actual
+       )
+ '
+ test_expect_success 'proper failure checks for fetching' '
+       (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+       export GIT_REMOTE_TESTGIT_FAILURE &&
+       cd local &&
+       test_must_fail git fetch 2> error &&
+       cat error &&
+       grep -q "Error while running fast-import" error
+       )
+ '
+ test_expect_success 'proper failure checks for pushing' '
+       (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+       export GIT_REMOTE_TESTGIT_FAILURE &&
+       cd local &&
+       test_must_fail git push --all 2> error &&
+       cat error &&
+       grep -q "Reading from helper .git-remote-testgit. failed" error
+       )
+ '
  test_expect_success 'push messages' '
        (cd local &&
        git checkout -b new_branch master &&
diff --combined transport-helper.c
index 522d79178e2cc4909a5df21fe483946fc3d1ad0e,6cd0be90e0a4e6d377004d8dbf37ae9f254640f4..2f5ac3fbeefd65c61c9e9c89f97896f9dfac89c5
@@@ -11,6 -11,7 +11,7 @@@
  #include "thread-utils.h"
  #include "sigchain.h"
  #include "argv-array.h"
+ #include "refs.h"
  
  static int debug;
  
@@@ -25,7 -26,6 +26,7 @@@ struct helper_data 
                option : 1,
                push : 1,
                connect : 1,
 +              signed_tags : 1,
                no_disconnect_req : 1;
        char *export_marks;
        char *import_marks;
@@@ -47,7 -47,7 +48,7 @@@ static void sendline(struct helper_dat
                die_errno("Full write to remote helper failed");
  }
  
- static int recvline_fh(FILE *helper, struct strbuf *buffer)
+ static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
  {
        strbuf_reset(buffer);
        if (debug)
@@@ -55,7 -55,7 +56,7 @@@
        if (strbuf_getline(buffer, helper, '\n') == EOF) {
                if (debug)
                        fprintf(stderr, "Debug: Remote helper quit.\n");
-               exit(128);
+               die("Reading from helper 'git-remote-%s' failed", name);
        }
  
        if (debug)
@@@ -65,7 -65,7 +66,7 @@@
  
  static int recvline(struct helper_data *helper, struct strbuf *buffer)
  {
-       return recvline_fh(helper->out, buffer);
+       return recvline_fh(helper->out, buffer, helper->name);
  }
  
  static void xchgline(struct helper_data *helper, struct strbuf *buffer)
@@@ -192,8 -192,6 +193,8 @@@ static struct child_process *get_helper
                        refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
                } else if (!strcmp(capname, "connect")) {
                        data->connect = 1;
 +              } else if (!strcmp(capname, "signed-tags")) {
 +                      data->signed_tags = 1;
                } else if (!prefixcmp(capname, "export-marks ")) {
                        struct strbuf arg = STRBUF_INIT;
                        strbuf_addstr(&arg, "--export-marks=");
                int i;
                data->refspec_nr = refspec_nr;
                data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
 -              for (i = 0; i < refspec_nr; i++) {
 +              for (i = 0; i < refspec_nr; i++)
                        free((char *)refspecs[i]);
 -              }
                free(refspecs);
+       } else if (data->import || data->bidi_import || data->export) {
+               warning("This remote helper should implement refspec capability.");
        }
        strbuf_release(&buf);
        if (debug)
@@@ -412,11 -413,9 +415,11 @@@ static int get_exporter(struct transpor
        /* we need to duplicate helper->in because we want to use it after
         * fastexport is done with it. */
        fastexport->out = dup(helper->in);
 -      fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
 +      fastexport->argv = xcalloc(6 + revlist_args->nr, sizeof(*fastexport->argv));
        fastexport->argv[argc++] = "fast-export";
        fastexport->argv[argc++] = "--use-done-feature";
 +      fastexport->argv[argc++] = data->signed_tags ?
 +              "--signed-tags=verbatim" : "--signed-tags=warn-strip";
        if (data->export_marks)
                fastexport->argv[argc++] = data->export_marks;
        if (data->import_marks)
@@@ -473,7 -472,7 +476,7 @@@ static int fetch_with_import(struct tra
         * were fetching.
         *
         * (If no "refspec" capability was specified, for historical
-        * reasons we default to *:*.)
+        * reasons we default to the equivalent of *:*.)
         *
         * Store the result in to_fetch[i].old_sha1.  Callers such
         * as "git fetch" can use the value to write feedback to the
@@@ -540,7 -539,7 +543,7 @@@ static int process_connect_service(stru
                goto exit;
  
        sendline(data, &cmdbuf);
-       recvline_fh(input, &cmdbuf);
+       recvline_fh(input, &cmdbuf, name);
        if (!strcmp(cmdbuf.buf, "")) {
                data->no_disconnect_req = 1;
                if (debug)
@@@ -622,7 -621,7 +625,7 @@@ static int fetch(struct transport *tran
        return -1;
  }
  
- static void push_update_ref_status(struct strbuf *buf,
+ static int push_update_ref_status(struct strbuf *buf,
                                   struct ref **ref,
                                   struct ref *remote_refs)
  {
                *ref = find_ref_by_name(remote_refs, refname);
        if (!*ref) {
                warning("helper reported unexpected status of %s", refname);
-               return;
+               return 1;
        }
  
        if ((*ref)->status != REF_STATUS_NONE) {
                 * status reported by the remote helper if the latter is 'no match'.
                 */
                if (status == REF_STATUS_NONE)
-                       return;
+                       return 1;
        }
  
        (*ref)->status = status;
        (*ref)->remote_status = msg;
+       return !(status == REF_STATUS_OK);
  }
  
  static void push_update_refs_status(struct helper_data *data,
        struct strbuf buf = STRBUF_INIT;
        struct ref *ref = remote_refs;
        for (;;) {
+               char *private;
                recvline(data, &buf);
                if (!buf.len)
                        break;
  
-               push_update_ref_status(&buf, &ref, remote_refs);
+               if (push_update_ref_status(&buf, &ref, remote_refs))
+                       continue;
+               if (!data->refspecs)
+                       continue;
+               /* propagate back the update to the remote namespace */
+               private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+               if (!private)
+                       continue;
+               update_ref("update by helper", private, ref->new_sha1, NULL, 0, 0);
+               free(private);
        }
        strbuf_release(&buf);
  }
@@@ -789,6 -802,9 +806,9 @@@ static int push_refs_with_export(struc
        struct string_list revlist_args = STRING_LIST_INIT_NODUP;
        struct strbuf buf = STRBUF_INIT;
  
+       if (!data->refspecs)
+               die("remote-helper doesn't support push; refspec needed");
        helper = get_helper(transport);
  
        write_constant(helper->in, "export\n");
                char *private;
                unsigned char sha1[20];
  
-               if (!data->refspecs)
-                       continue;
+               if (ref->deletion)
+                       die("remote-helpers do not support ref deletion");
                private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
                if (private && !get_sha1(private, sha1)) {
                        strbuf_addf(&buf, "^%s", private);
                }
                free(private);
  
-               if (ref->deletion) {
-                       die("remote-helpers do not support ref deletion");
-               }
                if (ref->peer_ref)
                        string_list_append(&revlist_args, ref->peer_ref->name);
        }
  
        if (get_exporter(transport, &exporter, &revlist_args))
@@@ -996,7 -1008,7 +1012,7 @@@ struct unidirectional_transfer 
        int src_is_sock;
        /* Is destination socket? */
        int dest_is_sock;
 -      /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */
 +      /* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */
        int state;
        /* Buffer. */
        char buf[BUFFERSIZE];