Merge branch 'fc/transport-helper-fixes'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:49:32 +0000 (13:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Mar 2014 20:49:33 +0000 (13:49 -0700)
Updates transport-helper, fast-import and fast-export to allow the
ref mapping and ref deletion in a way similar to the natively
supported transports.

* fc/transport-helper-fixes:
remote-bzr: support the new 'force' option
test-hg.sh: tests are now expected to pass
transport-helper.c: do not overwrite forced bit
transport-helper: check for 'forced update' message
transport-helper: add 'force' to 'export' helpers
transport-helper: don't update refs in dry-run
transport-helper: mismerge fix

1  2 
Documentation/gitremote-helpers.txt
contrib/remote-helpers/git-remote-bzr
contrib/remote-helpers/test-bzr.sh
contrib/remote-helpers/test-hg.sh
transport-helper.c
index c2908db76317c91cf5502065e9c4bf1d97da19bd,e75699ce1f7177e2453d06506ed2d7586f00f6ab..64f7ad26b40085ced91e73fd806c11e90ad2eb9a
@@@ -437,13 -437,10 +437,17 @@@ set by Git if the remote helper has th
  'option check-connectivity' \{'true'|'false'\}::
        Request the helper to check connectivity of a clone.
  
+ 'option force' \{'true'|'false'\}::
+       Request the helper to perform a force update.  Defaults to
+       'false'.
 +'option cloning \{'true'|'false'\}::
 +      Notify the helper this is a clone request (i.e. the current
 +      repository is guaranteed empty).
 +
 +'option update-shallow \{'true'|'false'\}::
 +      Allow to extend .git/shallow if the new refs require it.
 +
  SEE ALSO
  --------
  linkgit:git-remote[1]
index 332aba784b8810120df8e1ddfafeb034a55337a3,f1ba477fb8f75eb646befd80feb9af08b10b8d35..5f4b2e3e160f60955e25f8b8fedf8c122dd5c73c
@@@ -44,8 -44,8 +44,8 @@@ import StringI
  import atexit, shutil, hashlib, urlparse, subprocess
  
  NAME_RE = re.compile('^([^<>]+)')
 -AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
 -EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
 +AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
 +EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
  RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
  
  def die(msg, *args):
@@@ -193,7 -193,8 +193,7 @@@ def fixup_user(user)
      else:
          m = EMAIL_RE.match(user)
          if m:
 -            name = m.group(1)
 -            mail = m.group(2)
 +            mail = m.group(1)
          else:
              m = NAME_RE.match(user)
              if m:
@@@ -684,7 -685,8 +684,8 @@@ def do_export(parser)
                  peer = bzrlib.branch.Branch.open(peers[name],
                                                   possible_transports=transports)
                  try:
-                     peer.bzrdir.push_branch(branch, revision_id=revid)
+                     peer.bzrdir.push_branch(branch, revision_id=revid,
+                                             overwrite=force)
                  except bzrlib.errors.DivergedBranches:
                      print "error %s non-fast forward" % ref
                      continue
@@@ -718,8 -720,32 +719,32 @@@ def do_capabilities(parser)
          print "*import-marks %s" % path
      print "*export-marks %s" % path
  
+     print "option"
      print
  
+ class InvalidOptionValue(Exception):
+     pass
+ def get_bool_option(val):
+     if val == 'true':
+         return True
+     elif val == 'false':
+         return False
+     else:
+         raise InvalidOptionValue()
+ def do_option(parser):
+     global force
+     opt, val = parser[1:3]
+     try:
+         if opt == 'force':
+             force = get_bool_option(val)
+             print 'ok'
+         else:
+             print 'unsupported'
+     except InvalidOptionValue:
+         print "error '%s' is not a valid value for option '%s'" % (val, opt)
  def ref_is_valid(name):
      return not True in [c in name for c in '~^: \\']
  
@@@ -882,17 -908,8 +907,18 @@@ def main(args)
      global is_tmp
      global branches, peers
      global transports
+     global force
  
 +    marks = None
 +    is_tmp = False
 +    gitdir = os.environ.get('GIT_DIR', None)
 +
 +    if len(args) < 3:
 +        die('Not enough arguments.')
 +
 +    if not gitdir:
 +        die('GIT_DIR not set')
 +
      alias = args[1]
      url = args[2]
  
      blob_marks = {}
      parsed_refs = {}
      files_cache = {}
 -    marks = None
      branches = {}
      peers = {}
      transports = []
+     force = False
  
      if alias[5:] == url:
          is_tmp = True
          alias = hashlib.sha1(alias).hexdigest()
 -    else:
 -        is_tmp = False
  
      prefix = 'refs/bzr/%s' % alias
 -    gitdir = os.environ['GIT_DIR']
      dirname = os.path.join(gitdir, 'bzr', alias)
  
      if not is_tmp:
              do_import(parser)
          elif parser.check('export'):
              do_export(parser)
+         elif parser.check('option'):
+             do_option(parser)
          else:
              die('unhandled command: %s' % line)
          sys.stdout.flush()
index 1e53ff9a584a302366dcbd647a2e27db3fd1b9fe,ae26dbb2e9605e939ab4c478537d98949025363d..4f379c2ab499f2ca5556f844cbdcacb98fffa13c
@@@ -5,8 -5,7 +5,8 @@@
  
  test_description='Test remote-bzr'
  
 -. ./test-lib.sh
 +test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
 +. "$TEST_DIRECTORY"/test-lib.sh
  
  if ! test_have_prereq PYTHON
  then
@@@ -66,13 -65,33 +66,33 @@@ test_expect_success 'pushing' 
        test_cmp expected actual
  '
  
+ test_expect_success 'forced pushing' '
+       (
+       cd gitrepo &&
+       echo three-new >content &&
+       git commit -a --amend -m three-new &&
+       git push -f
+       ) &&
+       (
+       cd bzrrepo &&
+       # the forced update overwrites the bzr branch but not the bzr
+       # working directory (it tries to merge instead)
+       bzr revert
+       ) &&
+       echo three-new >expected &&
+       cat bzrrepo/content >actual &&
+       test_cmp expected actual
+ '
  test_expect_success 'roundtrip' '
        (
        cd gitrepo &&
        git pull &&
        git log --format="%s" -1 origin/master >actual
        ) &&
-       echo three >expected &&
+       echo three-new >expected &&
        test_cmp expected actual &&
  
        (cd gitrepo && git push && git pull) &&
@@@ -379,7 -398,7 +399,7 @@@ test_expect_success 'export utf-8 autho
        git add content &&
        git commit -m one &&
        git remote add bzr "bzr::../bzrrepo" &&
 -      git push bzr
 +      git push bzr master
        ) &&
  
        (
index 5d128a5da9a1afd3bf441f58e75d4f02a64509d5,aacd8a9528e351408ad34f731d363d38ee4584d3..a933b1e30c6fe82393f3df1446333386d9795b12
@@@ -8,8 -8,7 +8,8 @@@
  
  test_description='Test remote-hg'
  
 -. ./test-lib.sh
 +test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
 +. "$TEST_DIRECTORY"/test-lib.sh
  
  if ! test_have_prereq PYTHON
  then
@@@ -54,14 -53,14 +54,14 @@@ check_bookmark () 
  }
  
  check_push () {
 -      local expected_ret=$1 ret=0 ref_ret=0 IFS=':'
 +      expected_ret=$1 ret=0 ref_ret=0
  
        shift
        git push origin "$@" 2>error
        ret=$?
        cat error
  
 -      while read branch kind
 +      while IFS=':' read branch kind
        do
                case "$kind" in
                'new')
@@@ -83,7 -82,7 +83,7 @@@
                test $ref_ret -ne 0 && echo "match for '$branch' failed" && break
        done
  
 -      if test $expected_ret -ne $ret -o $ref_ret -ne 0
 +      if test $expected_ret -ne $ret || test $ref_ret -ne 0
        then
                return 1
        fi
@@@ -206,17 -205,16 +206,17 @@@ test_expect_success 'authors' 
  
        >../expected &&
        author_test alpha "" "H G Wells <wells@example.com>" &&
 -      author_test beta "test" "test <unknown>" &&
 -      author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
 -      author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
 -      author_test delta "name<test@example.com>" "name <test@example.com>" &&
 -      author_test epsilon "name <test@example.com" "name <test@example.com>" &&
 -      author_test zeta " test " "test <unknown>" &&
 -      author_test eta "test < test@example.com >" "test <test@example.com>" &&
 -      author_test theta "test >test@example.com>" "test <test@example.com>" &&
 -      author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
 -      author_test kappa "test@example.com" "Unknown <test@example.com>"
 +      author_test beta "beta" "beta <unknown>" &&
 +      author_test gamma "gamma <test@example.com> (comment)" "gamma <test@example.com>" &&
 +      author_test delta "<delta@example.com>" "Unknown <delta@example.com>" &&
 +      author_test epsilon "epsilon<test@example.com>" "epsilon <test@example.com>" &&
 +      author_test zeta "zeta <test@example.com" "zeta <test@example.com>" &&
 +      author_test eta " eta " "eta <unknown>" &&
 +      author_test theta "theta < test@example.com >" "theta <test@example.com>" &&
 +      author_test iota "iota >test@example.com>" "iota <test@example.com>" &&
 +      author_test kappa "kappa < test <at> example <dot> com>" "kappa <unknown>" &&
 +      author_test lambda "lambda@example.com" "Unknown <lambda@example.com>" &&
 +      author_test mu "mu.mu@example.com" "Unknown <mu.mu@example.com>"
        ) &&
  
        git clone "hg::hgrepo" gitrepo &&
@@@ -337,17 -335,6 +337,17 @@@ test_expect_success 'remote cloning' 
        check gitrepo HEAD zero
  '
  
 +test_expect_success 'moving remote clone' '
 +      test_when_finished "rm -rf gitrepo*" &&
 +
 +      (
 +      git clone "hg::hgrepo" gitrepo &&
 +      mv gitrepo gitrepo2 &&
 +      cd gitrepo2 &&
 +      git fetch
 +      )
 +'
 +
  test_expect_success 'remote update bookmark' '
        test_when_finished "rm -rf gitrepo*" &&
  
@@@ -455,74 -442,6 +455,74 @@@ test_expect_success 'remote new bookmar
  # cleanup previous stuff
  rm -rf hgrepo
  
 +test_expect_success 'fetch special filenames' '
 +      test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
 +
 +      LC_ALL=en_US.UTF-8
 +      export LC_ALL
 +
 +      (
 +      hg init hgrepo &&
 +      cd hgrepo &&
 +
 +      echo test >> "æ rø" &&
 +      hg add "æ rø" &&
 +      echo test >> "ø~?" &&
 +      hg add "ø~?" &&
 +      hg commit -m add-utf-8 &&
 +      echo test >> "æ rø" &&
 +      hg commit -m test-utf-8 &&
 +      hg rm "ø~?" &&
 +      hg mv "æ rø" "ø~?" &&
 +      hg commit -m hg-mv-utf-8
 +      ) &&
 +
 +      (
 +      git clone "hg::hgrepo" gitrepo &&
 +      cd gitrepo &&
 +      git -c core.quotepath=false ls-files > ../actual
 +      ) &&
 +      echo "ø~?" > expected &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'push special filenames' '
 +      test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
 +
 +      mkdir -p tmp && cd tmp &&
 +
 +      LC_ALL=en_US.UTF-8
 +      export LC_ALL
 +
 +      (
 +      hg init hgrepo &&
 +      cd hgrepo &&
 +
 +      echo one >> content &&
 +      hg add content &&
 +      hg commit -m one
 +      ) &&
 +
 +      (
 +      git clone "hg::hgrepo" gitrepo &&
 +      cd gitrepo &&
 +
 +      echo test >> "æ rø" &&
 +      git add "æ rø" &&
 +      git commit -m utf-8 &&
 +
 +      git push
 +      ) &&
 +
 +      (cd hgrepo &&
 +      hg update &&
 +      hg manifest > ../actual
 +      ) &&
 +
 +      printf "content\næ rø\n" > expected &&
 +      test_cmp expected actual
 +'
 +
  setup_big_push () {
        (
        hg init hgrepo &&
@@@ -680,7 -599,7 +680,7 @@@ test_expect_success 'remote big push fe
        )
  '
  
- test_expect_failure 'remote big push force' '
+ test_expect_success 'remote big push force' '
        test_when_finished "rm -rf hgrepo gitrepo*" &&
  
        setup_big_push
        check_bookmark hgrepo new_bmark six
  '
  
- test_expect_failure 'remote big push dry-run' '
+ test_expect_success 'remote big push dry-run' '
        test_when_finished "rm -rf hgrepo gitrepo*" &&
  
        setup_big_push
diff --combined transport-helper.c
index ad72fbd53cb7bfc7fc037d98288b0cc831ea949a,705dce7e049af441f706ea56b8c5ca82bf2b5482..86e1679c1e0e1e58ebe5297f4c5f48ca1a1f6418
@@@ -190,7 -190,7 +190,7 @@@ static struct child_process *get_helper
                        data->export = 1;
                else if (!strcmp(capname, "check-connectivity"))
                        data->check_connectivity = 1;
 -              else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
 +              else if (!data->refspecs && starts_with(capname, "refspec ")) {
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
                        data->connect = 1;
                } else if (!strcmp(capname, "signed-tags")) {
                        data->signed_tags = 1;
 -              } else if (!prefixcmp(capname, "export-marks ")) {
 +              } else if (starts_with(capname, "export-marks ")) {
                        struct strbuf arg = STRBUF_INIT;
                        strbuf_addstr(&arg, "--export-marks=");
                        strbuf_addstr(&arg, capname + strlen("export-marks "));
                        data->export_marks = strbuf_detach(&arg, NULL);
 -              } else if (!prefixcmp(capname, "import-marks")) {
 +              } else if (starts_with(capname, "import-marks")) {
                        struct strbuf arg = STRBUF_INIT;
                        strbuf_addstr(&arg, "--import-marks=");
                        strbuf_addstr(&arg, capname + strlen("import-marks "));
                        data->import_marks = strbuf_detach(&arg, NULL);
 -              } else if (!prefixcmp(capname, "no-private-update")) {
 +              } else if (starts_with(capname, "no-private-update")) {
                        data->no_private_update = 1;
                } else if (mandatory) {
                        die("Unknown mandatory capability %s. This remote "
@@@ -269,7 -269,6 +269,7 @@@ static const char *unsupported_options[
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP
        };
 +
  static const char *boolean_options[] = {
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
@@@ -311,7 -310,7 +311,7 @@@ static int set_helper_option(struct tra
  
        if (!strcmp(buf.buf, "ok"))
                ret = 0;
 -      else if (!prefixcmp(buf.buf, "error")) {
 +      else if (starts_with(buf.buf, "error")) {
                ret = -1;
        } else if (!strcmp(buf.buf, "unsupported"))
                ret = 1;
@@@ -360,12 -359,6 +360,12 @@@ static int fetch_with_fetch(struct tran
            data->transport_options.check_self_contained_and_connected)
                set_helper_option(transport, "check-connectivity", "true");
  
 +      if (transport->cloning)
 +              set_helper_option(transport, "cloning", "true");
 +
 +      if (data->transport_options.update_shallow)
 +              set_helper_option(transport, "update-shallow", "true");
 +
        for (i = 0; i < nr_heads; i++) {
                const struct ref *posn = to_fetch[i];
                if (posn->status & REF_STATUS_UPTODATE)
        while (1) {
                recvline(data, &buf);
  
 -              if (!prefixcmp(buf.buf, "lock ")) {
 +              if (starts_with(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
                        if (transport->pack_lockfile)
                                warning("%s also locked %s", data->name, name);
@@@ -650,12 -643,12 +650,12 @@@ static int push_update_ref_status(struc
                                   struct ref *remote_refs)
  {
        char *refname, *msg;
-       int status;
+       int status, forced = 0;
  
 -      if (!prefixcmp(buf->buf, "ok ")) {
 +      if (starts_with(buf->buf, "ok ")) {
                status = REF_STATUS_OK;
                refname = buf->buf + 3;
 -      } else if (!prefixcmp(buf->buf, "error ")) {
 +      } else if (starts_with(buf->buf, "error ")) {
                status = REF_STATUS_REMOTE_REJECT;
                refname = buf->buf + 6;
        } else
                        free(msg);
                        msg = NULL;
                }
+               else if (!strcmp(msg, "forced update")) {
+                       forced = 1;
+                       free(msg);
+                       msg = NULL;
+               }
        }
  
        if (*ref)
        }
  
        (*ref)->status = status;
+       (*ref)->forced_update |= forced;
        (*ref)->remote_status = msg;
        return !(status == REF_STATUS_OK);
  }
  
  static void push_update_refs_status(struct helper_data *data,
-                                   struct ref *remote_refs)
+                                   struct ref *remote_refs,
+                                   int flags)
  {
        struct strbuf buf = STRBUF_INIT;
        struct ref *ref = remote_refs;
                if (push_update_ref_status(&buf, &ref, remote_refs))
                        continue;
  
-               if (!data->refspecs || data->no_private_update)
+               if (flags & TRANSPORT_PUSH_DRY_RUN || !data->refspecs || data->no_private_update)
                        continue;
  
                /* propagate back the update to the remote namespace */
@@@ -839,7 -839,7 +846,7 @@@ static int push_refs_with_push(struct t
        sendline(data, &buf);
        strbuf_release(&buf);
  
-       push_update_refs_status(data, remote_refs);
+       push_update_refs_status(data, remote_refs, flags);
        return 0;
  }
  
@@@ -860,6 -860,11 +867,11 @@@ static int push_refs_with_export(struc
                        die("helper %s does not support dry-run", data->name);
        }
  
+       if (flags & TRANSPORT_PUSH_FORCE) {
+               if (set_helper_option(transport, "force", "true") != 0)
+                       warning("helper %s does not support 'force'", data->name);
+       }
        helper = get_helper(transport);
  
        write_constant(helper->in, "export\n");
                }
                free(private);
  
-               if (ref->deletion)
-                       die("remote-helpers do not support ref deletion");
                if (ref->peer_ref) {
                        if (strcmp(ref->peer_ref->name, ref->name))
                                die("remote-helpers do not support old:new syntax");
  
        if (finish_command(&exporter))
                die("Error while running fast-export");
-       push_update_refs_status(data, remote_refs);
+       push_update_refs_status(data, remote_refs, flags);
        return 0;
  }
  
@@@ -1135,8 -1137,9 +1144,8 @@@ static int udt_do_write(struct unidirec
                return 0;       /* Nothing to write. */
  
        transfer_debug("%s is writable", t->dest_name);
 -      bytes = write(t->dest, t->buf, t->bufuse);
 -      if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
 -              errno != EINTR) {
 +      bytes = xwrite(t->dest, t->buf, t->bufuse);
 +      if (bytes < 0 && errno != EWOULDBLOCK) {
                error("write(%s) failed: %s", t->dest_name, strerror(errno));
                return -1;
        } else if (bytes > 0) {