Merge branch 'mv/maint-branch-m-symref'
authorJunio C Hamano <gitster@pobox.com>
Wed, 5 Nov 2008 19:33:19 +0000 (11:33 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Nov 2008 19:33:19 +0000 (11:33 -0800)
* mv/maint-branch-m-symref:
update-ref --no-deref -d: handle the case when the pointed ref is packed
git branch -m: forbid renaming of a symref
Fix git update-ref --no-deref -d.
rename_ref(): handle the case when the reflog of a ref does not exist
Fix git branch -m for symrefs.

1  2 
builtin-branch.c
builtin-receive-pack.c
builtin-remote.c
builtin-send-pack.c
builtin-tag.c
cache.h
refs.c
diff --combined builtin-branch.c
index 8d634ff571ce34ce21a5519628d6f66a7e52aa93,4b4abfd3639d2c6a1405e2e056ed5362b4b73abb..2b3613fea2d7799b7d20e9e30da20527d811ed64
@@@ -160,7 -160,7 +160,7 @@@ static int delete_branches(int argc, co
                        continue;
                }
  
-               if (delete_ref(name, sha1)) {
+               if (delete_ref(name, sha1, 0)) {
                        error("Error deleting %sbranch '%s'", remote,
                               argv[i]);
                        ret = 1;
@@@ -334,10 -334,11 +334,10 @@@ static void print_ref_item(struct ref_i
        }
  
        if (verbose) {
 -              struct strbuf subject;
 +              struct strbuf subject = STRBUF_INIT;
                const char *sub = " **** invalid ref ****";
                char stat[128];
  
 -              strbuf_init(&subject, 0);
                stat[0] = '\0';
  
                commit = item->commit;
diff --combined builtin-receive-pack.c
index 2c0225c89a44a28150806608ea3956c44e27ef6f,0000000000000000000000000000000000000000..7f9f134806766244bf1eb3de2de3a823a62b180a
mode 100644,000000..100644
--- /dev/null
@@@ -1,575 -1,0 +1,575 @@@
-               if (delete_ref(name, old_sha1)) {
 +#include "cache.h"
 +#include "pack.h"
 +#include "refs.h"
 +#include "pkt-line.h"
 +#include "run-command.h"
 +#include "exec_cmd.h"
 +#include "commit.h"
 +#include "object.h"
 +#include "remote.h"
 +#include "transport.h"
 +
 +static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 +
 +static int deny_deletes = 0;
 +static int deny_non_fast_forwards = 0;
 +static int receive_fsck_objects;
 +static int receive_unpack_limit = -1;
 +static int transfer_unpack_limit = -1;
 +static int unpack_limit = 100;
 +static int report_status;
 +
 +static char capabilities[] = " report-status delete-refs ";
 +static int capabilities_sent;
 +
 +static int receive_pack_config(const char *var, const char *value, void *cb)
 +{
 +      if (strcmp(var, "receive.denydeletes") == 0) {
 +              deny_deletes = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.denynonfastforwards") == 0) {
 +              deny_non_fast_forwards = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.unpacklimit") == 0) {
 +              receive_unpack_limit = git_config_int(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "transfer.unpacklimit") == 0) {
 +              transfer_unpack_limit = git_config_int(var, value);
 +              return 0;
 +      }
 +
 +      if (strcmp(var, "receive.fsckobjects") == 0) {
 +              receive_fsck_objects = git_config_bool(var, value);
 +              return 0;
 +      }
 +
 +      return git_default_config(var, value, cb);
 +}
 +
 +static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 +{
 +      if (capabilities_sent)
 +              packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
 +      else
 +              packet_write(1, "%s %s%c%s\n",
 +                           sha1_to_hex(sha1), path, 0, capabilities);
 +      capabilities_sent = 1;
 +      return 0;
 +}
 +
 +static void write_head_info(void)
 +{
 +      for_each_ref(show_ref, NULL);
 +      if (!capabilities_sent)
 +              show_ref("capabilities^{}", null_sha1, 0, NULL);
 +
 +}
 +
 +struct command {
 +      struct command *next;
 +      const char *error_string;
 +      unsigned char old_sha1[20];
 +      unsigned char new_sha1[20];
 +      char ref_name[FLEX_ARRAY]; /* more */
 +};
 +
 +static struct command *commands;
 +
 +static const char pre_receive_hook[] = "hooks/pre-receive";
 +static const char post_receive_hook[] = "hooks/post-receive";
 +
 +static int hook_status(int code, const char *hook_name)
 +{
 +      switch (code) {
 +      case 0:
 +              return 0;
 +      case -ERR_RUN_COMMAND_FORK:
 +              return error("hook fork failed");
 +      case -ERR_RUN_COMMAND_EXEC:
 +              return error("hook execute failed");
 +      case -ERR_RUN_COMMAND_PIPE:
 +              return error("hook pipe failed");
 +      case -ERR_RUN_COMMAND_WAITPID:
 +              return error("waitpid failed");
 +      case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
 +              return error("waitpid is confused");
 +      case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
 +              return error("%s died of signal", hook_name);
 +      case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
 +              return error("%s died strangely", hook_name);
 +      default:
 +              error("%s exited with error code %d", hook_name, -code);
 +              return -code;
 +      }
 +}
 +
 +static int run_hook(const char *hook_name)
 +{
 +      static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
 +      struct command *cmd;
 +      struct child_process proc;
 +      const char *argv[2];
 +      int have_input = 0, code;
 +
 +      for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
 +              if (!cmd->error_string)
 +                      have_input = 1;
 +      }
 +
 +      if (!have_input || access(hook_name, X_OK) < 0)
 +              return 0;
 +
 +      argv[0] = hook_name;
 +      argv[1] = NULL;
 +
 +      memset(&proc, 0, sizeof(proc));
 +      proc.argv = argv;
 +      proc.in = -1;
 +      proc.stdout_to_stderr = 1;
 +
 +      code = start_command(&proc);
 +      if (code)
 +              return hook_status(code, hook_name);
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!cmd->error_string) {
 +                      size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
 +                              sha1_to_hex(cmd->old_sha1),
 +                              sha1_to_hex(cmd->new_sha1),
 +                              cmd->ref_name);
 +                      if (write_in_full(proc.in, buf, n) != n)
 +                              break;
 +              }
 +      }
 +      close(proc.in);
 +      return hook_status(finish_command(&proc), hook_name);
 +}
 +
 +static int run_update_hook(struct command *cmd)
 +{
 +      static const char update_hook[] = "hooks/update";
 +      struct child_process proc;
 +      const char *argv[5];
 +
 +      if (access(update_hook, X_OK) < 0)
 +              return 0;
 +
 +      argv[0] = update_hook;
 +      argv[1] = cmd->ref_name;
 +      argv[2] = sha1_to_hex(cmd->old_sha1);
 +      argv[3] = sha1_to_hex(cmd->new_sha1);
 +      argv[4] = NULL;
 +
 +      memset(&proc, 0, sizeof(proc));
 +      proc.argv = argv;
 +      proc.no_stdin = 1;
 +      proc.stdout_to_stderr = 1;
 +
 +      return hook_status(run_command(&proc), update_hook);
 +}
 +
 +static const char *update(struct command *cmd)
 +{
 +      const char *name = cmd->ref_name;
 +      unsigned char *old_sha1 = cmd->old_sha1;
 +      unsigned char *new_sha1 = cmd->new_sha1;
 +      struct ref_lock *lock;
 +
 +      /* only refs/... are allowed */
 +      if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
 +              error("refusing to create funny ref '%s' remotely", name);
 +              return "funny refname";
 +      }
 +
 +      if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
 +              error("unpack should have generated %s, "
 +                    "but I can't find it!", sha1_to_hex(new_sha1));
 +              return "bad pack";
 +      }
 +      if (deny_deletes && is_null_sha1(new_sha1) &&
 +          !is_null_sha1(old_sha1) &&
 +          !prefixcmp(name, "refs/heads/")) {
 +              error("denying ref deletion for %s", name);
 +              return "deletion prohibited";
 +      }
 +      if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
 +          !is_null_sha1(old_sha1) &&
 +          !prefixcmp(name, "refs/heads/")) {
 +              struct object *old_object, *new_object;
 +              struct commit *old_commit, *new_commit;
 +              struct commit_list *bases, *ent;
 +
 +              old_object = parse_object(old_sha1);
 +              new_object = parse_object(new_sha1);
 +
 +              if (!old_object || !new_object ||
 +                  old_object->type != OBJ_COMMIT ||
 +                  new_object->type != OBJ_COMMIT) {
 +                      error("bad sha1 objects for %s", name);
 +                      return "bad ref";
 +              }
 +              old_commit = (struct commit *)old_object;
 +              new_commit = (struct commit *)new_object;
 +              bases = get_merge_bases(old_commit, new_commit, 1);
 +              for (ent = bases; ent; ent = ent->next)
 +                      if (!hashcmp(old_sha1, ent->item->object.sha1))
 +                              break;
 +              free_commit_list(bases);
 +              if (!ent) {
 +                      error("denying non-fast forward %s"
 +                            " (you should pull first)", name);
 +                      return "non-fast forward";
 +              }
 +      }
 +      if (run_update_hook(cmd)) {
 +              error("hook declined to update %s", name);
 +              return "hook declined";
 +      }
 +
 +      if (is_null_sha1(new_sha1)) {
 +              if (!parse_object(old_sha1)) {
 +                      warning ("Allowing deletion of corrupt ref.");
 +                      old_sha1 = NULL;
 +              }
++              if (delete_ref(name, old_sha1, 0)) {
 +                      error("failed to delete %s", name);
 +                      return "failed to delete";
 +              }
 +              return NULL; /* good */
 +      }
 +      else {
 +              lock = lock_any_ref_for_update(name, old_sha1, 0);
 +              if (!lock) {
 +                      error("failed to lock %s", name);
 +                      return "failed to lock";
 +              }
 +              if (write_ref_sha1(lock, new_sha1, "push")) {
 +                      return "failed to write"; /* error() already called */
 +              }
 +              return NULL; /* good */
 +      }
 +}
 +
 +static char update_post_hook[] = "hooks/post-update";
 +
 +static void run_update_post_hook(struct command *cmd)
 +{
 +      struct command *cmd_p;
 +      int argc;
 +      const char **argv;
 +
 +      for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
 +              if (cmd_p->error_string)
 +                      continue;
 +              argc++;
 +      }
 +      if (!argc || access(update_post_hook, X_OK) < 0)
 +              return;
 +      argv = xmalloc(sizeof(*argv) * (2 + argc));
 +      argv[0] = update_post_hook;
 +
 +      for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
 +              char *p;
 +              if (cmd_p->error_string)
 +                      continue;
 +              p = xmalloc(strlen(cmd_p->ref_name) + 1);
 +              strcpy(p, cmd_p->ref_name);
 +              argv[argc] = p;
 +              argc++;
 +      }
 +      argv[argc] = NULL;
 +      run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
 +              | RUN_COMMAND_STDOUT_TO_STDERR);
 +}
 +
 +static void execute_commands(const char *unpacker_error)
 +{
 +      struct command *cmd = commands;
 +
 +      if (unpacker_error) {
 +              while (cmd) {
 +                      cmd->error_string = "n/a (unpacker error)";
 +                      cmd = cmd->next;
 +              }
 +              return;
 +      }
 +
 +      if (run_hook(pre_receive_hook)) {
 +              while (cmd) {
 +                      cmd->error_string = "pre-receive hook declined";
 +                      cmd = cmd->next;
 +              }
 +              return;
 +      }
 +
 +      while (cmd) {
 +              cmd->error_string = update(cmd);
 +              cmd = cmd->next;
 +      }
 +}
 +
 +static void read_head_info(void)
 +{
 +      struct command **p = &commands;
 +      for (;;) {
 +              static char line[1000];
 +              unsigned char old_sha1[20], new_sha1[20];
 +              struct command *cmd;
 +              char *refname;
 +              int len, reflen;
 +
 +              len = packet_read_line(0, line, sizeof(line));
 +              if (!len)
 +                      break;
 +              if (line[len-1] == '\n')
 +                      line[--len] = 0;
 +              if (len < 83 ||
 +                  line[40] != ' ' ||
 +                  line[81] != ' ' ||
 +                  get_sha1_hex(line, old_sha1) ||
 +                  get_sha1_hex(line + 41, new_sha1))
 +                      die("protocol error: expected old/new/ref, got '%s'",
 +                          line);
 +
 +              refname = line + 82;
 +              reflen = strlen(refname);
 +              if (reflen + 82 < len) {
 +                      if (strstr(refname + reflen + 1, "report-status"))
 +                              report_status = 1;
 +              }
 +              cmd = xmalloc(sizeof(struct command) + len - 80);
 +              hashcpy(cmd->old_sha1, old_sha1);
 +              hashcpy(cmd->new_sha1, new_sha1);
 +              memcpy(cmd->ref_name, line + 82, len - 81);
 +              cmd->error_string = NULL;
 +              cmd->next = NULL;
 +              *p = cmd;
 +              p = &cmd->next;
 +      }
 +}
 +
 +static const char *parse_pack_header(struct pack_header *hdr)
 +{
 +      switch (read_pack_header(0, hdr)) {
 +      case PH_ERROR_EOF:
 +              return "eof before pack header was fully read";
 +
 +      case PH_ERROR_PACK_SIGNATURE:
 +              return "protocol error (pack signature mismatch detected)";
 +
 +      case PH_ERROR_PROTOCOL:
 +              return "protocol error (pack version unsupported)";
 +
 +      default:
 +              return "unknown error in parse_pack_header";
 +
 +      case 0:
 +              return NULL;
 +      }
 +}
 +
 +static const char *pack_lockfile;
 +
 +static const char *unpack(void)
 +{
 +      struct pack_header hdr;
 +      const char *hdr_err;
 +      char hdr_arg[38];
 +
 +      hdr_err = parse_pack_header(&hdr);
 +      if (hdr_err)
 +              return hdr_err;
 +      snprintf(hdr_arg, sizeof(hdr_arg),
 +                      "--pack_header=%"PRIu32",%"PRIu32,
 +                      ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
 +
 +      if (ntohl(hdr.hdr_entries) < unpack_limit) {
 +              int code, i = 0;
 +              const char *unpacker[4];
 +              unpacker[i++] = "unpack-objects";
 +              if (receive_fsck_objects)
 +                      unpacker[i++] = "--strict";
 +              unpacker[i++] = hdr_arg;
 +              unpacker[i++] = NULL;
 +              code = run_command_v_opt(unpacker, RUN_GIT_CMD);
 +              switch (code) {
 +              case 0:
 +                      return NULL;
 +              case -ERR_RUN_COMMAND_FORK:
 +                      return "unpack fork failed";
 +              case -ERR_RUN_COMMAND_EXEC:
 +                      return "unpack execute failed";
 +              case -ERR_RUN_COMMAND_WAITPID:
 +                      return "waitpid failed";
 +              case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
 +                      return "waitpid is confused";
 +              case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
 +                      return "unpacker died of signal";
 +              case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
 +                      return "unpacker died strangely";
 +              default:
 +                      return "unpacker exited with error code";
 +              }
 +      } else {
 +              const char *keeper[7];
 +              int s, status, i = 0;
 +              char keep_arg[256];
 +              struct child_process ip;
 +
 +              s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
 +              if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
 +                      strcpy(keep_arg + s, "localhost");
 +
 +              keeper[i++] = "index-pack";
 +              keeper[i++] = "--stdin";
 +              if (receive_fsck_objects)
 +                      keeper[i++] = "--strict";
 +              keeper[i++] = "--fix-thin";
 +              keeper[i++] = hdr_arg;
 +              keeper[i++] = keep_arg;
 +              keeper[i++] = NULL;
 +              memset(&ip, 0, sizeof(ip));
 +              ip.argv = keeper;
 +              ip.out = -1;
 +              ip.git_cmd = 1;
 +              if (start_command(&ip))
 +                      return "index-pack fork failed";
 +              pack_lockfile = index_pack_lockfile(ip.out);
 +              close(ip.out);
 +              status = finish_command(&ip);
 +              if (!status) {
 +                      reprepare_packed_git();
 +                      return NULL;
 +              }
 +              return "index-pack abnormal exit";
 +      }
 +}
 +
 +static void report(const char *unpack_status)
 +{
 +      struct command *cmd;
 +      packet_write(1, "unpack %s\n",
 +                   unpack_status ? unpack_status : "ok");
 +      for (cmd = commands; cmd; cmd = cmd->next) {
 +              if (!cmd->error_string)
 +                      packet_write(1, "ok %s\n",
 +                                   cmd->ref_name);
 +              else
 +                      packet_write(1, "ng %s %s\n",
 +                                   cmd->ref_name, cmd->error_string);
 +      }
 +      packet_flush(1);
 +}
 +
 +static int delete_only(struct command *cmd)
 +{
 +      while (cmd) {
 +              if (!is_null_sha1(cmd->new_sha1))
 +                      return 0;
 +              cmd = cmd->next;
 +      }
 +      return 1;
 +}
 +
 +static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
 +{
 +      char *other;
 +      size_t len;
 +      struct remote *remote;
 +      struct transport *transport;
 +      const struct ref *extra;
 +
 +      e->name[-1] = '\0';
 +      other = xstrdup(make_absolute_path(e->base));
 +      e->name[-1] = '/';
 +      len = strlen(other);
 +
 +      while (other[len-1] == '/')
 +              other[--len] = '\0';
 +      if (len < 8 || memcmp(other + len - 8, "/objects", 8))
 +              return 0;
 +      /* Is this a git repository with refs? */
 +      memcpy(other + len - 8, "/refs", 6);
 +      if (!is_directory(other))
 +              return 0;
 +      other[len - 8] = '\0';
 +      remote = remote_get(other);
 +      transport = transport_get(remote, other);
 +      for (extra = transport_get_remote_refs(transport);
 +           extra;
 +           extra = extra->next) {
 +              add_extra_ref(".have", extra->old_sha1, 0);
 +      }
 +      transport_disconnect(transport);
 +      free(other);
 +      return 0;
 +}
 +
 +static void add_alternate_refs(void)
 +{
 +      foreach_alt_odb(add_refs_from_alternate, NULL);
 +}
 +
 +int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 +{
 +      int i;
 +      char *dir = NULL;
 +
 +      argv++;
 +      for (i = 1; i < argc; i++) {
 +              const char *arg = *argv++;
 +
 +              if (*arg == '-') {
 +                      /* Do flag handling here */
 +                      usage(receive_pack_usage);
 +              }
 +              if (dir)
 +                      usage(receive_pack_usage);
 +              dir = xstrdup(arg);
 +      }
 +      if (!dir)
 +              usage(receive_pack_usage);
 +
 +      setup_path();
 +
 +      if (!enter_repo(dir, 0))
 +              die("'%s': unable to chdir or not a git archive", dir);
 +
 +      if (is_repository_shallow())
 +              die("attempt to push into a shallow repository");
 +
 +      git_config(receive_pack_config, NULL);
 +
 +      if (0 <= transfer_unpack_limit)
 +              unpack_limit = transfer_unpack_limit;
 +      else if (0 <= receive_unpack_limit)
 +              unpack_limit = receive_unpack_limit;
 +
 +      add_alternate_refs();
 +      write_head_info();
 +      clear_extra_refs();
 +
 +      /* EOF */
 +      packet_flush(1);
 +
 +      read_head_info();
 +      if (commands) {
 +              const char *unpack_status = NULL;
 +
 +              if (!delete_only(commands))
 +                      unpack_status = unpack();
 +              execute_commands(unpack_status);
 +              if (pack_lockfile)
 +                      unlink(pack_lockfile);
 +              if (report_status)
 +                      report(unpack_status);
 +              run_hook(post_receive_hook);
 +              run_update_post_hook(commands);
 +      }
 +      return 0;
 +}
diff --combined builtin-remote.c
index df2be068b69b6d926d4980e6e9eada05d92643e1,584280fbf5bbbd485a1388adb15104a1ad1cf19c..e396a3ac9055aa91d0f808abb196fb6ea9a99a29
@@@ -54,7 -54,7 +54,7 @@@ static int add(int argc, const char **a
        struct string_list track = { NULL, 0, 0 };
        const char *master = NULL;
        struct remote *remote;
 -      struct strbuf buf, buf2;
 +      struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
        const char *name, *url;
        int i;
  
@@@ -81,6 -81,9 +81,6 @@@
                        remote->fetch_refspec_nr))
                die("remote %s already exists.", name);
  
 -      strbuf_init(&buf, 0);
 -      strbuf_init(&buf2, 0);
 -
        strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
        if (!valid_fetch_refspec(buf2.buf))
                die("'%s' is not a valid remote name", name);
@@@ -337,7 -340,7 +337,7 @@@ static int remove_branches(struct strin
                const char *refname = item->string;
                unsigned char *sha1 = item->util;
  
-               if (delete_ref(refname, sha1))
+               if (delete_ref(refname, sha1, 0))
                        result |= error("Could not remove branch %s", refname);
        }
        return result;
@@@ -349,7 -352,7 +349,7 @@@ static int rm(int argc, const char **ar
                OPT_END()
        };
        struct remote *remote;
 -      struct strbuf buf;
 +      struct strbuf buf = STRBUF_INIT;
        struct known_remotes known_remotes = { NULL, NULL };
        struct string_list branches = { NULL, 0, 0, 1 };
        struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
        known_remotes.to_delete = remote;
        for_each_remote(add_known_remote, &known_remotes);
  
 -      strbuf_init(&buf, 0);
        strbuf_addf(&buf, "remote.%s", remote->name);
        if (git_config_rename_section(buf.buf, NULL) < 1)
                return error("Could not remove config section '%s'", buf.buf);
@@@ -412,9 -416,10 +412,9 @@@ static void show_list(const char *title
                return;
  
        printf(title, list->nr > 1 ? "es" : "", extra_arg);
 -      printf("\n    ");
 -      for (i = 0; i < list->nr; i++)
 -              printf("%s%s", i ? " " : "", list->items[i].string);
        printf("\n");
 +      for (i = 0; i < list->nr; i++)
 +              printf("    %s\n", list->items[i].string);
  }
  
  static int get_remote_ref_states(const char *name,
@@@ -510,17 -515,17 +510,17 @@@ static int show(int argc, const char **
                show_list("  Tracked remote branch%s", &states.tracked, "");
  
                if (states.remote->push_refspec_nr) {
 -                      printf("  Local branch%s pushed with 'git push'\n   ",
 +                      printf("  Local branch%s pushed with 'git push'\n",
                                states.remote->push_refspec_nr > 1 ?
                                        "es" : "");
                        for (i = 0; i < states.remote->push_refspec_nr; i++) {
                                struct refspec *spec = states.remote->push + i;
 -                              printf(" %s%s%s%s", spec->force ? "+" : "",
 +                              printf("    %s%s%s%s\n",
 +                                     spec->force ? "+" : "",
                                       abbrev_branch(spec->src),
                                       spec->dst ? ":" : "",
                                       spec->dst ? abbrev_branch(spec->dst) : "");
                        }
 -                      printf("\n");
                }
  
                /* NEEDSWORK: free remote */
@@@ -565,7 -570,7 +565,7 @@@ static int prune(int argc, const char *
                        const char *refname = states.stale.items[i].util;
  
                        if (!dry_run)
-                               result |= delete_ref(refname, NULL);
+                               result |= delete_ref(refname, NULL, 0);
  
                        printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
                               abbrev_ref(refname, "refs/remotes/"));
diff --combined builtin-send-pack.c
index d68ce2d0e3451127c61658ae7df3053f5eae6366,ea1ad6e35f59ef361b40e8f5a54bc9fc9940e59a..298bd719240ba503a3128486883d33df011765b1
@@@ -18,7 -18,7 +18,7 @@@ static struct send_pack_args args = 
  /*
   * Make a pack stream and spit it out into file descriptor fd
   */
 -static int pack_objects(int fd, struct ref *refs)
 +static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra)
  {
        /*
         * The child becomes pack-objects --revs; we feed
@@@ -34,8 -34,6 +34,8 @@@
                NULL,
        };
        struct child_process po;
 +      int i;
 +      char buf[42];
  
        if (args.use_thin_pack)
                argv[4] = "--thin";
         * We feed the pack-objects we just spawned with revision
         * parameters by writing to the pipe.
         */
 -      while (refs) {
 -              char buf[42];
 +      for (i = 0; i < extra->nr; i++) {
 +              memcpy(buf + 1, sha1_to_hex(&extra->array[i][0]), 40);
 +              buf[0] = '^';
 +              buf[41] = '\n';
 +              if (!write_or_whine(po.in, buf, 42, "send-pack: send refs"))
 +                      break;
 +      }
  
 +      while (refs) {
                if (!is_null_sha1(refs->old_sha1) &&
                    has_sha1_file(refs->old_sha1)) {
                        memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
@@@ -140,13 -132,7 +140,13 @@@ static struct ref *remote_refs, **remot
  static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
  {
        struct ref *ref;
 -      int len = strlen(refname) + 1;
 +      int len;
 +
 +      /* we already know it starts with refs/ to get here */
 +      if (check_ref_format(refname + 5))
 +              return 0;
 +
 +      len = strlen(refname) + 1;
        ref = xcalloc(1, sizeof(*ref) + len);
        hashcpy(ref->new_sha1, sha1);
        memcpy(ref->name, refname, len);
@@@ -240,7 -226,7 +240,7 @@@ static void update_tracking_ref(struct 
                if (args.verbose)
                        fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
                if (ref->deletion) {
-                       delete_ref(rs.dst, NULL);
+                       delete_ref(rs.dst, NULL, 0);
                } else
                        update_ref("update by push", rs.dst,
                                        ref->new_sha1, NULL, 0, 0);
@@@ -395,17 -381,14 +395,17 @@@ static int do_send_pack(int in, int out
        int expect_status_report = 0;
        int flags = MATCH_REFS_NONE;
        int ret;
 +      struct extra_have_objects extra_have;
  
 +      memset(&extra_have, 0, sizeof(extra_have));
        if (args.send_all)
                flags |= MATCH_REFS_ALL;
        if (args.send_mirror)
                flags |= MATCH_REFS_MIRROR;
  
        /* No funny business with the matcher */
 -      remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
 +      remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL,
 +                                     &extra_have);
        get_local_heads();
  
        /* Does the other end support the reporting? */
  
        packet_flush(out);
        if (new_refs && !args.dry_run) {
 -              if (pack_objects(out, remote_refs) < 0)
 +              if (pack_objects(out, remote_refs, &extra_have) < 0)
                        return -1;
        }
        else
diff --combined builtin-tag.c
index b13fa34d8c59a9226599ba10b4d679f41ad230b0,5b141c584680ecc2362fc0e036e01dbeefa4ee90..1ff7b37162185a70de5183dd55819f6b11fa6688
@@@ -125,7 -125,7 +125,7 @@@ static int for_each_tag_name(const cha
  static int delete_tag(const char *name, const char *ref,
                                const unsigned char *sha1)
  {
-       if (delete_ref(ref, sha1))
+       if (delete_ref(ref, sha1, 0))
                return 1;
        printf("Deleted tag '%s'\n", name);
        return 0;
@@@ -338,7 -338,7 +338,7 @@@ static int parse_msg_arg(const struct o
  
  int cmd_tag(int argc, const char **argv, const char *prefix)
  {
 -      struct strbuf buf;
 +      struct strbuf buf = STRBUF_INIT;
        unsigned char object[20], prev[20];
        char ref[PATH_MAX];
        const char *object_ref, *tag;
        if (verify)
                return for_each_tag_name(argv, verify_tag);
  
 -      strbuf_init(&buf, 0);
        if (msg.given || msgfile) {
                if (msg.given && msgfile)
                        die("only one -F or -m option is allowed.");
diff --combined cache.h
index b0edbf9b9f68a98a0052ecb949f20aafb3125e56,715348a0663e278d4eecc0c04c0ad9b8148493b2..a3c77f08ca18f44c1617efd429a819ead3f80234
+++ b/cache.h
@@@ -6,14 -6,8 +6,14 @@@
  #include "hash.h"
  
  #include SHA1_HEADER
 -#include <zlib.h>
 +#ifndef git_SHA_CTX
 +#define git_SHA_CTX   SHA_CTX
 +#define git_SHA1_Init SHA1_Init
 +#define git_SHA1_Update       SHA1_Update
 +#define git_SHA1_Final        SHA1_Final
 +#endif
  
 +#include <zlib.h>
  #if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
  #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
  #endif
@@@ -132,7 -126,6 +132,7 @@@ struct cache_entry 
  
  #define CE_NAMEMASK  (0x0fff)
  #define CE_STAGEMASK (0x3000)
 +#define CE_EXTENDED  (0x4000)
  #define CE_VALID     (0x8000)
  #define CE_STAGESHIFT 12
  
@@@ -277,7 -270,6 +277,7 @@@ static inline void remove_name_hash(str
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
  #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 +#define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
  #endif
  
  enum object_type {
@@@ -320,7 -312,6 +320,7 @@@ extern int is_bare_repository(void)
  extern int is_inside_git_dir(void);
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
 +extern int have_git_dir(void);
  extern const char *get_git_dir(void);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
@@@ -379,7 -370,6 +379,7 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
 +#define ADD_CACHE_NEW_ONLY 16         /* Do not replace existing ones */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
@@@ -388,13 -378,10 +388,13 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_PRETEND 2
  #define ADD_CACHE_IGNORE_ERRORS       4
 +#define ADD_CACHE_IGNORE_REMOVAL 8
 +#define ADD_CACHE_INTENT 16
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
  extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int index_name_is_other(const struct index_state *, const char *, int);
  
  /* do stat comparison even if CE_VALID is true */
  #define CE_MATCH_IGNORE_VALID         01
@@@ -405,6 -392,7 +405,6 @@@ extern int ie_modified(const struct ind
  
  extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
 -extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
@@@ -423,8 -411,6 +423,8 @@@ struct lock_file 
        char on_list;
        char filename[PATH_MAX];
  };
 +#define LOCK_DIE_ON_ERROR 1
 +#define LOCK_NODEREF 2
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
  extern int commit_lock_file(struct lock_file *);
@@@ -434,7 -420,7 +434,7 @@@ extern int commit_locked_index(struct l
  extern void set_alternate_index_output(const char *);
  extern int close_lock_file(struct lock_file *);
  extern void rollback_lock_file(struct lock_file *);
- extern int delete_ref(const char *, const unsigned char *sha1);
+ extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
  
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
@@@ -466,7 -452,6 +466,7 @@@ enum safe_crlf 
  extern enum safe_crlf safe_crlf;
  
  enum branch_track {
 +      BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
@@@ -519,7 -504,6 +519,7 @@@ static inline void hashclr(unsigned cha
  {
        memset(hash, 0, 20);
  }
 +extern int is_empty_blob_sha1(const unsigned char *sha1);
  
  int git_mkstemp(char *path, size_t n, const char *template);
  
@@@ -547,7 -531,6 +547,7 @@@ static inline int is_absolute_path(cons
  {
        return path[0] == '/' || has_dos_drive_prefix(path);
  }
 +int is_directory(const char *);
  const char *make_absolute_path(const char *path);
  const char *make_nonrelative_path(const char *path);
  const char *make_relative_path(const char *abs, const char *base);
@@@ -655,8 -638,6 +655,8 @@@ extern struct alternate_object_databas
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
  extern void add_to_alternates_file(const char *reference);
 +typedef int alt_odb_fn(struct alternate_object_database *, void *);
 +extern void foreach_alt_odb(alt_odb_fn, void*);
  
  struct pack_window {
        struct pack_window *next;
@@@ -725,11 -706,7 +725,11 @@@ extern struct child_process *git_connec
  extern int finish_connect(struct child_process *conn);
  extern int path_match(const char *path, int nr, char **match);
  extern int get_ack(int fd, unsigned char *result_sha1);
 -extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
 +struct extra_have_objects {
 +      int nr, alloc;
 +      unsigned char (*array)[20];
 +};
 +extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
  extern int server_supports(const char *feature);
  
  extern struct packed_git *parse_pack_index(unsigned char *sha1);
@@@ -763,6 -740,7 +763,6 @@@ typedef int (*config_fn_t)(const char *
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
  extern int git_config(config_fn_t fn, void *);
 -extern int git_parse_long(const char *, long *);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
diff --combined refs.c
index 0a126fa371ae24f5cb251ff52cf7d38322d78ba7,0d239e15b08b9ccf798700ff4b93b4a85f416d7d..cc4b4c3941145980bde66d387b0febd8f14c70e2
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -390,18 -390,6 +390,18 @@@ int resolve_gitlink_ref(const char *pat
        return retval;
  }
  
 +/*
 + * If the "reading" argument is set, this function finds out what _object_
 + * the ref points at by "reading" the ref.  The ref, if it is not symbolic,
 + * has to exist, and if it is symbolic, it has to point at an existing ref,
 + * because the "read" goes through the symref to the ref it points at.
 + *
 + * The access that is not "reading" may often be "writing", but does not
 + * have to; it can be merely checking _where it leads to_. If it is a
 + * prelude to "writing" to the ref, a write to a symref that points at
 + * yet-to-be-born ref will create the real ref pointed by the symref.
 + * reading=0 allows the caller to check where such a symref leads to.
 + */
  const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
  {
        int depth = MAXDEPTH;
                if (--depth < 0)
                        return NULL;
  
 -              /* Special case: non-existing file.
 -               * Not having the refs/heads/new-branch is OK
 -               * if we are writing into it, so is .git/HEAD
 -               * that points at refs/heads/master still to be
 -               * born.  It is NOT OK if we are resolving for
 -               * reading.
 -               */
 +              /* Special case: non-existing file. */
                if (lstat(path, &st) < 0) {
                        struct ref_list *list = get_packed_refs();
                        while (list) {
@@@ -796,7 -790,7 +796,7 @@@ static struct ref_lock *lock_ref_sha1_b
        struct ref_lock *lock;
        struct stat st;
        int last_errno = 0;
 -      int type;
 +      int type, lflags;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
  
        lock = xcalloc(1, sizeof(struct ref_lock));
  
        lock->lk = xcalloc(1, sizeof(struct lock_file));
  
 -      if (flags & REF_NODEREF)
 +      lflags = LOCK_DIE_ON_ERROR;
 +      if (flags & REF_NODEREF) {
                ref = orig_ref;
 +              lflags |= LOCK_NODEREF;
 +      }
        lock->ref_name = xstrdup(ref);
        lock->orig_ref_name = xstrdup(orig_ref);
        ref_file = git_path("%s", ref);
                error("unable to create directory for %s", ref_file);
                goto error_return;
        }
 -      lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1);
  
 +      lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
        return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
  
   error_return:
@@@ -921,25 -912,33 +921,33 @@@ static int repack_without_ref(const cha
        return commit_lock_file(&packlock);
  }
  
- int delete_ref(const char *refname, const unsigned char *sha1)
+ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
  {
        struct ref_lock *lock;
-       int err, i, ret = 0, flag = 0;
+       int err, i = 0, ret = 0, flag = 0;
  
        lock = lock_ref_sha1_basic(refname, sha1, 0, &flag);
        if (!lock)
                return 1;
-       if (!(flag & REF_ISPACKED)) {
+       if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
                /* loose */
-               i = strlen(lock->lk->filename) - 5; /* .lock */
-               lock->lk->filename[i] = 0;
-               err = unlink(lock->lk->filename);
+               const char *path;
+               if (!(delopt & REF_NODEREF)) {
+                       i = strlen(lock->lk->filename) - 5; /* .lock */
+                       lock->lk->filename[i] = 0;
+                       path = lock->lk->filename;
+               } else {
+                       path = git_path(refname);
+               }
+               err = unlink(path);
                if (err && errno != ENOENT) {
                        ret = 1;
                        error("unlink(%s) failed: %s",
-                             lock->lk->filename, strerror(errno));
+                             path, strerror(errno));
                }
-               lock->lk->filename[i] = '.';
+               if (!(delopt & REF_NODEREF))
+                       lock->lk->filename[i] = '.';
        }
        /* removing the loose one could have resurrected an earlier
         * packed one.  Also, if it was not loose we need to repack
@@@ -964,11 -963,16 +972,16 @@@ int rename_ref(const char *oldref, cons
        struct ref_lock *lock;
        struct stat loginfo;
        int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+       const char *symref = NULL;
  
-       if (S_ISLNK(loginfo.st_mode))
+       if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldref);
  
-       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+       symref = resolve_ref(oldref, orig_sha1, 1, &flag);
+       if (flag & REF_ISSYMREF)
+               return error("refname %s is a symbolic ref, renaming it is not supported",
+                       oldref);
+       if (!symref)
                return error("refname %s not found", oldref);
  
        if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
                return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
                        oldref, strerror(errno));
  
-       if (delete_ref(oldref, orig_sha1)) {
+       if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
                error("unable to delete old %s", oldref);
                goto rollback;
        }
  
-       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
                        if (remove_empty_directories(git_path("%s", newref))) {
                                error("Directory not empty: %s", newref);
                error("unable to lock %s for update", newref);
                goto rollback;
        }
        lock->force_write = 1;
        hashcpy(lock->old_sha1, orig_sha1);
        if (write_ref_sha1(lock, orig_sha1, logmsg)) {