From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:51
A few years ago, this developer was convinced that it was a bad idea to
auto-update working directories when pushing into the current branch, and
that an excellent way to prove this was to implement that feature. To his
surprise, it turned out to be the one thing he misses most in upstream Git.
So here goes: this patch series adds support for two new
receive.denyCurrentBranch settings: one to update the working directory
(which must be clean, i.e. there must not be any uncommitted changes) when
pushing into the current branch, the other setting detaches the HEAD
instead.
The scenario in which in particular the 'updateInstead' setting became a
boon in this developer's daily work is a multi-laptop one, where working
directories need to be updated between computers without a hassle.
Johannes Schindelin (2):
Add a few more values for receive.denyCurrentBranch
Let deny.currentBranch=updateInstead ignore submodules
Documentation/config.txt | 5 +++++
builtin/receive-pack.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 36 ++++++++++++++++++++++++++++++
3 files changed, 97 insertions(+), 2 deletions(-)
--
2.0.0.rc3.9669.g840d1f9
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:51
Under certain circumstances, it makes a *lot* of sense to allow pushing
into the current branch. For example, when two machines with different
Operating Systems are required for testing, it makes much more sense to
synchronize between working directories than having to go via a third
server.
Under different circumstances, the working directory needs to be left
untouched, for example when a bunch of VMs need to be shut down to save
RAM and one needs to push everything out into the host's non-bare
repositories quickly.
This change supports both workflows by offering two new values for the
denyCurrentBranch setting:
'updateInstead':
Update the working tree accordingly, but refuse to do so if there
are any uncommitted changes.
'detachInstead':
Detach the HEAD, thereby keeping currently checked-out revision,
index and working directory unchanged.
Signed-off-by: Johannes Schindelin <redacted>
---
Documentation/config.txt | 5 +++++
builtin/receive-pack.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 36 ++++++++++++++++++++++++++++++
3 files changed, 97 insertions(+), 2 deletions(-)
@@ -2129,6 +2129,11 @@ 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".+++There are two more options: "updateInstead" which will update the working+directory (must be clean) if pushing into the current branch, and+"detachInstead" which will leave the working directory untouched, detaching+the HEAD so it does not need to change. receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is
@@ -730,6 +737,44 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)return0;}+staticvoidmerge_worktree(unsignedchar*sha1)+{+constchar*update_refresh[]={+"update-index","--refresh",NULL+};+constchar*read_tree[]={+"read-tree","-u","-m",sha1_to_hex(sha1),NULL+};+structchild_processchild;+structstrbufgit_env=STRBUF_INIT;+constchar*env[2];++if(is_bare_repository())+die("denyCurrentBranch = updateInstead needs a worktree");++strbuf_addf(&git_env,"GIT_DIR=%s",absolute_path(get_git_dir()));+env[0]=git_env.buf;+env[1]=NULL;++memset(&child,0,sizeof(child));+child.argv=update_refresh;+child.env=env;+child.dir=git_work_tree_cfg?git_work_tree_cfg:"..";+child.stdout_to_stderr=1;+child.git_cmd=1;+if(run_command(&child))+die("Could not refresh the index");++child.argv=read_tree;+child.no_stdin=1;+child.no_stdout=1;+child.stdout_to_stderr=0;+if(run_command(&child))+die("Could not merge working tree with new HEAD. Good luck.");++strbuf_release(&git_env);+}+staticconstchar*update(structcommand*cmd,structshallow_info*si){constchar*name=cmd->ref_name;
@@ -760,6 +805,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)if(deny_current_branch==DENY_UNCONFIGURED)refuse_unconfigured_deny();return"branch is currently checked out";+caseDENY_UPDATE_INSTEAD:+merge_worktree(new_sha1);+break;+caseDENY_DETACH_INSTEAD:+update_ref("push into current branch (detach)","HEAD",+old_sha1,NULL,REF_NODEREF,UPDATE_REFS_DIE_ON_ERR);+break;}}
@@ -788,6 +840,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)refuse_unconfigured_deny_delete_current();rp_error("refusing to delete the current branch: %s",name);return"deletion of the current branch prohibited";+default:+die("Invalid denyDeleteCurrent setting");}}}
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:51
They are not affected by the update anyway.
Signed-off-by: Johannes Schindelin <redacted>
---
builtin/receive-pack.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Jeff King <hidden> Date: 2016-06-15 23:02:53
On Fri, Nov 07, 2014 at 02:58:17PM +0100, Johannes Schindelin wrote:
Under certain circumstances, it makes a *lot* of sense to allow pushing
into the current branch. For example, when two machines with different
Operating Systems are required for testing, it makes much more sense to
synchronize between working directories than having to go via a third
server.
FWIW, I do this without a third server (and without resorting to pull),
with:
host1$ git push host2 master:refs/remotes/host1/master
host2$ git merge host1/master
You can even set up a push refspec to make "git push host2" do the right
thing.
That being said, I do like the premise of your patch, as it eliminates
the extra step on the remote side (which is not that big a deal in
itself, but when you realize that host2 _did_ have some changes on it,
then you end up doing the merge there, when in general I'd prefer to do
all the work on host1 via "git pull").
-Peff
From: brian m. carlson <hidden> Date: 2016-06-15 23:02:53
On Sat, Nov 08, 2014 at 06:18:55AM -0500, Jeff King wrote:
On Fri, Nov 07, 2014 at 02:58:17PM +0100, Johannes Schindelin wrote:
quoted
Under certain circumstances, it makes a *lot* of sense to allow pushing
into the current branch. For example, when two machines with different
Operating Systems are required for testing, it makes much more sense to
synchronize between working directories than having to go via a third
server.
FWIW, I do this without a third server (and without resorting to pull),
with:
host1$ git push host2 master:refs/remotes/host1/master
host2$ git merge host1/master
You can even set up a push refspec to make "git push host2" do the right
thing.
I do something similar, but it's inconvenient when the repo you're
pushing into is $HOME, since you have to type something like "exec zsh
-l" in order to fix things up.
That being said, I do like the premise of your patch, as it eliminates
the extra step on the remote side (which is not that big a deal in
itself, but when you realize that host2 _did_ have some changes on it,
then you end up doing the merge there, when in general I'd prefer to do
all the work on host1 via "git pull").
I agree. This is very useful.
--
brian m. carlson / brian with sandals: Houston, Texas, US
+1 832 623 2791 | http://www.crustytoothpaste.net/~bmc | My opinion only
OpenPGP: RSA v4 4096b: 88AC E9B2 9196 305B A994 7552 F1BA 225C 0223 B187
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:54
Hi Peff,
On Sat, 8 Nov 2014, Jeff King wrote:
On Fri, Nov 07, 2014 at 02:58:17PM +0100, Johannes Schindelin wrote:
quoted
Under certain circumstances, it makes a *lot* of sense to allow pushing
into the current branch. For example, when two machines with different
Operating Systems are required for testing, it makes much more sense to
synchronize between working directories than having to go via a third
server.
FWIW, I do this without a third server (and without resorting to pull),
with:
host1$ git push host2 master:refs/remotes/host1/master
host2$ git merge host1/master
You can even set up a push refspec to make "git push host2" do the right
thing.
That being said, I do like the premise of your patch, as it eliminates
the extra step on the remote side (which is not that big a deal in
itself, but when you realize that host2 _did_ have some changes on it,
then you end up doing the merge there, when in general I'd prefer to do
all the work on host1 via "git pull").
Plus: you have the luxury of working on an OS that makes ssh'ing from
another machine relatively easy. At least if you have the root password on
your machine. Which, I hate to point it out, is not too common a
commodity.
Ciao,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:54
This patch series adds support for two new receive.denyCurrentBranch settings:
one to update the working directory (which must be clean, i.e. there must not
be any uncommitted changes) when pushing into the current branch, the other
setting detaches the HEAD instead.
The scenario in which in particular the 'updateInstead' setting became a
boon in this developer's daily work is when trying to get a bug fix from a
Windows computer, a virtual machine or a user's machine onto his main
machine (in all of those cases it is only possible to connect via ssh in one
direction, but not in the reverse direction).
Interdiff vs v1 below the diffstat.
Johannes Schindelin (2):
Clean stale environment pointer in finish_command()
Add a few more options for receive.denyCurrentBranch
Documentation/config.txt | 9 ++++++++
builtin/receive-pack.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++--
run-command.c | 3 +++
t/t5516-fetch-push.sh | 36 +++++++++++++++++++++++++++++
4 files changed, 106 insertions(+), 2 deletions(-)
@@ -2130,10 +2130,14 @@ receive.denyCurrentBranch:: proceed. If set to false or "ignore", allow such pushes with no message. Defaults to "refuse". +-There are two more options: "updateInstead" which will update the working-directory (must be clean) if pushing into the current branch, and-"detachInstead" which will leave the working directory untouched, detaching-the HEAD so it does not need to change.+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 ssh (e.g. inside a VM).+++Yet another option is "detachInstead" which will detach the HEAD if updates+are pushed into the current branch; That way, the current revision, the+index and the working directory are always left untouched by pushes. receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is
@@ -745,41 +745,36 @@ static void merge_worktree(unsigned char *sha1)constchar*read_tree[]={"read-tree","-u","-m",sha1_to_hex(sha1),NULL};-structchild_processchild;-structstrbufgit_env=STRBUF_INIT;-constchar*env[2];+structchild_processchild=CHILD_PROCESS_INIT;if(is_bare_repository())-die("denyCurrentBranch = updateInstead needs a worktree");--strbuf_addf(&git_env,"GIT_DIR=%s",absolute_path(get_git_dir()));-env[0]=git_env.buf;-env[1]=NULL;+return"denyCurrentBranch = updateInstead needs a worktree";-memset(&child,0,sizeof(child));+argv_array_pushf(&child.env_array,"GIT_DIR=%s",absolute_path(get_git_dir()));child.argv=update_refresh;-child.env=env;child.dir=git_work_tree_cfg?git_work_tree_cfg:"..";child.stdout_to_stderr=1;child.git_cmd=1;if(run_command(&child))-die("Could not refresh the index");+die("Could not refresh the index");+/* finish_command cleared the environment; reinitialize */+argv_array_pushf(&child.env_array,"GIT_DIR=%s",absolute_path(get_git_dir()));child.argv=read_tree;child.no_stdin=1;child.no_stdout=1;child.stdout_to_stderr=0;if(run_command(&child))-die("Could not merge working tree with new HEAD. Good luck.");+die("Could not merge working tree with new HEAD.");-strbuf_release(&git_env);+returnNULL;}staticconstchar*update(structcommand*cmd,structshallow_info*si){constchar*name=cmd->ref_name;structstrbufnamespaced_name_buf=STRBUF_INIT;-constchar*namespaced_name;+constchar*namespaced_name,*ret;unsignedchar*old_sha1=cmd->old_sha1;unsignedchar*new_sha1=cmd->new_sha1;
@@ -806,11 +801,17 @@ static const char *update(struct command *cmd, struct shallow_info *si)refuse_unconfigured_deny();return"branch is currently checked out";caseDENY_UPDATE_INSTEAD:-merge_worktree(new_sha1);+ret=merge_worktree(new_sha1);+if(ret)+returnret;break;caseDENY_DETACH_INSTEAD:-update_ref("push into current branch (detach)","HEAD",-old_sha1,NULL,REF_NODEREF,UPDATE_REFS_DIE_ON_ERR);+ret=update_ref("push into current branch (detach)",+"HEAD",old_sha1,NULL,REF_NODEREF,+UPDATE_REFS_DIE_ON_ERR)?+"Could not detach HEAD":NULL;+if(ret)+returnret;break;}}
@@ -555,6 +555,9 @@ int finish_command(struct child_process *cmd){intret=wait_or_whine(cmd->pid,cmd->argv[0]);argv_array_clear(&cmd->args);+/* Avoid pointing to a stale environment */+if(cmd->env==cmd->env_array.argv)+cmd->env=NULL;argv_array_clear(&cmd->env_array);returnret;}
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:55
This patch series adds support for a new receive.denyCurrentBranch setting
to update the working directory (which must be clean, i.e. there must not be
any uncommitted changes) when pushing into the current branch.
The scenario in which the 'updateInstead' setting became a boon in this
developer's daily work is when trying to get a bug fix from a Windows
computer, a virtual machine or a user's machine onto his main machine (in
all of those cases it is only possible to connect via ssh in one direction,
but not in the reverse direction).
Interdiff vs v2 below the diffstat.
Johannes Schindelin (1):
Add another option for receive.denyCurrentBranch
Documentation/config.txt | 5 ++++
builtin/receive-pack.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 17 +++++++++++
3 files changed, 98 insertions(+), 2 deletions(-)
@@ -2134,10 +2134,6 @@ 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 ssh (e.g. inside a VM).-+-Yet another option is "detachInstead" which will detach the HEAD if updates-are pushed into the current branch; That way, the current revision, the-index and the working directory are always left untouched by pushes. receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is
@@ -737,36 +733,66 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)return0;}-staticconstchar*merge_worktree(unsignedchar*sha1)+staticconstchar*update_worktree(unsignedchar*sha1){constchar*update_refresh[]={"update-index","--ignore-submodules","--refresh",NULL};+constchar*diff_index[]={+"diff-index","--quiet","--cached","--ignore-submodules",+"HEAD","--",NULL+};constchar*read_tree[]={"read-tree","-u","-m",sha1_to_hex(sha1),NULL};+constchar*work_tree=git_work_tree_cfg?git_work_tree_cfg:"..";+structargv_arrayenv=ARGV_ARRAY_INIT;structchild_processchild=CHILD_PROCESS_INIT;if(is_bare_repository())return"denyCurrentBranch = updateInstead needs a worktree";-argv_array_pushf(&child.env_array,"GIT_DIR=%s",absolute_path(get_git_dir()));+argv_array_pushf(&env,"GIT_DIR=%s",absolute_path(get_git_dir()));+child.argv=update_refresh;-child.dir=git_work_tree_cfg?git_work_tree_cfg:"..";+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))-die("Could not refresh the index");+if(run_command(&child)){+argv_array_clear(&env);+return"Up-to-date check failed";+}-/* finish_command cleared the environment; reinitialize */-argv_array_pushf(&child.env_array,"GIT_DIR=%s",absolute_path(get_git_dir()));+/* run_command() does not clean up completely; reinitialize */+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 not clean";+}++/* run_command() does not clean up completely; reinitialize */+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;-if(run_command(&child))-die("Could not merge working tree with new HEAD.");+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);returnNULL;}
@@ -801,15 +827,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)refuse_unconfigured_deny();return"branch is currently checked out";caseDENY_UPDATE_INSTEAD:-ret=merge_worktree(new_sha1);-if(ret)-returnret;-break;-caseDENY_DETACH_INSTEAD:-ret=update_ref("push into current branch (detach)",-"HEAD",old_sha1,NULL,REF_NODEREF,-UPDATE_REFS_DIE_ON_ERR)?-"Could not detach HEAD":NULL;+ret=update_worktree(new_sha1);if(ret)returnret;break;
@@ -837,12 +855,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)break;caseDENY_REFUSE:caseDENY_UNCONFIGURED:+caseDENY_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:-die("Invalid denyDeleteCurrent setting");+return"Invalid denyDeleteCurrent setting";}}}
@@ -555,9 +555,6 @@ int finish_command(struct child_process *cmd){intret=wait_or_whine(cmd->pid,cmd->argv[0]);argv_array_clear(&cmd->args);-/* Avoid pointing to a stale environment */-if(cmd->env==cmd->env_array.argv)-cmd->env=NULL;argv_array_clear(&cmd->env_array);returnret;}
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:02:55
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 <redacted>
---
Documentation/config.txt | 5 ++++
builtin/receive-pack.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 17 +++++++++++
3 files changed, 98 insertions(+), 2 deletions(-)
@@ -2129,6 +2129,11 @@ 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 ssh (e.g. inside a VM). receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is
@@ -730,11 +733,74 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)return0;}+staticconstchar*update_worktree(unsignedchar*sha1)+{+constchar*update_refresh[]={+"update-index","--ignore-submodules","--refresh",NULL+};+constchar*diff_index[]={+"diff-index","--quiet","--cached","--ignore-submodules",+"HEAD","--",NULL+};+constchar*read_tree[]={+"read-tree","-u","-m",sha1_to_hex(sha1),NULL+};+constchar*work_tree=git_work_tree_cfg?git_work_tree_cfg:"..";+structargv_arrayenv=ARGV_ARRAY_INIT;+structchild_processchild=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_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 not clean";+}++/* run_command() does not clean up completely; reinitialize */+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);+returnNULL;+}+staticconstchar*update(structcommand*cmd,structshallow_info*si){constchar*name=cmd->ref_name;structstrbufnamespaced_name_buf=STRBUF_INIT;-constchar*namespaced_name;+constchar*namespaced_name,*ret;unsignedchar*old_sha1=cmd->old_sha1;unsignedchar*new_sha1=cmd->new_sha1;
@@ -760,6 +826,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)if(deny_current_branch==DENY_UNCONFIGURED)refuse_unconfigured_deny();return"branch is currently checked out";+caseDENY_UPDATE_INSTEAD:+ret=update_worktree(new_sha1);+if(ret)+returnret;+break;}}
@@ -784,10 +855,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)break;caseDENY_REFUSE:caseDENY_UNCONFIGURED:+caseDENY_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";}}}
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:03:07
This patch "series" adds support for a new receive.denyCurrentBranch setting
to update the working directory (which must be clean, i.e. there must not be
any uncommitted changes) when pushing into the current branch.
The scenario in which the 'updateInstead' setting became a boon in this
developer's daily work is when trying to get a bug fix from a Windows
computer, a virtual machine, or when getting a bug fix from a user's machine
onto his main machine (in all of those cases it is only possible to connect
via ssh in one direction, but not in the reverse direction). It also comes
in handy when updating a live web site via push (in which case a clean
working directory is an absolute must).
As to the name 'updateInstead': since I do not want the option to perform
the equivalent of a checkout (where staged changes would be okay), I stuck
to the name I use in all of my $HOME/.gitconfigs. Hopefully you don't
mind.
Interdiff vs v3 below the diffstat
Johannes Schindelin (1):
Add another option for receive.denyCurrentBranch
Documentation/config.txt | 7 ++++
builtin/receive-pack.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 21 +++++++++++
3 files changed, 120 insertions(+), 2 deletions(-)
@@ -2133,7 +2133,9 @@ receive.denyCurrentBranch:: 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 ssh (e.g. inside a VM).+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
@@ -767,6 +770,19 @@ static const char *update_worktree(unsigned char *sha1)/* 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 not clean";+}++/* run_command() does not clean up completely; reinitialize */+child_process_init(&child);child.argv=diff_index;child.env=env.argv;child.no_stdin=1;
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:03:07
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 <redacted>
---
Documentation/config.txt | 7 ++++
builtin/receive-pack.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 21 +++++++++++
3 files changed, 120 insertions(+), 2 deletions(-)
@@ -2129,6 +2129,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
@@ -730,11 +733,90 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)return0;}+staticconstchar*update_worktree(unsignedchar*sha1)+{+constchar*update_refresh[]={+"update-index","--ignore-submodules","--refresh","-q",NULL+};+constchar*diff_files[]={+"diff-files","--quiet","--ignore-submodules","--",NULL+};+constchar*diff_index[]={+"diff-index","--quiet","--cached","--ignore-submodules",+"HEAD","--",NULL+};+constchar*read_tree[]={+"read-tree","-u","-m",sha1_to_hex(sha1),NULL+};+constchar*work_tree=git_work_tree_cfg?git_work_tree_cfg:"..";+structargv_arrayenv=ARGV_ARRAY_INIT;+structchild_processchild=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 not clean";+}++/* run_command() does not clean up completely; reinitialize */+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 not clean";+}++/* run_command() does not clean up completely; reinitialize */+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);+returnNULL;+}+staticconstchar*update(structcommand*cmd,structshallow_info*si){constchar*name=cmd->ref_name;structstrbufnamespaced_name_buf=STRBUF_INIT;-constchar*namespaced_name;+constchar*namespaced_name,*ret;unsignedchar*old_sha1=cmd->old_sha1;unsignedchar*new_sha1=cmd->new_sha1;
@@ -760,6 +842,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)if(deny_current_branch==DENY_UNCONFIGURED)refuse_unconfigured_deny();return"branch is currently checked out";+caseDENY_UPDATE_INSTEAD:+ret=update_worktree(new_sha1);+if(ret)+returnret;+break;}}
@@ -784,10 +871,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)break;caseDENY_REFUSE:caseDENY_UNCONFIGURED:+caseDENY_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";}}}
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:03:07
This patch series adds support for a new receive.denyCurrentBranch setting
to update the working directory (which must be clean, i.e. there must not be
any uncommitted changes) when pushing into the current branch.
The scenario in which the 'updateInstead' setting became a boon in this
developer's daily work is when trying to get a bug fix from a Windows
computer, a virtual machine, or when getting a bug fix from a user's machine
onto his main machine (in all of those cases it is only possible to connect
via ssh in one direction, but not in the reverse direction). It also comes
in handy when updating a live web site via push (in which case a clean
working directory is an absolute must).
Interdiff vs v4 below diffstat.
Johannes Schindelin (1):
Add another option for receive.denyCurrentBranch
Documentation/config.txt | 7 ++++
builtin/receive-pack.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 26 ++++++++++++++
3 files changed, 124 insertions(+), 2 deletions(-)
@@ -778,10 +778,9 @@ static const char *update_worktree(unsigned char *sha1)child.git_cmd=1;if(run_command(&child)){argv_array_clear(&env);-return"Working directory not clean";+return"Working directory has unstaged changes";}-/* run_command() does not clean up completely; reinitialize */child_process_init(&child);child.argv=diff_index;child.env=env.argv;
@@ -791,10 +790,10 @@ static const char *update_worktree(unsigned char *sha1)child.git_cmd=1;if(run_command(&child)){argv_array_clear(&env);-return"Working directory not clean";+return"Working directory has staged changes";}-/* run_command() does not clean up completely; reinitialize */+read_tree[3]=sha1_to_hex(sha1);child_process_init(&child);child.argv=read_tree;child.env=env.argv;
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:03:07
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 <redacted>
---
Documentation/config.txt | 7 ++++
builtin/receive-pack.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t5516-fetch-push.sh | 26 ++++++++++++++
3 files changed, 124 insertions(+), 2 deletions(-)
@@ -2129,6 +2129,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
@@ -730,11 +733,89 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)return0;}+staticconstchar*update_worktree(unsignedchar*sha1)+{+constchar*update_refresh[]={+"update-index","-q","--ignore-submodules","--refresh",NULL+};+constchar*diff_files[]={+"diff-files","--quiet","--ignore-submodules","--",NULL+};+constchar*diff_index[]={+"diff-index","--quiet","--cached","--ignore-submodules",+"HEAD","--",NULL+};+constchar*read_tree[]={+"read-tree","-u","-m",NULL,NULL+};+constchar*work_tree=git_work_tree_cfg?git_work_tree_cfg:"..";+structargv_arrayenv=ARGV_ARRAY_INIT;+structchild_processchild=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);+returnNULL;+}+staticconstchar*update(structcommand*cmd,structshallow_info*si){constchar*name=cmd->ref_name;structstrbufnamespaced_name_buf=STRBUF_INIT;-constchar*namespaced_name;+constchar*namespaced_name,*ret;unsignedchar*old_sha1=cmd->old_sha1;unsignedchar*new_sha1=cmd->new_sha1;
@@ -760,6 +841,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)if(deny_current_branch==DENY_UNCONFIGURED)refuse_unconfigured_deny();return"branch is currently checked out";+caseDENY_UPDATE_INSTEAD:+ret=update_worktree(new_sha1);+if(ret)+returnret;+break;}}
@@ -784,10 +870,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)break;caseDENY_REFUSE:caseDENY_UNCONFIGURED:+caseDENY_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";}}}
From: Junio C Hamano <hidden> Date: 2016-06-15 23:03:09
Thanks, will queue.
I think we would need a bit more tests to protect the feature from
future changes, if you care about the cleanliness requirement of
this feature which is a lot stricter than that of "git checkout".
Perhaps like this one on top.
-- >8 --
From: Junio C Hamano <redacted>
Date: Sun, 30 Nov 2014 17:54:30 -0800
Subject: [PATCH] t5516: more tests for receive.denyCurrentBranch=updateInstead
The previous one tests only the case where a path to be updated by
the push-to-deploy has an incompatible change in the target's
working tree that has already been added to the index, but the
feature itself wants to require the working tree to be a lot cleaner
than what is tested. Add a handful more tests to protect the
feature from future changes that mistakenly (from the viewpoint of
the inventor of the feature) loosens the cleanliness requirement,
namely:
- A change only to the working tree but not to the index is still a
change to be protected;
- An untracked file in the working tree that would be overwritten
by a push-to-deploy needs to be protected;
- A change that happens to make a file identical to what is being
pushed is still a change to be protected (i.e. the feature's
cleanliness requirement is more strict than that of checkout).
Also, test that a stat-only change to the working tree is not a
reason to reject a push-to-deploy.
Signed-off-by: Junio C Hamano <redacted>
---
t/t5516-fetch-push.sh | 96 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 87 insertions(+), 9 deletions(-)
@@ -1332,28 +1332,106 @@ test_expect_success 'fetch into bare respects core.logallrefupdates' ' test_expect_success'receive.denyCurrentBranch = updateInstead''gitpushtestrepomaster&&-(cdtestrepo&&+(+cdtestrepo&&gitreset--hard&&gitconfigreceive.denyCurrentBranchupdateInstead)&&test_committhirdpath2&&++# Try pushing into a repository with pristine working treegitpushtestrepomaster&&-test$(gitrev-parseHEAD)=$(cdtestrepo&&gitrev-parseHEAD)&&-testthird="$(cattestrepo/path2)"&&-(cdtestrepo&&+(+cdtestrepo&&+gitupdate-index-q--refresh&&+gitdiff-files--quiet--&&+gitdiff-index--quiet--cachedHEAD--&&+testthird="$(catpath2)"&&+test$(git-C..rev-parseHEAD)=$(gitrev-parseHEAD)+)&&++# Try pushing into a repository with working tree needing a refresh+(+cdtestrepo&&+gitreset--hardHEAD^&&+test$(git-C..rev-parseHEAD^)=$(gitrev-parseHEAD)&&+test-chmtime+100path1+)&&+gitpushtestrepomaster&&+(+cdtestrepo&&gitupdate-index-q--refresh&&gitdiff-files--quiet--&&gitdiff-index--quiet--cachedHEAD--&&-echochanged>path2&&-gitaddpath2+test_cmp../path1path1&&+testthird="$(catpath2)"&&+test$(git-C..rev-parseHEAD)=$(gitrev-parseHEAD))&&++# Update what is to be pushedtest_commitfourthpath2&&++# Try pushing into a repository with a dirty working tree+# (1) the working tree updated+(+cdtestrepo&&+echochanged>path1+)&&test_must_failgitpushtestrepomaster&&-test$(gitrev-parseHEAD^)=$(git-Ctestreporev-parseHEAD)&&-(cdtestrepo&&+(+cdtestrepo&&+test$(git-C..rev-parseHEAD^)=$(gitrev-parseHEAD)&&+gitdiff--quiet--cached&&+testchanged="$(catpath1)"+)&&++# (2) the index updated+(+cdtestrepo&&+echochanged>path1&&+gitaddpath1+)&&+test_must_failgitpushtestrepomaster&&+(+cdtestrepo&&+test$(git-C..rev-parseHEAD^)=$(gitrev-parseHEAD)&&+gitdiff--quiet&&+testchanged="$(catpath1)"+)&&++# Introduce a new file in the update+test_commitfifthpath3&&++# (3) the working tree has an untracked file that would interfere+(+cdtestrepo&&+gitreset--hard&&+echochanged>path3+)&&+test_must_failgitpushtestrepomaster&&+(+cdtestrepo&&+test$(git-C..rev-parseHEAD^^)=$(gitrev-parseHEAD)&&+gitdiff--quiet&&+gitdiff--quiet--cached&&+testchanged="$(catpath3)"+)&&++# (4) the target changes to what gets pushed but it still is a change+(+cdtestrepo&&+gitreset--hard&&+echofifth>path3&&+gitaddpath3+)&&+test_must_failgitpushtestrepomaster&&+(+cdtestrepo&&+test$(git-C..rev-parseHEAD^^)=$(gitrev-parseHEAD)&&gitdiff--quiet&&-testchanged="$(catpath2)"+testfifth="$(catpath3)")+' test_done
From: Johannes Schindelin <hidden> Date: 2016-06-15 23:03:09
Hi Junio,
On Sun, 30 Nov 2014, Junio C Hamano wrote:
Thanks, will queue.
Thanks!
I think we would need a bit more tests to protect the feature from
future changes, if you care about the cleanliness requirement of
this feature which is a lot stricter than that of "git checkout".
Perhaps like this one on top.