I do agree that separating

 - where to push to, and
 - where to fetch for integrating with

is a necessary step for supporting triangular workflow better.

The "upstream" is that on top of which you build your work.  You
clone from there to bootstrap yourself, you add your work (which may
include integrating the work of your contributors, if you are a
mid-tier maintainer/integrator aka a lieutenant) on top of it, and
arrange the result to reach the "upstream" in some way.

For the simplest (and still widely used) workflow that employs a
central shared repository, the way to make the result to reach the
"upstream" is by directly pushing into it yourself.  In that sense,
the word "upstream" and the traditional behaviour of "git push" that
pushes back to the 'origin' (or branch.$name.remote) to update your
"upstream" (or branch.$name.merge at 'origin') both make perfect
sense.

Also, if you are rebasing, @{u} refers to that place you integrate
with, i.e. your "upstream", in the central shared repository
workflow.

But in a triangular workflow, the way to make the result reach the
"upstream" is *not* by pushing there yourself.  For developers at
the leaf level, it is to push to their own repository (often on
GitHub), which is different from where they (initially) clone from
in order to bootstrap themselves, and (subsequently) pull from in
order to keep them up-to-date.  And then they request the published
work to be pulled by the "upstream".

Note that even in a triangular workflow, @{u} should still refer to
the place you integrate with, i.e. your "upstream", not to the place
you push to publish the result of your work.

There is an interesting twist.  For a maintainer/integrator (either
a mid-tier one who has "upstream" somebody else controls, or the
top-level one whose conceptual "upstream" is himself), "git pull"
does not necessarily always go to the "upstream".  You also respond
to pull requests made to you, by those who consider you as their
"upstream".  As the antonym of "upstream", "downstream" would be a
good word to refer to them (and that is why calling the place you
push to "downstream" is not a good idea).  The integrator responds
to such a pull request by running "git pull" against his
"downstream".

Because an integrator will pull from more than one "downstream" by
definition (otherwise he is merely a relay or a gatekeeper), a pull
request typically carries the full URL and the branch name to be
pulled from, and there is not much need for an integrator to have a
short-hand mechanism to allow "git pull" (without other arguments on
the command line) to go to a "downstream".

But for an integrator, the place they consider their "upstream" is
not the place they push to publish.  A mid-tier integrator aka
lieutenant has exactly the same issue as leaf developers do on the
"push" side.

In a triangular workflow, "git push" that goes to 'origin' does not
make sense, and remote.pushdefault (and branch.$name.pushremote)
solves a half of the problem by allowing them to name the repository
they publish their work as the destination of a push.

The other half of the issue is that there is no good way to say "The
result of my work on this branch should go to that branch in the
repository I publish to; they are not named the same".

The value "upstream" for push.default does not make sense in the
context of a triangular workflow, as you may well base your work on
'master' from the upstream, which is a branch with a very generic
purpose (e.g. "to advance the state of the overall project"), but
your work may have a more specific purpose (e.g. "to improve frotz
feature") that deserves to be on a branch named after that specific
purpose in the repository you publish your work on.  That is, after
working on your 'frotz' branch this way:

    git checkout -t -b frotz origin/master
    work work work, commit commit commit

you do not want to push the result as 'master'; you want to call it
'frotz' in the published repository.

The value "current" for push.default dictates that the work you did
on your branch 'frotz' must be published as the same name 'frotz',
which may be good enough.

For those who do not find it good enough, a possible improvement is
to introduce another mechanism that tweaks the remote.publish.push
refspec.  I am thinking aloud here, but imagine that you are working
on your 'frotz' branch with this configuration:

    [remote "origin"]
        url = ... this is where your upstream is ...
        fetch = +refs/heads/*:refs/remotes/origin/*
    [remote "publish"]
        url = ... this is where you push to ...
        ;; push = +refs/heads/*:refs/heads/topics/*
    [remote]
        pushdefault = publish
    [push]
        default = single

    ; the remainder is per-branch
    [branch "frotz"]
        remote = origin
        merge = refs/heads/master

and you eventually want to push the result to topics/frotz branch
there.

In a triangular workflow, when you need to depend on some feature
that is only available in the updated upstream, you would rebase
your work on your upstream, e.g.

    git pull --rebase

which would go to branch.frotz.remote (= origin) and rebase your
work on top of their master.  Before such a rebase, you could

    git fetch
    git log ..@{u}

to see what new stuff you could exploit if you did rebase now.

Up to this point, there was no need to change anything; the current
Git does the above perfectly fine.  But then you try to push:

    git push

Thanks to the recent introduction of remote.pushdefault, this push
will no longer go to 'origin' but goes to 'publish'.  But not
necessarily to the desired branch.

With 'upstream', it may try to go to 'master' (as branch.frotz.merge
is set there), and with 'current', it may go to 'frotz'.

Uncommenting

    [remote "publish"]
        push = +refs/heads/*:refs/heads/topics/*

will let "git push publish" (or "git push") push 'frotz' to update
'topics/frotz' branch over there, which is what we want in this
example, but it will also push other branches at the same time,
which is not what we want.

Now I have a curious value "single" in the above configuration that
is not understood by current Git.  It is to trigger a new rule to
modify the way remote.publish.push refspec is used:

    When on branch 'frotz', where pushremote_get() would return
    'publish', find where refs/heads/frotz would be pushed with the
    refspec for that remote (i.e. refs/heads/*:refs/heads/topics/*
    maps refs/heads/frotz to refs/heads/topics/frotz) and push only
    that, i.e. update refs/heads/topics/frotz over there with the
    tip of 'frotz' we are pushing.

That may be a solution for those who do not find 'current' good
enough.


--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to