Merge branch 'jc/update-instead-into-void'
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Apr 2015 18:49:10 +0000 (11:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 14 Apr 2015 18:49:10 +0000 (11:49 -0700)
A push into an unborn branch, with "receive.denyCurrentBranch" set
to "updateInstead", did not check out the working tree as expected.

* jc/update-instead-into-void:
push-to-deploy: allow pushing into an unborn branch and updating it

1  2 
builtin/receive-pack.c
t/t5516-fetch-push.sh
diff --combined builtin/receive-pack.c
index 70e9ce5f96f3e8da78e4b5746580869f9ae65330,0c0a2616ada04e474a879cd28f5b0bd5b876c8ca..5292bb5a506805778c6b9c164523c80a44da3edd
@@@ -38,11 -38,9 +38,11 @@@ static int receive_fsck_objects = -1
  static int transfer_fsck_objects = -1;
  static int receive_unpack_limit = -1;
  static int transfer_unpack_limit = -1;
 +static int advertise_atomic_push = 1;
  static int unpack_limit = 100;
  static int report_status;
  static int use_sideband;
 +static int use_atomic;
  static int quiet;
  static int prefer_ofs_delta = 1;
  static int auto_update_server_info;
@@@ -69,7 -67,6 +69,7 @@@ static const char *NONCE_SLOP = "SLOP"
  static const char *nonce_status;
  static long nonce_stamp_slop;
  static unsigned long nonce_stamp_slop_limit;
 +static struct ref_transaction *transaction;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -163,11 -160,6 +163,11 @@@ static int receive_pack_config(const ch
                return 0;
        }
  
 +      if (strcmp(var, "receive.advertiseatomic") == 0) {
 +              advertise_atomic_push = git_config_bool(var, value);
 +              return 0;
 +      }
 +
        return git_default_config(var, value, cb);
  }
  
@@@ -183,8 -175,6 +183,8 @@@ static void show_ref(const char *path, 
  
                strbuf_addstr(&cap,
                              "report-status delete-refs side-band-64k quiet");
 +              if (advertise_atomic_push)
 +                      strbuf_addstr(&cap, " atomic");
                if (prefer_ofs_delta)
                        strbuf_addstr(&cap, " ofs-delta");
                if (push_cert_nonce)
@@@ -444,7 -434,7 +444,7 @@@ static const char *check_nonce(const ch
        nonce_stamp_slop = (long)ostamp - (long)stamp;
  
        if (nonce_stamp_slop_limit &&
 -          abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
 +          labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
                /*
                 * Pretend as if the received nonce (which passes the
                 * HMAC check, so it is not a forged by third-party)
@@@ -743,6 -733,22 +743,22 @@@ static int update_shallow_ref(struct co
        return 0;
  }
  
+ /*
+  * NEEDSWORK: we should consolidate various implementions of "are we
+  * on an unborn branch?" test into one, and make the unified one more
+  * robust. !get_sha1() based check used here and elsewhere would not
+  * allow us to tell an unborn branch from corrupt ref, for example.
+  * For the purpose of fixing "deploy-to-update does not work when
+  * pushing into an empty repository" issue, this should suffice for
+  * now.
+  */
+ static int head_has_history(void)
+ {
+       unsigned char sha1[20];
+       return !get_sha1("HEAD", sha1);
+ }
  static const char *push_to_deploy(unsigned char *sha1,
                                  struct argv_array *env,
                                  const char *work_tree)
        };
        const char *diff_index[] = {
                "diff-index", "--quiet", "--cached", "--ignore-submodules",
-               "HEAD", "--", NULL
+               NULL, "--", NULL
        };
        const char *read_tree[] = {
                "read-tree", "-u", "-m", NULL, NULL
        if (run_command(&child))
                return "Working directory has unstaged changes";
  
+       /* diff-index with either HEAD or an empty tree */
+       diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX;
        child_process_init(&child);
        child.argv = diff_index;
        child.env = env->argv;
@@@ -940,7 -949,6 +959,7 @@@ static const char *update(struct comman
        }
  
        if (is_null_sha1(new_sha1)) {
 +              struct strbuf err = STRBUF_INIT;
                if (!parse_object(old_sha1)) {
                        old_sha1 = NULL;
                        if (ref_exists(name)) {
                                cmd->did_not_exist = 1;
                        }
                }
 -              if (delete_ref(namespaced_name, old_sha1, 0)) {
 -                      rp_error("failed to delete %s", name);
 +              if (ref_transaction_delete(transaction,
 +                                         namespaced_name,
 +                                         old_sha1,
 +                                         0, "push", &err)) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_release(&err);
                        return "failed to delete";
                }
 +              strbuf_release(&err);
                return NULL; /* good */
        }
        else {
                struct strbuf err = STRBUF_INIT;
 -              struct ref_transaction *transaction;
 -
                if (shallow_update && si->shallow_ref[cmd->index] &&
                    update_shallow_ref(cmd, si))
                        return "shallow error";
  
 -              transaction = ref_transaction_begin(&err);
 -              if (!transaction ||
 -                  ref_transaction_update(transaction, namespaced_name,
 -                                         new_sha1, old_sha1, 0, 1, "push",
 -                                         &err) ||
 -                  ref_transaction_commit(transaction, &err)) {
 -                      ref_transaction_free(transaction);
 -
 +              if (ref_transaction_update(transaction,
 +                                         namespaced_name,
 +                                         new_sha1, old_sha1,
 +                                         0, "push",
 +                                         &err)) {
                        rp_error("%s", err.buf);
                        strbuf_release(&err);
 +
                        return "failed to update ref";
                }
 -
 -              ref_transaction_free(transaction);
                strbuf_release(&err);
 +
                return NULL; /* good */
        }
  }
@@@ -1084,7 -1092,7 +1103,7 @@@ static void check_aliased_updates(struc
                        string_list_append(&ref_list, cmd->ref_name);
                item->util = (void *)cmd;
        }
 -      sort_string_list(&ref_list);
 +      string_list_sort(&ref_list);
  
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string)
@@@ -1162,105 -1170,11 +1181,105 @@@ static void reject_updates_to_hidden(st
        }
  }
  
 +static int should_process_cmd(struct command *cmd)
 +{
 +      return !cmd->error_string && !cmd->skip_update;
 +}
 +
 +static void warn_if_skipped_connectivity_check(struct command *commands,
 +                                             struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      int checked_connectivity = 1;
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
 +                      error("BUG: connectivity check has not been run on ref %s",
 +                            cmd->ref_name);
 +                      checked_connectivity = 0;
 +              }
 +      }
 +      if (!checked_connectivity)
 +              die("BUG: connectivity check skipped???");
 +}
 +
 +static void execute_commands_non_atomic(struct command *commands,
 +                                      struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!should_process_cmd(cmd))
 +                      continue;
 +
 +              transaction = ref_transaction_begin(&err);
 +              if (!transaction) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_reset(&err);
 +                      cmd->error_string = "transaction failed to start";
 +                      continue;
 +              }
 +
 +              cmd->error_string = update(cmd, si);
 +
 +              if (!cmd->error_string
 +                  && ref_transaction_commit(transaction, &err)) {
 +                      rp_error("%s", err.buf);
 +                      strbuf_reset(&err);
 +                      cmd->error_string = "failed to update ref";
 +              }
 +              ref_transaction_free(transaction);
 +      }
 +      strbuf_release(&err);
 +}
 +
 +static void execute_commands_atomic(struct command *commands,
 +                                      struct shallow_info *si)
 +{
 +      struct command *cmd;
 +      struct strbuf err = STRBUF_INIT;
 +      const char *reported_error = "atomic push failure";
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction) {
 +              rp_error("%s", err.buf);
 +              strbuf_reset(&err);
 +              reported_error = "transaction failed to start";
 +              goto failure;
 +      }
 +
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!should_process_cmd(cmd))
 +                      continue;
 +
 +              cmd->error_string = update(cmd, si);
 +
 +              if (cmd->error_string)
 +                      goto failure;
 +      }
 +
 +      if (ref_transaction_commit(transaction, &err)) {
 +              rp_error("%s", err.buf);
 +              reported_error = "atomic transaction failed";
 +              goto failure;
 +      }
 +      goto cleanup;
 +
 +failure:
 +      for (cmd = commands; cmd; cmd = cmd->next)
 +              if (!cmd->error_string)
 +                      cmd->error_string = reported_error;
 +
 +cleanup:
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +}
 +
  static void execute_commands(struct command *commands,
                             const char *unpacker_error,
                             struct shallow_info *si)
  {
 -      int checked_connectivity;
        struct command *cmd;
        unsigned char sha1[20];
        struct iterate_data data;
        free(head_name_to_free);
        head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
  
 -      checked_connectivity = 1;
 -      for (cmd = commands; cmd; cmd = cmd->next) {
 -              if (cmd->error_string)
 -                      continue;
 -
 -              if (cmd->skip_update)
 -                      continue;
 -
 -              cmd->error_string = update(cmd, si);
 -              if (shallow_update && !cmd->error_string &&
 -                  si->shallow_ref[cmd->index]) {
 -                      error("BUG: connectivity check has not been run on ref %s",
 -                            cmd->ref_name);
 -                      checked_connectivity = 0;
 -              }
 -      }
 +      if (use_atomic)
 +              execute_commands_atomic(commands, si);
 +      else
 +              execute_commands_non_atomic(commands, si);
  
 -      if (shallow_update && !checked_connectivity)
 -              error("BUG: run 'git fsck' for safety.\n"
 -                    "If there are errors, try to remove "
 -                    "the reported refs above");
 +      if (shallow_update)
 +              warn_if_skipped_connectivity_check(commands, si);
  }
  
  static struct command **queue_command(struct command **tail,
@@@ -1379,9 -1307,6 +1398,9 @@@ static struct command *read_head_info(s
                                use_sideband = LARGE_PACKET_MAX;
                        if (parse_feature_request(feature_list, "quiet"))
                                quiet = 1;
 +                      if (advertise_atomic_push
 +                          && parse_feature_request(feature_list, "atomic"))
 +                              use_atomic = 1;
                }
  
                if (!strcmp(line, "push-cert")) {
diff --combined t/t5516-fetch-push.sh
index 5e04d641092267e5bf6ae7fa0a1e36959d8c6b80,4f659d949bd2c8ec8f1be0663bb1b370412e2751..8a5f2363a93b324964caaab81903fae6b802c29b
@@@ -238,7 -238,7 +238,7 @@@ test_expect_success 'push with pushInst
  test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
        mk_empty testrepo &&
        test_config "url.trash2/.pushInsteadOf" testrepo/ &&
 -      test_config "url.trash3/.pusnInsteadOf" trash/wrong &&
 +      test_config "url.trash3/.pushInsteadOf" trash/wrong &&
        test_config remote.r.url trash/wrong &&
        test_config remote.r.pushurl "testrepo/" &&
        git push r refs/heads/master:refs/remotes/origin/master &&
@@@ -1107,16 -1107,9 +1107,16 @@@ test_expect_success 'fetch exact SHA1' 
                        git config uploadpack.allowtipsha1inwant true
                ) &&
  
 -              git fetch -v ../testrepo $the_commit:refs/heads/copy &&
 -              result=$(git rev-parse --verify refs/heads/copy) &&
 -              test "$the_commit" = "$result"
 +              git fetch -v ../testrepo $the_commit:refs/heads/copy master:refs/heads/extra &&
 +              cat >expect <<-EOF &&
 +              $the_commit
 +              $the_first_commit
 +              EOF
 +              {
 +                      git rev-parse --verify refs/heads/copy &&
 +                      git rev-parse --verify refs/heads/extra
 +              } >actual &&
 +              test_cmp expect actual
        )
  '
  
@@@ -1437,8 -1430,22 +1437,22 @@@ test_expect_success 'receive.denyCurren
                test $(git -C .. rev-parse HEAD^^) = $(git rev-parse HEAD) &&
                git diff --quiet &&
                test fifth = "$(cat path3)"
-       )
+       ) &&
  
+       # (5) push into void
+       rm -fr void &&
+       git init void &&
+       (
+               cd void &&
+               git config receive.denyCurrentBranch updateInstead
+       ) &&
+       git push void master &&
+       (
+               cd void &&
+               test $(git -C .. rev-parse master) = $(git rev-parse HEAD) &&
+               git diff --quiet &&
+               git diff --cached --quiet
+       )
  '
  
  test_expect_success 'updateInstead with push-to-checkout hook' '
                test "$(cat path5)" = irrelevant &&
                test "$(git diff --name-only --cached HEAD)" = path5 &&
                test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD)
+       ) &&
+       # push into void
+       rm -fr void &&
+       git init void &&
+       (
+               cd void &&
+               git config receive.denyCurrentBranch updateInstead &&
+               write_script .git/hooks/push-to-checkout <<-\EOF
+               if git rev-parse --quiet --verify HEAD
+               then
+                       has_head=yes
+                       echo >&2 updating from $(git rev-parse HEAD)
+               else
+                       has_head=no
+                       echo >&2 pushing into void
+               fi
+               echo >&2 updating to "$1"
+               git update-index -q --refresh &&
+               case "$has_head" in
+               yes)
+                       git read-tree -u -m HEAD "$1" ;;
+               no)
+                       git read-tree -u -m "$1" ;;
+               esac || {
+                       status=$?
+                       echo >&2 read-tree failed
+                       exit $status
+               }
+               EOF
+       ) &&
+       git push void master &&
+       (
+               cd void &&
+               git diff --quiet &&
+               git diff --cached --quiet &&
+               test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD)
        )
  '