threads / discuss / 17644

receive.denyCurrentBranch

Subject: receive.denyCurrentBranch

## tl;dr

15 messages between Feb 8, 2009 and Feb 9, 2009.

replies: 14people: 6as markdown or json

George Spelvin· Feb 8, 2009, 04:29 UTC · lore
(Prompted by the 1.6.2-rc0 announcement.)

Myself, I always thought the obvious way to handle this problem was to detach the HEAD. Is there a reson there's no "detach" option for this?

Junio C Hamano· Feb 8, 2009, 06:43 UTC · re: George Spelvin · lore

Re: receive.denyCurrentBranch

"George Spelvin" <linux@horizon.com> writes:
> (Prompted by the 1.6.2-rc0 announcement.)
>
> Myself, I always thought the obvious way to handle this problem was to
> detach the HEAD.  Is there a reson there's no "detach" option for this?

Let's first think about why pushing into the current branch with the traditional behaviour is bad.

If you are in control of the remote whose current branch was checked out when push was made, there are two cases:

 (1) You do not know what you are doing.  The first time you do "git diff
     HEAD", or perhaps "git status", you suddenly see a lot of reverts,
     sctach your head and cry for help;
     If you set 'detach' option, this clueless user is not helped; he will
     happily keep working and would make tons of commits on detached HEAD,
     and next time he switches to another branch, will lose all of them.
     In a sense, this is a lot worse than the current behaviour, because
     you will do a lot more damage than just a single commit that has many
     uninteniontal reverts as what currently happens---which can be sorted
     out.
 (2) You know what you are doing.  You'd say:
	: detach HEAD at the commit before the push updated the branch
	$ git checkout @{1}  
	$ work work work
        $ git commit
     to detach the HEAD, finish the work you were doing in the pushed-into
     repository and then:
	: check out the previous branch (i.e. go back)
	$ git checkout @{-1}
	: merge the last commit, i.e. the work you done on detached HEAD
        $ git merge @{1}
    If you allow 'detach' option, then you saved him from typing "git
    checkout @{1}".  Not a big gain, especially if you compare this with
    increased pain you are causing to people in category (1).

The case where the pushed-into repository is not yours was discussed previously by Jeff King in the second thread below.

Previous discussions.
    http://thread.gmane.org/gmane.comp.version-control.git/35770/focus=36291
    http://thread.gmane.org/gmane.comp.version-control.git/100339/focus=100731
Johannes Schindelin· Feb 8, 2009, 10:30 UTC · re: George Spelvin · lore

Re: receive.denyCurrentBranch

Hi,
On Sat, 7 Feb 2009, George Spelvin wrote:
> (Prompted by the 1.6.2-rc0 announcement.)
> 
> Myself, I always thought the obvious way to handle this problem was to
> detach the HEAD.  Is there a reson there's no "detach" option for this?
It's insane, that's what it is.

You have _no_ business meddling with a remote working directory, _except_ by logging into that machine and working on that working directory _directly_.

If you do not agree, you have not thought about the implications, i.e. what problems you buy.

Ciao, Dscho

Jay Soffian· Feb 8, 2009, 17:50 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

On Sun, Feb 8, 2009 at 5:30 AM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 8 quoted lines
> It's insane, that's what it is.
>
> You have _no_ business meddling with a remote working directory, _except_
> by logging into that machine and working on that working directory
> _directly_.
>
> If you do not agree, you have not thought about the implications, i.e.
> what problems you buy.

Just by way of providing an additional perspective, I thought I'd illustrate how Mercurial handles this situation:

$ mkdir repoA $ cd repoA repoA$ hg init repoA$ echo foo > foo repoA$ hg add foo repoA$ hg commit -m "added foo" repoA$ cd .. $ hg clone repoA repoB updating working directory 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd repoA repoA$ cat >> .hg/hgrc <<EOF

> [paths]
> default = ../repoB
> EOF
repoA$ echo bar > bar
repoA$ hg add bar
repoA$ hg commit -m "added bar"
repoA$ hg push
pushing to /Users/jay/repoB
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
repoA$ cd ../repoB
repoB$ hg parents
0   d32c3edefcba   2009-02-08 11:18 -0500   jaysoffian
  added foo
repoB$ hg glog
o  1[tip]   8e0a16e691f4   2009-02-08 11:19 -0500   jaysoffian
|    added bar
|
@  0   d32c3edefcba   2009-02-08 11:18 -0500   jaysoffian
     added foo
repoB$ echo baz > baz
repoB$ hg add baz
repoB$ hg commit -m "added baz"
created new head
repoB$ hg parents
2[tip]:0   8d7db8257fd0   2009-02-08 11:19 -0500   jaysoffian
  added baz
repoB$ hg glog
@  2[tip]:0   8d7db8257fd0   2009-02-08 11:19 -0500   jaysoffian
|    added baz
|
| o  1   8e0a16e691f4   2009-02-08 11:19 -0500   jaysoffian
|/     added bar
|
o  0   d32c3edefcba   2009-02-08 11:18 -0500   jaysoffian
     added foo
Now let's look at what happens if I pull from B, instead of pushing from A:
$ rm -rf repoA repoB
$ mkdir repoA
$ cd repoA
repoA$ hg init
repoA$ echo foo > foo
repoA$ hg add foo
repoA$ hg commit -m "added foo"
repoA$ cd ..
$ hg clone repoA repoB
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd repoA
repoA$ echo bar > bar
repoA$ hg add bar
repoA$ hg commit -m "added bar"
repoA$ cd ../repoB
repoB$ hg pull
pulling from /Users/jay/repoA
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)
callisto:~/repoB$ hg parents
0   1adbe0643fb7   2009-02-08 11:22 -0500   jaysoffian
  added foo
repoB$ hg glog
o  1[tip]   66a5c6395cfc   2009-02-08 11:22 -0500   jaysoffian
|    added bar
|
@  0   1adbe0643fb7   2009-02-08 11:22 -0500   jaysoffian
     added foo
repoB$ echo baz > baz
repoB$ hg add baz
repoB$ hg commit -m "added baz"
created new head
repoB$ hg parents
2[tip]:0   cb8f0c639bd4   2009-02-08 11:23 -0500   jaysoffian
  added baz
repoB$ hg glog
@  2[tip]:0   cb8f0c639bd4   2009-02-08 11:23 -0500   jaysoffian
|    added baz
|
| o  1   66a5c6395cfc   2009-02-08 11:22 -0500   jaysoffian
|/     added bar
|
o  0   1adbe0643fb7   2009-02-08 11:22 -0500   jaysoffian
     added foo

What I especially like about Mercurial here is that pushing from A is perfectly symetrical to pulling from B.

With git, cloning sets things up to fetch into a tracking branch, but then the push is not symetric to the fetch. That makes sense if you're cloning from a bare repo, but I think leads to confusion for new users when they clone a non-bare repo they wish to later push into. I dunno, I guess we'll see if the new message helps any.

j.
Johannes Schindelin· Feb 8, 2009, 20:54 UTC · re: Jay Soffian · lore

Re: receive.denyCurrentBranch

Hi,
On Sun, 8 Feb 2009, Jay Soffian wrote:
Show 19 quoted lines
> On Sun, Feb 8, 2009 at 5:30 AM, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > It's insane, that's what it is.
> >
> > You have _no_ business meddling with a remote working directory, _except_
> > by logging into that machine and working on that working directory
> > _directly_.
> >
> > If you do not agree, you have not thought about the implications, i.e.
> > what problems you buy.
> 
> Just by way of providing an additional perspective, I thought I'd illustrate
> how Mercurial handles this situation:
>
> [... lots of lines which say in a long and winding manner the same as 
>  the following...] 
>
> What I especially like about Mercurial here is that pushing from A is 
> perfectly symetrical to pulling from B.

Just to make it clear: if you have merge conflicts on the remote end, you will get into trouble.

I do not know how Mercurial handles this (I am sure you will send a page-long mail illustrating it), but in my humble opinion, there is _no_ way to handle this except if you have shell access to the remote repository/working directory.

No matter if you suggest leaving merge conflict, a detached HEAD, or "read-tree -u -m HEAD" (i.e. trying a simple merge with the working directory): _all_ of them are unsafe.

Ciao, Dscho

Jay Soffian· Feb 8, 2009, 22:16 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

On Sun, Feb 8, 2009 at 3:54 PM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

> I do not know how Mercurial handles this (I am sure you will send a
> page-long mail illustrating it)
No, I will not. Sorry for trying to help. Over and out.
j.
Jay Soffian· Feb 8, 2009, 22:51 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

On Sun, Feb 8, 2009 at 3:54 PM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 7 quoted lines
> Just to make it clear: if you have merge conflicts on the remote end, you
> will get into trouble.
>
> I do not know how Mercurial handles this (I am sure you will send a
> page-long mail illustrating it), but in my humble opinion, there is _no_
> way to handle this except if you have shell access to the remote
> repository/working directory.

I apologize for my prior reply. I understand you find any mention of Mercurial offensive. But if you had paid attention, you would have noticed that Mercurial did not attempt to merge. Rather, it created a new branch head in the remote repository.

The equivalent in git would be for the user to do something like this:
satellite:~/repo (master)$ git push origin
! [remote rejected] master -> master (branch is currently checked out)
satellite:~/repo (master)$ git push origin master:satellite-master
 * [new branch]      master -> satellite-master
satellite:~/repo (master)$ ssh origin
origin$ cd repo
origin:~/repo (master)$ git merge satellite-master
> No matter if you suggest leaving merge conflict, a detached HEAD, or
> "read-tree -u -m HEAD" (i.e. trying a simple merge with the working
> directory): _all_ of them are unsafe.

I'm not suggesting any of those things. I was just illustrating what another DVCS does to see if there was anything that might be learned from it.

Clearly users are confused about pushing into a checked-out branch. Maybe making that impossible by default will be enough.

j.
Johannes Schindelin· Feb 8, 2009, 23:41 UTC · re: Jay Soffian · lore

Re: receive.denyCurrentBranch

Hi,
On Sun, 8 Feb 2009, Jay Soffian wrote:
> If you had paid attention, you would have noticed that Mercurial did not 
> attempt to merge. Rather, it created a new branch head in the remote 
> repository.

So this is the "detached HEAD" idea. Which contradicts the law of the least surprise.

> Clearly users are confused about pushing into a checked-out branch. 
> Maybe making that impossible by default will be enough.

It should be clear that the equivalent of a central repository is a bare repository. And hopefully Junio's strategy will make that clearer, so I think this is the superior approach.

Ciao, Dscho

Junio C Hamano· Feb 9, 2009, 01:38 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 8 quoted lines
> On Sun, 8 Feb 2009, Jay Soffian wrote:
>
>> If you had paid attention, you would have noticed that Mercurial did not 
>> attempt to merge. Rather, it created a new branch head in the remote 
>> repository.
>
> So this is the "detached HEAD" idea.  Which contradicts the law of the 
> least surprise.

I do not think so. It is "adopting the symmetric mothership-satellite layout, when it suits the user's workflow".

Jay Soffian· Feb 9, 2009, 01:47 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

On Sun, Feb 8, 2009 at 6:41 PM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 10 quoted lines
> Hi,
>
> On Sun, 8 Feb 2009, Jay Soffian wrote:
>
>> If you had paid attention, you would have noticed that Mercurial did not
>> attempt to merge. Rather, it created a new branch head in the remote
>> repository.
>
> So this is the "detached HEAD" idea.  Which contradicts the law of the
> least surprise.

I agree that a detached HEAD is a bad idea. The closest parallel that I can come up with for git would be for receive-pack to store incoming changes into separate branch hierarchy, NOT for it to detach HEAD. A toy-patch I played around with earlier allowed this on the non-bare upstream repo:

[receive]
     prefix = refs/remotes/incoming

Then a push to refs/heads/master was automatically stored as refs/remotes/incoming/master instead.

And yes, I'm aware the user can use a push refspec on the sending side.
> It should be clear that the equivalent of a central repository is a bare
> repository.  And hopefully Junio's strategy will make that clearer, so I
> think this is the superior approach.
I foresee new user doing this:
laptop:~$ git clone ssh://workstation/~/repo
laptop:~$ cd repo
laptop:~/repo (master)$ echo change >> file && git commit -am change
laptop:~/repo (master)$ git push
...
error: refusing to update checked out branch: refs/heads/master
To ssh://workstation/~/repo
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'ssh://workstation/~/repo'
And now new user is stumped.
Perhaps adding something like this to the git-push man page:
---8<---

Non-bare Repositories --------------------- When pushing to a non-bare upstream repository (i.e. an upstream repository with a working copy), changes to the checked out branch are NOT reflected in the upstream index, nor in the working copy. This creates a situation where it is easy to accidentally revert the changes on the next commit in the upstream repository.

e.g. Assume the following history exists in the upstream repository:
A---B master

master is the currently checked out branch, nothing is staged in the index and the working copy is clean.

A single change is made to master downstream and pushed. The upstream repository is now in this state:

A---B---C master

However, the index and working copy reflect the state at commit B. Performing a new commit in the upstream repository would do this:

A---B---C---B' master
B' is a new commit, but reflects the same state as B.

In order to prevent this situation, it is recommended that if you need to push into a non-bare upstream repository, set receive.denyCurrentBranch = true in the upstream repository (this will become the default in git-X.Y). This will prevent the push from occurring. Instead, you can push into an alternate branch, and then merge that branch in the upstream repository. e.g.:

server$ cd ~/repo && git config receive.denyCurrentBranch true
laptop$ git push
...
error: refusing to update checked out branch: refs/heads/master
To ssh://server/~/repo
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'ssh://server/~/repo'
laptop$ git push origin master:refs/remotes/laptop/master
laptop$ ssh server
server$ cd ~/repo
server$ git merge laptop/master

Alternatively, you can set receive.denyCurrentBranch = warn in the upstream repo, but then you must remember to perform "git reset --hard" in the upstream repo after pushing to its currently checked out branch. (But be careful, as "git reset --hard" throws away uncommitted changes.)

---8<---
j.
Theodore Tso· Feb 9, 2009, 05:06 UTC · re: Jay Soffian · lore

Re: receive.denyCurrentBranch

On Sun, Feb 08, 2009 at 08:47:26PM -0500, Jay Soffian wrote:
Show 11 quoted lines
> I agree that a detached HEAD is a bad idea. The closest parallel that
> I can come up with for git would be for receive-pack to store incoming
> changes into separate branch hierarchy, NOT for it to detach HEAD. A
> toy-patch I played around with earlier allowed this on the non-bare
> upstream repo:
> 
> [receive]
>      prefix = refs/remotes/incoming
> 
> Then a push to refs/heads/master was automatically stored as
> refs/remotes/incoming/master instead.

What happens when the next person pushes to the same remote repo, and their refs/heads/master push is not a fast-forward merge of the current refs/remotes/incoming/master?

Do you lose the first user's push at that point? Or do you refuse the push?

						- Ted
Johannes Schindelin· Feb 9, 2009, 11:06 UTC · re: Theodore Tso · lore

Re: receive.denyCurrentBranch

Hi,
On Mon, 9 Feb 2009, Theodore Tso wrote:
Show 19 quoted lines
> On Sun, Feb 08, 2009 at 08:47:26PM -0500, Jay Soffian wrote:
> > I agree that a detached HEAD is a bad idea. The closest parallel that
> > I can come up with for git would be for receive-pack to store incoming
> > changes into separate branch hierarchy, NOT for it to detach HEAD. A
> > toy-patch I played around with earlier allowed this on the non-bare
> > upstream repo:
> > 
> > [receive]
> >      prefix = refs/remotes/incoming
> > 
> > Then a push to refs/heads/master was automatically stored as
> > refs/remotes/incoming/master instead.
> 
> What happens when the next person pushes to the same remote repo, and
> their refs/heads/master push is not a fast-forward merge of the
> current refs/remotes/incoming/master?
> 
> Do you lose the first user's push at that point?  Or do you refuse the
> push?

This is meant for non-bare repositories, right? Repositories that do have reflogs...

Ciao, Dscho

P.S.: There _have_ been times when I would have liked an automatic PUSH_HEAD that is always temporary, such as FETCH_HEAD. I _could_ imagine that this is something we could do (opt-in, of course): storing what was already pushed in a PUSH_HEAD, even if the refs could not be updated.

Jay Soffian· Feb 9, 2009, 15:46 UTC · re: Johannes Schindelin · lore

Re: receive.denyCurrentBranch

On Mon, Feb 9, 2009 at 6:06 AM, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 13 quoted lines
>
> On Mon, 9 Feb 2009, Theodore Tso wrote:
>>
>>
>> What happens when the next person pushes to the same remote repo, and
>> their refs/heads/master push is not a fast-forward merge of the
>> current refs/remotes/incoming/master?
>>
>> Do you lose the first user's push at that point?  Or do you refuse the
>> push?
>
> This is meant for non-bare repositories, right?  Repositories that do have
> reflogs...
Absolutely. It would be insane on a bare repository.
> P.S.: There _have_ been times when I would have liked an automatic
> PUSH_HEAD that is always temporary, such as FETCH_HEAD.  I _could_ imagine
> that this is something we could do (opt-in, of course): storing what was
> already pushed in a PUSH_HEAD, even if the refs could not be updated.
Oooh. Interesting idea.
j.
Jay Soffian· Feb 9, 2009, 15:43 UTC · re: Theodore Tso · lore

Re: receive.denyCurrentBranch

On Mon, Feb 9, 2009 at 12:06 AM, Theodore Tso <tytso@mit.edu> wrote:
Show 14 quoted lines
> On Sun, Feb 08, 2009 at 08:47:26PM -0500, Jay Soffian wrote:
>>
>> [receive]
>>      prefix = refs/remotes/incoming
>>
>> Then a push to refs/heads/master was automatically stored as
>> refs/remotes/incoming/master instead.
>
> What happens when the next person pushes to the same remote repo, and
> their refs/heads/master push is not a fast-forward merge of the
> current refs/remotes/incoming/master?
>
> Do you lose the first user's push at that point?  Or do you refuse the
> push?

The idea initially was to give a place to store the push head for the checked-out branch, but that isn't what HEAD points to. But it seemed confusing to do this for just the checked-out branch, so I was playing with having all incoming pushes go into their own namespace, hence the prefix.

Also, the use case I have in mind here is an individual with a workstation and a laptop, and the repository on the workstation and its clone on the laptop are both non-bare. There are not multiple individuals pushing to the non-bare repo. Someone please correct me if I'm wrong, but it is my impression that these are the users that have motivated the receive.denyCurrentBranch option (and its eventual default changing to refuse) in the first place.

In that case, the work-flow is typically:
server$ mkdir repo && cd repo && git init
server$ ... add, commit, add, commit ...
  [...time passes...]
laptop$ git clone ssh://server/~/repo
laptop$ ... add, commit, add, commit ...
laptop$ git push
laptop$ ssh server
server$ cd repo
server$ git merge incoming/master
server$ git branch -dr incoming/master

But, to answer your question, the non-fast-forward push is rejected, unless the user forces it, unless receive.denyNonFastForwards prevents the force.

j.
Jakub Narebski· Feb 8, 2009, 22:03 UTC · re: Jay Soffian · lore

Re: receive.denyCurrentBranch

Jay Soffian <jaysoffian@gmail.com> writes:
Show 8 quoted lines
> What I especially like about Mercurial here is that pushing from A is
> perfectly symetrical to pulling from B.
> 
> With git, cloning sets things up to fetch into a tracking branch,
> but then the push is not symetric to the fetch. That makes sense if
> you're cloning from a bare repo, but I think leads to confusion for
> new users when they clone a non-bare repo they wish to later push
> into. I dunno, I guess we'll see if the new message helps any.

Push _is_ symmetric to fetch; it is not symmetric to pull, and it cannot be. BTW. Mercurial's pull is equivalent to Git's fetch.

That is of course provided that you have the same refspec(s) for push and for fetch. And up to the point: push needs to be via authenticated channel, for obvious reasons.

The fact that default setup is not symmetric reflect the fact that in most cases the situation is not symmetric: you fetch from other remote repository into separate remotes layout, you push into your own public bare publishing repository set up in mirroring mode.

-- 
Jakub Narebski
Poland
ShadeHawk on #git

← back to recent threads