receive-pack: add another option for receive.denyCurrentBranch
When synchronizing between working directories, it can be handy to update the current branch via 'push' rather than 'pull', e.g. when pushing a fix from inside a VM, or when pushing a fix made on a user's machine (where the developer is not at liberty to install an ssh daemon let alone know the user's password). The common workaround – pushing into a temporary branch and then merging on the other machine – is no longer necessary with this patch. The new option is: 'updateInstead': Update the working tree accordingly, but refuse to do so if there are any uncommitted changes. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Johannes Schindelin committed
Nov 26, 2014 at 23:44 UTC
1404bcbb6b3bdb248d32024430644e55faec91ce
3 files changed
+124
-2
Documentation/config.txt
+7
@@ -2129,6 +2129,13 @@ receive.denyCurrentBranch::
2129
print a warning of such a push to stderr, but allow the push to
2130
proceed. If set to false or "ignore", allow such pushes with no
2131
message. Defaults to "refuse".
2132
++
2133
+Another option is "updateInstead" which will update the working
2134
+directory (must be clean) if pushing into the current branch. This option is
2135
+intended for synchronizing working directories when one side is not easily
2136
+accessible via interactive ssh (e.g. a live web site, hence the requirement
2137
+that the working directory be clean). This mode also comes in handy when
2138
+developing inside a VM to test and fix code on different Operating Systems.
2139
2140
receive.denyNonFastForwards::
2141
If set to true, git-receive-pack will deny a ref update which is
builtin/receive-pack.c
+91
-2
@@ -26,7 +26,8 @@ enum deny_action {
26
DENY_UNCONFIGURED,
27
DENY_IGNORE,
28
DENY_WARN,
29
- DENY_REFUSE
29
+ DENY_REFUSE,
30
+ DENY_UPDATE_INSTEAD
31
};
32
33
static int deny_deletes;
@@ -76,6 +77,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
77
return DENY_WARN;
78
if (!strcasecmp(value, "refuse"))
79
return DENY_REFUSE;
80
+ if (!strcasecmp(value, "updateinstead"))
81
+ return DENY_UPDATE_INSTEAD;
82
}
83
if (git_config_bool(var, value))
84
return DENY_REFUSE;
@@ -730,11 +733,89 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
733
return 0;
734
}
735
736
+static const char *update_worktree(unsigned char *sha1)
737
+{
738
+ const char *update_refresh[] = {
739
+ "update-index", "-q", "--ignore-submodules", "--refresh", NULL
740
+ };
741
+ const char *diff_files[] = {
742
+ "diff-files", "--quiet", "--ignore-submodules", "--", NULL
743
+ };
744
+ const char *diff_index[] = {
745
+ "diff-index", "--quiet", "--cached", "--ignore-submodules",
746
+ "HEAD", "--", NULL
747
+ };
748
+ const char *read_tree[] = {
749
+ "read-tree", "-u", "-m", NULL, NULL
750
+ };
751
+ const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
752
+ struct argv_array env = ARGV_ARRAY_INIT;
753
+ struct child_process child = CHILD_PROCESS_INIT;
754
+
755
+ if (is_bare_repository())
756
+ return "denyCurrentBranch = updateInstead needs a worktree";
757
+
758
+ argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
759
+
760
+ child.argv = update_refresh;
761
+ child.env = env.argv;
762
+ child.dir = work_tree;
763
+ child.no_stdin = 1;
764
+ child.stdout_to_stderr = 1;
765
+ child.git_cmd = 1;
766
+ if (run_command(&child)) {
767
+ argv_array_clear(&env);
768
+ return "Up-to-date check failed";
769
+ }
770
+
771
+ /* run_command() does not clean up completely; reinitialize */
772
+ child_process_init(&child);
773
+ child.argv = diff_files;
774
+ child.env = env.argv;
775
+ child.dir = work_tree;
776
+ child.no_stdin = 1;
777
+ child.stdout_to_stderr = 1;
778
+ child.git_cmd = 1;
779
+ if (run_command(&child)) {
780
+ argv_array_clear(&env);
781
+ return "Working directory has unstaged changes";
782
+ }
783
+
784
+ child_process_init(&child);
785
+ child.argv = diff_index;
786
+ child.env = env.argv;
787
+ child.no_stdin = 1;
788
+ child.no_stdout = 1;
789
+ child.stdout_to_stderr = 0;
790
+ child.git_cmd = 1;
791
+ if (run_command(&child)) {
792
+ argv_array_clear(&env);
793
+ return "Working directory has staged changes";
794
+ }
795
+
796
+ read_tree[3] = sha1_to_hex(sha1);
797
+ child_process_init(&child);
798
+ child.argv = read_tree;
799
+ child.env = env.argv;
800
+ child.dir = work_tree;
801
+ child.no_stdin = 1;
802
+ child.no_stdout = 1;
803
+ child.stdout_to_stderr = 0;
804
+ child.git_cmd = 1;
805
+ if (run_command(&child)) {
806
+ argv_array_clear(&env);
807
+ return "Could not update working tree to new HEAD";
808
+ }
809
+
810
+ argv_array_clear(&env);
811
+ return NULL;
812
+}
813
+
814
static const char *update(struct command *cmd, struct shallow_info *si)
815
{
816
const char *name = cmd->ref_name;
817
struct strbuf namespaced_name_buf = STRBUF_INIT;
737
- const char *namespaced_name;
818
+ const char *namespaced_name, *ret;
819
unsigned char *old_sha1 = cmd->old_sha1;
820
unsigned char *new_sha1 = cmd->new_sha1;
821
@@ -760,6 +841,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
841
if (deny_current_branch == DENY_UNCONFIGURED)
842
refuse_unconfigured_deny();
843
return "branch is currently checked out";
844
+ case DENY_UPDATE_INSTEAD:
845
+ ret = update_worktree(new_sha1);
846
+ if (ret)
847
+ return ret;
848
+ break;
849
}
850
}
851
@@ -784,10 +870,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
870
break;
871
case DENY_REFUSE:
872
case DENY_UNCONFIGURED:
873
+ case DENY_UPDATE_INSTEAD:
874
if (deny_delete_current == DENY_UNCONFIGURED)
875
refuse_unconfigured_deny_delete_current();
876
rp_error("refusing to delete the current branch: %s", name);
877
return "deletion of the current branch prohibited";
878
+ default:
879
+ return "Invalid denyDeleteCurrent setting";
880
}
881
}
882
}
t/t5516-fetch-push.sh
+26
@@ -1330,4 +1330,30 @@ test_expect_success 'fetch into bare respects core.logallrefupdates' '
1330
)
1331
'
1332
1333
+test_expect_success 'receive.denyCurrentBranch = updateInstead' '
1334
+ git push testrepo master &&
1335
+ (cd testrepo &&
1336
+ git reset --hard &&
1337
+ git config receive.denyCurrentBranch updateInstead
1338
+ ) &&
1339
+ test_commit third path2 &&
1340
+ git push testrepo master &&
1341
+ test $(git rev-parse HEAD) = $(cd testrepo && git rev-parse HEAD) &&
1342
+ test third = "$(cat testrepo/path2)" &&
1343
+ (cd testrepo &&
1344
+ git update-index -q --refresh &&
1345
+ git diff-files --quiet -- &&
1346
+ git diff-index --quiet --cached HEAD -- &&
1347
+ echo changed >path2 &&
1348
+ git add path2
1349
+ ) &&
1350
+ test_commit fourth path2 &&
1351
+ test_must_fail git push testrepo master &&
1352
+ test $(git rev-parse HEAD^) = $(git -C testrepo rev-parse HEAD) &&
1353
+ (cd testrepo &&
1354
+ git diff --quiet &&
1355
+ test changed = "$(cat path2)"
1356
+ )
1357
+'
1358
+
1359
test_done