On Sat, Aug 05 2017, Junio C. Hamano jotted:

> Ævar Arnfjörð Bjarmason <ava...@gmail.com> writes:
>
>> We've talked how this UX should look before on-list. Rather than
>> re-hashing the arguments I had before I thought it was useful to present
>> it as a table. Here's how the patch looks now:
>>
>>     
>> |----------------------------+--------------+-------+---------+-----------------+-------------|
>>     | cmd                        | creates new? | moves | copies? | ...with 
>> config? | checks out? |
>>     
>> |----------------------------+--------------+-------+---------+-----------------+-------------|
>>     | branch <name>              | Y            | N     | N       | N        
>>        | N           |
>>     | checkout <name>            | Y            | N     | N       | N        
>>        | Y           |
>>     | checkout -b <name> <start> | Y            | N     | Y       | N        
>>        | Y           |
>>     | branch -m <name>           | Y            | Y     | N       | Y        
>>        | Y           |
>>     | NEW: branch -c <name>      | Y            | N     | Y       | Y        
>>        | Y           |
>>     
>> |----------------------------+--------------+-------+---------+-----------------+-------------|
>
> I actually consider "branch" to *never* invoking a checkout.  Even
> when "git branch -m A B" happens to be done when your checked out
> branch is A and you end up being on B.  That is not a "checkout".

I think we just have a different mental model of what "checkout"
means. In my mind any operation that updates the HEAD to point to a new
branch is a checkout of that branch.

In the interest of explaining what I mean with the above and not proving
you wrong or whatever, consider a basic implementation of git before
pack-refs or whatever.

You could then copy a branch name like:

    $ cp .git/refs/heads/{master,master-copy}

Get your currently checked out branch with:

    $ cat .git/HEAD
    ref: refs/heads/master

And you could then implement "git checkout" like:

    $ echo "ref: refs/heads/master-copy" >.git/HEAD

This still works today. Now if I make a commit it goes on the
.git/refs/heads/master-copy branch.

Now let's say I wanted to rename the 'master' branch. This would rename
it:

    $ mv .git/refs/heads/{master,trunk}

But it wouldn't check it out, my HEAD would still point to master:

    $ git log
    fatal: your current branch 'master' does not have any commits yet
    $ git log --oneline -1 trunk
    8b18718 (trunk) moo

To check it out I need:

    $ echo "ref: refs/heads/trunk" >.git/HEAD

Which yields:

    $ git log --oneline -1 HEAD
    8b18718 (HEAD -> trunk) moo

The mv sans the "echo" is what we'd get if we did:

    diff --git a/builtin/branch.c b/builtin/branch.c
    index 8a0595e115..62ed1a8e20 100644
    --- a/builtin/branch.c
    +++ b/builtin/branch.c
    @@ -493,9 +493,6 @@ static void rename_branch(const char *oldname, const 
char *newname, int force)
            if (recovery)
                    warning(_("Renamed a misnamed branch '%s' away"), 
oldref.buf + 11);

    -       if (replace_each_worktree_head_symref(oldref.buf, newref.buf, 
logmsg.buf))
    -               die(_("Branch renamed to %s, but HEAD is not updated!"), 
newname);
    -
            strbuf_release(&logmsg);

            strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);

I.e. the last step of "git branch -m" is checking out the branch by
updating the HEAD. Now let's compile git like that and rename "master"
to "trunk":

    $ git symbolic-ref HEAD
    refs/heads/master
    $ ./git-branch -m trunk
    $ git symbolic-ref HEAD
    refs/heads/master
    $ git show
    fatal: your current branch 'master' does not have any commits yet

Now let's do the equivalent of that removed
replace_each_worktree_head_symref():

    $ git checkout trunk
    Switched to branch 'trunk'

And voila:

    $ git symbolic-ref HEAD
    refs/heads/trunk

This is why I'm saying that "git branch -m" involves a checkout. Because
it is literally exactly the case that it could be replaced by a
shellscript whose last step is a "git checkout" of the new branch.

Anyway, none of that changes what we /should/ be doing on the fact that
"git branch -m" not updating the HEAD would be rather useless. I just
wanted to explain what I was talking about and why I was talking about
"checkout" in that context.

> Really from the end-user's point of view that is not a checkout.
> The user renamed the branch A and the same conceptual entity, which
> is a branch, is now called B.  If that branch was what was checked
> out (IOW, if that branch was what would be grown by one commit if
> the user did "git commit"), then now that branch's name is B.  It is
> natural if you ask "symbolic-ref HEAD" what branch is checked out
> after renaming A to B (and A happened to be what was checked out),
> the answer chould be B.
>
> It's like the city you live in changed the name of the street your
> house is on.  You do not call movers, you do not do anything, but
> your address changes.

Yeah I see what you mean, although this analogy rapidly breaks down when
you poke at it as shown above. My house (a sha1) can be on any number of
streets and new ones can be added/removed all the time without changing
where my house is at.

>> I.e. have "branch -c <name>" but just make it not checkout the new
>> thing. What you're describing above sounds to me like:
>>
>>     
>> |-------------------------------------------+--------------+-------+---------+-----------------+-------------|
>>     | cmd                                       | creates new? | moves | 
>> copies? | ...with config? | checks out? |
>>     [... stuff above this point is the same ...]
>>     | branch -m <name>                          | Y            | Y     | N   
>>     | Y               | Y           |
>>     [... so is branch -m but included for context ...]
>>     | NEW: checkout --super-b -b <name> <start> | Y            | N     | Y   
>>     | Y               | Y           |
>>     
>> |-------------------------------------------+--------------+-------+---------+-----------------+-------------|
>
> You are talking backwards.  I do not want "branch -c A B", even when
> A happens to be what is checked out, to check out branch B.  You and
> Sahil were who wanted to check out branch B while doing so, and I
> just tried to guess why you two wanted to have such a behaviour that
> did not make much sense to me.  And my guess was "perhaps they want
> a way to create a new branch starting from another branch, and check
> it out, and do so in a single end-user operation".

The use-case is the following. When I hack on e.g. git.git I'm on
"master" with configured upstream info:

    $ git log @{u}..

Because if I don't care about 'master' another shorter way to say:

    $ git checkout -b topic -t origin/master

Is:

    $ git branch -m topic

That's how I start hacking on "topic", now let's say I've submitted that
and want to start on topic-2 after getting feedback:

    # While still on topic
    $ git branch -c topic-2

So it's just a way to get something exactly like -m except the "move &&
checkout" logic is changed to "copy && checkout".

> I am not particulary interested in such an operation; in my guess,
> you two are.  And the "super-b" thing was a suggestion to you two:
> If you so desperately want such an operation, then don't make
> "branch --copy A B" that operation.  Such an operation better fits
> in "checkout", not "branch".

That's arguably another sensible way to do a /subset/ of what 'git
branch -c' does (or something in addition to this patch). But there's
another use-case it won't cover, i.e. the two-arg branch -m/-c
manipulating a branch that HEAD doesn't point to:

    $ git checkout master
    $ git branch -m topic avar/topic

You can now do that with -c to s/move/copy/, but couldn't do this with
checkout unless you wastefully and needlessly updated the working tree.

> If you are not interested in such an operation, then that is fine.
> Do not add the "super-b" mode to "checkout".

Yeah it's not something I'm interested in or have a use-case for,
although I think in the same way we have -t for checkout it might be
sensible to have e.g.:

    $ git checkout -b topic-2 -c topic -t origin/master

Where the new -c or --config-from would mean "...and get the config from
'topic'". Such a name would probably be less confusing than
--super-b[branch?] which to be implies some ongoing hierarchical
relationship.

> But I won't defend a "branch --copy A B" that checks out B when
> users and other people like Ramsay think that such a behaviour is
> illogical, because I do think it is, too.

I just started this from the POV of using branch -m and wanting
something like that which didn't remove the old thing when it was done
as its last step, which seemed easy to implement & explain.

As noted above I think it probably makes sense to also have something
like [--config-from|-c] for checkout, which would have a related but not
exactly the same use-case.

Reply via email to