Merge branch 'js/push-to-deploy'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:27:03 +0000 (12:27 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:27:04 +0000 (12:27 -0800)
"git push" into a repository with a working tree normally refuses
to modify the branch that is checked out. The command learned to
optionally do an equivalent of "git reset --hard" only when there
is no change to the working tree and the index instead, which would
be useful to "deploy" by pushing into a repository.

* js/push-to-deploy:
t5516: more tests for receive.denyCurrentBranch=updateInstead
receive-pack: add another option for receive.denyCurrentBranch

1  2 
Documentation/config.txt
builtin/receive-pack.c
diff --combined Documentation/config.txt
index b563ea8d3477aa686d332a321d684f6659e9fe06,6a4c19e775978fe6cac4922ab0687b88ded13e99..ac781a44d733f672d5b88281a8bea48bc25be226
@@@ -246,17 -246,6 +246,17 @@@ core.precomposeunicode:
        When false, file names are handled fully transparent by Git,
        which is backward compatible with older versions of Git.
  
 +core.protectHFS::
 +      If set to true, do not allow checkout of paths that would
 +      be considered equivalent to `.git` on an HFS+ filesystem.
 +      Defaults to `true` on Mac OS, and `false` elsewhere.
 +
 +core.protectNTFS::
 +      If set to true, do not allow checkout of paths that would
 +      cause problems with the NTFS filesystem, e.g. conflict with
 +      8.3 "short" names.
 +      Defaults to `true` on Windows, and `false` elsewhere.
 +
  core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
@@@ -694,7 -683,7 +694,7 @@@ alias.*:
        confusion and troubles with script usage, aliases that
        hide existing Git commands are ignored. Arguments are split by
        spaces, the usual shell quoting and escaping is supported.
 -      quote pair and a backslash can be used to quote them.
 +      A quote pair or a backslash can be used to quote them.
  +
  If the alias expansion is prefixed with an exclamation point,
  it will be treated as a shell command.  For example, defining
@@@ -850,10 -839,6 +850,10 @@@ accepted are `normal`, `black`, `red`, 
  `blink` and `reverse`.  The first color given is the foreground; the
  second is the background.  The position of the attribute, if any,
  doesn't matter.
 ++
 +Colors (foreground and background) may also be given as numbers between
 +0 and 255; these use ANSI 256-color mode (but note that not all
 +terminals may support this).
  
  color.diff::
        Whether to use ANSI escape sequences to add color to patches.
@@@ -2144,6 -2129,13 +2144,13 @@@ receive.denyCurrentBranch:
        print a warning of such a push to stderr, but allow the push to
        proceed. If set to false or "ignore", allow such pushes with no
        message. Defaults to "refuse".
+ +
+ Another option is "updateInstead" which will update the working
+ directory (must be clean) if pushing into the current branch. This option is
+ intended for synchronizing working directories when one side is not easily
+ accessible via interactive ssh (e.g. a live web site, hence the requirement
+ that the working directory be clean). This mode also comes in handy when
+ developing inside a VM to test and fix code on different Operating Systems.
  
  receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
@@@ -2318,7 -2310,6 +2325,7 @@@ sendemail.smtpserverport:
  sendemail.smtpserveroption::
  sendemail.smtpuser::
  sendemail.thread::
 +sendemail.transferencoding::
  sendemail.validate::
        See linkgit:git-send-email[1] for description.
  
@@@ -2355,7 -2346,7 +2362,7 @@@ status.showUntrackedFiles:
        files which are not currently tracked by Git. Directories which
        contain only untracked files, are shown with the directory name
        only. Showing untracked files means that Git needs to lstat() all
 -      all the files in the whole repository, which might be slow on some
 +      the files in the whole repository, which might be slow on some
        systems. So, this variable controls how the commands displays
        the untracked files. Possible values are:
  +
diff --combined builtin/receive-pack.c
index 1eefaddac883399b465dde1601ffbdfc579312c1,c04741878de456dbb978a57ab04e966a7cf20d44..8266c1fccf0c0b5908c14a726d443dec36a22d58
@@@ -26,7 -26,8 +26,8 @@@ enum deny_action 
        DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
-       DENY_REFUSE
+       DENY_REFUSE,
+       DENY_UPDATE_INSTEAD
  };
  
  static int deny_deletes;
@@@ -76,6 -77,8 +77,8 @@@ static enum deny_action parse_deny_acti
                        return DENY_WARN;
                if (!strcasecmp(value, "refuse"))
                        return DENY_REFUSE;
+               if (!strcasecmp(value, "updateinstead"))
+                       return DENY_UPDATE_INSTEAD;
        }
        if (git_config_bool(var, value))
                return DENY_REFUSE;
@@@ -431,7 -434,7 +434,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)
@@@ -730,11 -733,89 +733,89 @@@ static int update_shallow_ref(struct co
        return 0;
  }
  
+ static const char *update_worktree(unsigned char *sha1)
+ {
+       const char *update_refresh[] = {
+               "update-index", "-q", "--ignore-submodules", "--refresh", NULL
+       };
+       const char *diff_files[] = {
+               "diff-files", "--quiet", "--ignore-submodules", "--", NULL
+       };
+       const char *diff_index[] = {
+               "diff-index", "--quiet", "--cached", "--ignore-submodules",
+               "HEAD", "--", NULL
+       };
+       const char *read_tree[] = {
+               "read-tree", "-u", "-m", NULL, NULL
+       };
+       const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
+       struct argv_array env = ARGV_ARRAY_INIT;
+       struct child_process child = CHILD_PROCESS_INIT;
+       if (is_bare_repository())
+               return "denyCurrentBranch = updateInstead needs a worktree";
+       argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+       child.argv = update_refresh;
+       child.env = env.argv;
+       child.dir = work_tree;
+       child.no_stdin = 1;
+       child.stdout_to_stderr = 1;
+       child.git_cmd = 1;
+       if (run_command(&child)) {
+               argv_array_clear(&env);
+               return "Up-to-date check failed";
+       }
+       /* run_command() does not clean up completely; reinitialize */
+       child_process_init(&child);
+       child.argv = diff_files;
+       child.env = env.argv;
+       child.dir = work_tree;
+       child.no_stdin = 1;
+       child.stdout_to_stderr = 1;
+       child.git_cmd = 1;
+       if (run_command(&child)) {
+               argv_array_clear(&env);
+               return "Working directory has unstaged changes";
+       }
+       child_process_init(&child);
+       child.argv = diff_index;
+       child.env = env.argv;
+       child.no_stdin = 1;
+       child.no_stdout = 1;
+       child.stdout_to_stderr = 0;
+       child.git_cmd = 1;
+       if (run_command(&child)) {
+               argv_array_clear(&env);
+               return "Working directory has staged changes";
+       }
+       read_tree[3] = sha1_to_hex(sha1);
+       child_process_init(&child);
+       child.argv = read_tree;
+       child.env = env.argv;
+       child.dir = work_tree;
+       child.no_stdin = 1;
+       child.no_stdout = 1;
+       child.stdout_to_stderr = 0;
+       child.git_cmd = 1;
+       if (run_command(&child)) {
+               argv_array_clear(&env);
+               return "Could not update working tree to new HEAD";
+       }
+       argv_array_clear(&env);
+       return NULL;
+ }
  static const char *update(struct command *cmd, struct shallow_info *si)
  {
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
-       const char *namespaced_name;
+       const char *namespaced_name, *ret;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
  
                        if (deny_current_branch == DENY_UNCONFIGURED)
                                refuse_unconfigured_deny();
                        return "branch is currently checked out";
+               case DENY_UPDATE_INSTEAD:
+                       ret = update_worktree(new_sha1);
+                       if (ret)
+                               return ret;
+                       break;
                }
        }
  
                                break;
                        case DENY_REFUSE:
                        case DENY_UNCONFIGURED:
+                       case DENY_UPDATE_INSTEAD:
                                if (deny_delete_current == DENY_UNCONFIGURED)
                                        refuse_unconfigured_deny_delete_current();
                                rp_error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
+                       default:
+                               return "Invalid denyDeleteCurrent setting";
                        }
                }
        }
@@@ -964,7 -1053,7 +1053,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)