In message [off-list ref], Rich Pixley writes:
On 4/30/12 16:31 , Seth Robertson wrote:
> It seems that git is allergic to the dual head branch solution or
> something, which is surprising and disappointing.
>
> Git tracks your version of master separately from each other remote's
> master. This is exactly dual/multiple heads.
No, it isn't at all.
Multiple heads are the idea that a single commit can "branch" in the
repository and that /both /commits can be HEADS of the same branch at
once in a single repository. This allows a potential collision to exist
in the repository and to be pushed and pulled through multiple
repositories. It also largely eliminates this entire discussion since
each of the intermediate repositories between, say, you and I can carry
the collision. Either you or I, at will, can merge these heads just
like we'd merge any other two commits, push, etc.
That would seem to be the obvious and intuitive behavior, not
arbitrarily preventing the transfer.
I still don't see how git isn't providing this to you, with the caveat
that in git, a single commit with a specific SHA1 hash is constant.
You may modify it (making it a new commit), modify its history (making
it a new commit), or add new commits after it. Git doesn't number
commits the way other VCS do, which may be the source of confusion.
A specific commit with a specific SHA1 can be at the head of multiple
branches. Those branch may be local to the repository, local tracking
branches, or remote tracking branches.
Contrarywise, the head of the "master" (or any) branch (or ref) may
point to any SHA1. My master branch may point to one SHA1, yours may
point to another. You can look at my version of master and I can look
at your version of master (permissions permitting). After appropriate
network operations, either you, or I, at will, can merge these heads
just like we'd merge any other two commits, push, etc. With
appropriate naming conventions, we can even continue parallel updates
where I can make updates to your version of master and you can make
updates to my version of master, in addition to our own.
For example, in the diagram http://mercurial.selenic.com/wiki/Head
rev2 might be your version of master and rev3 might be my version.
Both might exist in my repository and both might exist in your
repository, and both might have the symbolic name "master" associated
with it, and git would keep it entirely straight.
> What git *does* forbid
> (by default) is:
>
> 1: Letting you update someone else's checked out (non-bare) repository
> underneath them
Yeah. That "underneath them" thing is confusing. I don't see any
reason why that should necessarily be so.
Git knows what commit is checked out. That's HEAD, yes? So what's
wrong with letting it collect other commits from other repositories
while your working directory sits?
It does! It can! What is forbidden is for me update what you have
checked out. Can you conceive of a revision control system where I
commit I would make would change what you have checked out? (Well, I
can, ClearCase with dynamic views, and it is more horrible than you
can imagineâI've had compile fail because the files changed underneath
my feet between the start of the compile and the end of the compile).
And what happens if the file I changed is also the file you are in the
middle of changing? Is your change going to overwrite mine? Is mine
going to overwrite yours? This way leads to insanity.
You can always commit your change right on top of what's checked
out, creating a second head for that branch.
With git, you must always commit your change right on top of what's
checked out, though of course you may decide to change what's checked
out and "float" the changes you made over to the new head (trivial, as
long as there are not conflicts between the two heads and the changes
you made). However, only the user of the repository is allowed to do
this. A remote user is not allowed to change what's checked out.
Yes, I've read that git-diff, etc, are all making assumptions that fail
in this case, but there's nothing significantly different about
collecting commits to other branches and collecting commits to the
branch you're currently checked out from.
Yes there is. Consider this use case. You can I both spot DIFFERENT
bugs. You and I both start editing filea. I fix my problem one way
which involves lines 10, 100, and 500, you fix your problem which
involves line 10, 100, and 200. I'm typing faster than you so I
commit/push first. If I can update your HEAD, at that point I've
changed the file you are editing. You save and my change is lost.
Now this is where you say, if git only supported multiple HEADs the
problem would go away. I could update my version of master and you
could update your version of master and there would be no conflict.
And...of course you can with git. I don't update your master, what
you have checked out, I update what you know is my master. Then when
you are done, you get to say "merge my master with your master" or
"instead of committing this change on my branch, let me try this last
change I made on top of the changes you made" or whatever you want to
say.
> 2: Letting you update someone else's repository if they have more
> recent changes than you do.
Again, if they have more recent changes, then my line of changes should
create a fresh HEAD on that branch. Then the repositories hold all of
our changes to be merged at our leisure.
And...git does this. Your changes are made on your HEAD (your
branch). My changes are made on my HEAD (my branch). Never the twain
shall meet until someone says "merge" (or "rebase").
The key to all of this is the namespace naming convention.
refs/remotes/<remotename>/<branchname> (or more informally
<remotename>/<branchname>) is a remote tracking branch, or my idea of
what your branches (HEADs) currently are. These are (kinda/sorta)
read-only reference which are only updated when you specifically ask
them to be updated, or if I decide to update what you believe I have
(kinda rude, but allowed).
So let us consider a specific example. There is a repository which
everyone call's "Rich" (they don't need to use the same name, but it
reduces confusion), a repository everyone calls "Seth", and a central
repository everyone calls "origin".
Location Branchname
-------- ----------
origin foo (bare)
rodger foo
rodger origin/foo
rodger seth/foo
seth foo
seth origin/foo
seth rodger/foo
I make a change in my foo. I can push the change into origin's foo,
rodger's seth/foo (and technically origin/foo but that is more than
just rude). You can take my change and store it in seth/foo.
At any point, after you have the changes (either because I push them
or you fetch them) you can then merge your work with my work.
So...what's not possible?
-Seth Robertson
For example, in the diagram http://mercurial.selenic.com/wiki/Head
rev2 might be your version of master and rev3 might be my version.
Both might exist in my repository and both might exist in your
repository, and both might have the symbolic name "master" associated
with it, and git would keep it entirely straight.
But not in the same repository. And therein lies the issue.
> What git *does* forbid
> (by default) is:
>
> 1: Letting you update someone else's checked out (non-bare) repository
> underneath them
Yeah. That "underneath them" thing is confusing. I don't see any
reason why that should necessarily be so.
Git knows what commit is checked out. That's HEAD, yes? So what's
wrong with letting it collect other commits from other repositories
while your working directory sits?
It does! It can!
To the branch you have checked out? That's what I want! How do I do that?
What is forbidden is for me update what you have
checked out.
There's some ambiguity in your sentence here. I don't know whether
you're referring to my being forbidden from modifying your working
directory or whether you're referring my being forbidden to modify the
branch from which your working directory is checked out. I understand
and respect the former, but the latter seems arbitrary.
(I think clearcase already answered the dynamic update problem. I liked
it. And I especially liked clearmake and build avoidance. Shame that
IBM killed it. (Oh, and if you had inconsistent builds, then you
weren't using clearmake. Clearmake solved that problem.))
I'm less concerned about whether my push changes your working directory.
I'm fine with leaving your working directory as is and letting you
decide when and how to move forward on your own time.
Perhaps part of the problem here is the unfortunate choice of the word
"HEAD" to refer to the thing you have checked out when that commit might
not be a childless commit at all. When I say "create another head" I
mean, "create a childless commit on the same branch".
You can always commit your change right on top of what's checked
out, creating a second head for that branch.
With git, you must always commit your change right on top of what's
checked out, though of course you may decide to change what's checked
out and "float" the changes you made over to the new head (trivial, as
long as there are not conflicts between the two heads and the changes
you made). However, only the user of the repository is allowed to do
this. A remote user is not allowed to change what's checked out.
Ok, how do I ask git to push a commit into the middle of a branch that
you have checked out at a tip, (there's always exactly one tip for any
branch in git, right?)? I don't care to change your working directory
nor your index. I just want my commits to show up in the middle of that
branch.
Yes, I've read that git-diff, etc, are all making assumptions that fail
in this case, but there's nothing significantly different about
collecting commits to other branches and collecting commits to the
branch you're currently checked out from.
Yes there is. Consider this use case. You can I both spot DIFFERENT
bugs. You and I both start editing filea. I fix my problem one way
which involves lines 10, 100, and 500, you fix your problem which
involves line 10, 100, and 200. I'm typing faster than you so I
commit/push first. If I can update your HEAD, at that point I've
changed the file you are editing. You save and my change is lost.
"HEAD", being defined as the thing I have checked out, should not be
changed, I agree.
But the nomenclature here is a bit misleading. Really, "HEAD" could be
any commit. It doesn't have to be a childless commit. The fact that my
HEAD was childless at the time I checked it out doesn't necessarily mean
that it must remain childless forever.
Let's back it up a moment. I change file1 and you change file2. These
are non-colliding changes. They can be trivially merged and yet git
refuses to push between our repositories.
The refusal seems arbitrary. It could just as easily accept my change,
leave your HEAD pointing where it was, but move the "master" pointer to
point to the merged commit. This is exactly what it does if I pull your
changes into my repository. I just can't ever push them again after
this happens. (I could push them previously.)
Now this is where you say, if git only supported multiple HEADs the
problem would go away.
Right.
I could update my version of master and you
could update your version of master and there would be no conflict.
There'd be a conflict. But we'd be able to update anyway. I'd be able
to see your changes in my repository by default. You'd be able to see
mine. And either one of us could merge, commit, and push the merge.
Until then, the collision could be carried and propagated by multiple
repositories around our repository network. Maybe a third person would
integrate them for us.
And...of course you can with git. I don't update your master, what
you have checked out, I update what you know is my master. Then when
you are done, you get to say "merge my master with your master" or
"instead of committing this change on my branch, let me try this last
change I made on top of the changes you made" or whatever you want to
say.
Yes. And I have no choice. I must say that at every repository I own,
every time I push or pull changes, tracking whether those have been
merged or not, even when 99% of my changes could have been trivially
merged, owned by me, in repositories I own. And I'm forced to do manual
merges and extra pulls in the most common collision situation I run
into, which can be handled automatically by other systems like
mercurial, even subversion.
My problem is that I must necessarily manage 10's of repositories for my
own work. These extra steps mean a geometric increase in complexity and
in error potential over something like mercurial, which maintains the
"common branch" illusion automatically for me or something like
subversion which genuinely has a common branch.
I don't need separate branches for each repository. What I really want
is a common branch whose changes I can push back and forth between the
various repositories, or coordinate through a central cache, without
worrying about the underlying details that git is forcing me to confront.
I think I'm beginning to understand what git does offer now. Thank you
for the help with clarifications. I just don't like it. It's a huge
let down for me in my context from working with systems like mercurial
which make my life easier. Quite frankly, git is a huge amount of work
for me by comparison to mercurial for no added benefit, or even by
comparison to subversion, with only minor benefits in most situations
over subversion.
So...what's not possible?
In effect, any interesting activities involving push or any workflows
that require pushing. Most of them can probably be worked around by
using a pull architecture instead, but that adds an unnecessary
explosion of complexity in many cases such as the one I'm facing.
In particular, sharing a branch becomes problematic with git.
--rich
From: Michael Witten <hidden> Date: 2016-06-15 22:53:42
On Mon, 30 Apr 2012 20:04:25 -0700, Rich Pixley wrote:
I don't need separate branches for each repository. What I really want
is a common branch whose changes I can push back and forth between the
various repositories, or coordinate through a central cache, without
worrying about the underlying details that git is forcing me to confront.
Here's a start for a more precise discussion.
Sincerely,
Michael Witten
Cache server:
$ git clone --mirror "$uri_for_central_repo"
Machine A:
$ git clone "$uri_for_cache_repo"
$ git checkout -b feature_0 origin/feature_0
$ # ... do some work ...
$ git push --set-upstream origin HEAD:shared/feature_0
$ git config push.default upstream
Machine B:
$ git clone "$uri_for_cache_repo"
$ git checkout -b feature_0 origin/feature_0
$ # ... do some work that conflicts with work done on Machine A...
$ git push --set-upstream origin HEAD:shared/feature_0
To $uri_for_cache_repo
! [rejected] HEAD -> shared/feature_0 (non-fast-forward)
error: failed to push some refs to '$uri_for_cache_repo'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
$ git pull origin shared/feature_0
From $uri_for_cache_repo
* branch shared/feature_0 -> FETCH_HEAD
Auto-merging a
CONFLICT (add/add): Merge conflict in a
Recorded preimage for 'a'
Automatic merge failed; fix conflicts and then commit the result.
$ # ... resolve conflict and commit results ...
$ git push --set-upstream origin HEAD:shared/feature_0
$ git config push.default upstream
Machine A:
$ git pull # pulls in origin's shared/feature_0
$ # ... do some work ...
$ git push # pushes to origin's shared/feature_0
Machine B:
$ git pull # pulls in origin's shared/feature_0
$ # ... do some work ...
$ git push # pushes to origin's shared/feature_0
Machine A:
$ git pull
$ git remote add central "$uri_for_central_repo"
$ git push central HEAD:feature_0 # Assume there is a conflict
To $uri_for_central_repo
! [rejected] HEAD -> feature_0 (non-fast-forward)
error: failed to push some refs to '$uri_for_central_repo'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
$ git pull central feature_0
$ # ... resolve conflict and commit results ...
$ git push central HEAD:feature_0 # Assume it succeeds this time
$ # Let's update the cache repo from Machine A:
$ git fetch central
$ git push origin 'refs/remotes/central/*:refs/heads/*'
Machine B:
$ git pull
$ git pull . origin/feature_0 # Get new stuff cached from central server
On Mon, 30 Apr 2012 20:04:25 -0700, Rich Pixley wrote:
quoted
I don't need separate branches for each repository. What I really want
is a common branch whose changes I can push back and forth between the
various repositories, or coordinate through a central cache, without
worrying about the underlying details that git is forcing me to confront.
Here's a start for a more precise discussion.
Thank you. That might be just what I need in git.
--rich
On Mon, 30 Apr 2012 20:04:25 -0700, Rich Pixley wrote:
quoted
I don't need separate branches for each repository. What I really want
is a common branch whose changes I can push back and forth between the
various repositories, or coordinate through a central cache, without
worrying about the underlying details that git is forcing me to confront.
Here's a start for a more precise discussion.
Sincerely,
Michael Witten
Cache server:
$ git clone --mirror "$uri_for_central_repo"
Machine A:
$ git clone "$uri_for_cache_repo"
$ git checkout -b feature_0 origin/feature_0
$ # ... do some work ...
$ git push --set-upstream origin HEAD:shared/feature_0
$ git config push.default upstream
Machine B:
$ git clone "$uri_for_cache_repo"
$ git checkout -b feature_0 origin/feature_0
$ # ... do some work that conflicts with work done on Machine A...
$ git push --set-upstream origin HEAD:shared/feature_0
To $uri_for_cache_repo
! [rejected] HEAD -> shared/feature_0 (non-fast-forward)
error: failed to push some refs to '$uri_for_cache_repo'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
$ git pull origin shared/feature_0
From $uri_for_cache_repo
* branch shared/feature_0 -> FETCH_HEAD
Auto-merging a
CONFLICT (add/add): Merge conflict in a
Recorded preimage for 'a'
Automatic merge failed; fix conflicts and then commit the result.
$ # ... resolve conflict and commit results ...
$ git push --set-upstream origin HEAD:shared/feature_0
$ git config push.default upstream
Machine A:
$ git pull # pulls in origin's shared/feature_0
$ # ... do some work ...
$ git push # pushes to origin's shared/feature_0
Machine B:
$ git pull # pulls in origin's shared/feature_0
$ # ... do some work ...
$ git push # pushes to origin's shared/feature_0
Machine A:
$ git pull
$ git remote add central "$uri_for_central_repo"
$ git push central HEAD:feature_0 # Assume there is a conflict
To $uri_for_central_repo
! [rejected] HEAD -> feature_0 (non-fast-forward)
error: failed to push some refs to '$uri_for_central_repo'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
$ git pull central feature_0
$ # ... resolve conflict and commit results ...
$ git push central HEAD:feature_0 # Assume it succeeds this time
$ # Let's update the cache repo from Machine A:
$ git fetch central
$ git push origin 'refs/remotes/central/*:refs/heads/*'
Machine B:
$ git pull
$ git pull . origin/feature_0 # Get new stuff cached from central server
This is probably what I'm going to end up using.
Just for comparison, here's a similar process in hg.
Cache server:
$ hg clone $uri_for_central_repo
Machine A:
$ hg clone $uri_for_cache_repo
$ # ...do some work...
$ hg push
Machine B:
$ hg clone $uri_for_cache_repo
$ # ...do some work...
$ hg push # assume this collides
pushing to $uri_for_cache_repo
searching for changes
abort: push creates new remote head 6d2eb0a6a278!
(you should pull and merge or use push -f to force)
$ hg push -f # the pull and merge case parallels git, so let's use
push -f.
Any repo:
$ hg pull # pulls in all changes including the dual heads
$ hg merge # collapses the dual heads
$ hg commit # commits the merge
$ hg push
Machine A:
$ hg pull # pulls in all changes so far
$ hg up
$ #... do some work ...
$ hg push
Machine B
$ hg pull
$ hg up
$ # ... do some work ...
$ hg push
Any repo:
$ hg pull $uri_to_central_repo
$ hg merge
$ hg push $uri_to_central_repo
$ hg push # default is cache repo
Machine B:
$ hg pull
Some Conclusions:
* the work flows are similar.
* the hg commands are simpler and have the defaults that we want,
primarily because no extra branches are required.
* the hg error messages are straightforward, clear, and don't require
any deep knowledge of the source code control system or it's
limitations. (I still don't understand what the git message on
collision is saying.)
* hg has more options about how to handle the collisions or the merges.
While git can mimic some of those options, doing so requires a priori
knowledge that isn't stored in the source code control system and
therefor requires a human exchange which is optional with hg.
--rich
On Thu, 03 May 2012 12:13:46 -0700
Rich Pixley [off-list ref] wrote:
* the hg commands are simpler and have the defaults that we want,
primarily because no extra branches are required.
You can mimic them with a brand new porclain.
For example, the worklfow described here :
http://nvie.com/posts/a-successful-git-branching-model/
have been implemented here :
https://github.com/nvie/gitflow
Since git do not have by design the multi HEAD hg design, you will
still not have it but all the glory command chains to mimic it can be
done with some shell scripts and a good knowledge of git internals.
Regards,
--
Jérôme Benoit aka fraggle
La Météo du Net - http://grenouille.com
OpenPGP Key ID : 9FE9161D
Key fingerprint : 9CA4 0249 AF57 A35B 34B3 AC15 FAA0 CB50 9FE9 161D
From: Felipe Contreras <hidden> Date: 2016-06-15 22:53:44
On Thu, May 3, 2012 at 9:13 PM, Rich Pixley [off-list ref] wrote:
This is probably what I'm going to end up using.
Just for comparison, here's a similar process in hg.
Cache server:
$ hg clone $uri_for_central_repo
% git clone $uri_for_central_repo
Machine A:
$ hg clone $uri_for_cache_repo
% git clone $uri_for_cache_repo
$ # ...do some work...
$ hg push
% git push
Machine B:
$ hg clone $uri_for_cache_repo
% git clone $uri_for_cache_repo
$ # ...do some work...
$ hg push # assume this collides
% git push
pushing to $uri_for_cache_repo
searching for changes
abort: push creates new remote head 6d2eb0a6a278!
(you should pull and merge or use push -f to force)
$ hg push -f # the pull and merge case parallels git, so let's use push -f.
This is stupid, why make everybody else's life difficult? Let's merge here.
% git pull
% git push
Any repo:
$ hg pull # pulls in all changes including the dual heads
% git pull
$ hg merge # collapses the dual heads
$ hg commit # commits the merge
$ hg push
No need for this, the guy that diverged did this (Machine B).
Plus, what happens if 3 other machines do this? You you have 3 merges,
2 would conflict, and then you would have useless recursive merges, or
some people would have to revert. Why bother N people, when one guy
can do it at the origin (Machine B)?
Machine A:
$ hg pull # pulls in all changes so far
$ hg up
% git pull
$ #... do some work ...
$ hg push
% git push
Machine B
$ hg pull
$ hg up
% git pull
$ # ... do some work ...
$ hg push
% git push
Any repo:
$ hg pull $uri_to_central_repo
$ hg merge
% git pull $uri_to_central_repo
$ hg push $uri_to_central_repo
% git push $uri_to_central_repo
$ hg push # default is cache repo
% git push
Machine B:
$ hg pull
% git pull
Some Conclusions:
* the work flows are similar.
* the hg commands are simpler and have the defaults that we want, primarily
because no extra branches are required.
Wrong.
* the hg error messages are straightforward, clear, and don't require any
deep knowledge of the source code control system or it's limitations. (I
still don't understand what the git message on collision is saying.)
Whatever. I don't care what the error message for a merge conflict
actually says, all I need to know is that there was a conflict.
* hg has more options about how to handle the collisions or the merges.
While git can mimic some of those options, doing so requires a priori
knowledge that isn't stored in the source code control system and therefor
requires a human exchange which is optional with hg.
WTF? git handles collisions just fine.
Actually the git version has less commands. And if configured properly
you don't need to specify URLs.
Cheers.
--
Felipe Contreras