I made a bare repository clone of my Git working repository. The remote bare repo is located on network drive and I have been pushing to it so I have backup on different physical media. I am now getting an error on an attempt to push to the bare repository because my local repository is behind the remote repository. All modifications to the remote repository have been by pushing from my working repository. How could my local repo be behind the remote when there have been no other changes made to the remote repo? Here is the error I am getting when trying to push to remote repo
git -c diff.mnemonicprefix=false -c core.quotepath=false push -v --tags --set-upstream AlcatrazVehicleBW1 BitworksDevelopment:BitworksDevelopment FixSetOsDateTime:FixSetOsDateTime HeapUseBy3rdPartyLibBugFix:HeapUseBy3rdPartyLibBugFix M_Bitworks20161226CLOSED:M_Bitworks20161226CLOSED Orig_Port_to_Vx7_Review_changes_by_jdn:Orig_Port_to_Vx7_Review_changes_by_jdn ReSquashM_BitworksIntoMaster:ReSquashM_BitworksIntoMaster master:master
Pushing to //bw1/Public/Bitworks/Projects/Alcatraz/Vehicle.git
To //bw1/Public/Bitworks/Projects/Alcatraz/Vehicle.git
= [up to date] FixSetOsDateTime -> FixSetOsDateTime
= [up to date] HeapUseBy3rdPartyLibBugFix -> HeapUseBy3rdPartyLibBugFix
= [up to date] Orig_Port_to_Vx7_Review_changes_by_jdn -> Orig_Port_to_Vx7_Review_changes_by_jdn
= [up to date] master -> master
* [new branch] M_Bitworks20161226CLOSED -> M_Bitworks20161226CLOSED
* [new branch] ReSquashM_BitworksIntoMaster -> ReSquashM_BitworksIntoMaster
! [rejected] BitworksDevelopment -> BitworksDevelopment (non-fast-forward)
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/FixSetOsDateTime'
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/HeapUseBy3rdPartyLibBugFix'
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/Orig_Port_to_Vx7_Review_changes_by_jdn'
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/master'
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/M_Bitworks20161226CLOSED'
updating local tracking ref 'refs/remotes/AlcatrazVehicleBW1/ReSquashM_BitworksIntoMaster'
error: failed to push some refs to '//bw1/Public/Bitworks/Projects/Alcatraz/Vehicle.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
The branch it is rejecting BitworksDevelopment was moved in the local repository to point to the head of the master branch in an attempt to discard the old BitworksDevelopment branch which was squash merged into master. The intent was to start new development branching off of the current master head. My understanding was that the push would overwrite the tracking branch pointer on the remote AlcatrazVehicleBW1 bare repository. It appears there is yet again a Git concept I don't understand.
Well, that's the problem, then. Note that the easiest thing here would be simply to delete this branch name.
That's the complaint from Git, but the complaint is ... well, "wrong" is the wrong word; maybe a better word is "presumptive".
Remember that in Git, branch names merely point to one particular commit. That commit is the "tip of the branch":
Here, commits
A
andB
are on all three branches, with branch namebr1
pointing toB
, so thatB
is the tip commit onbr1
. CommitsC
throughE
are on two branches, withE
being the tip ofbr2
, andF
is only on one branch, and is the tip ofbr3
.Git allows you to add new branch name labels, to jump existing labels around arbitrarily, and to delete branch name labels entirely. However, there's a "normal pattern" for name movement, and that's in a "forward-only" direction. Even without adding any new commits, I can move
br1
forward, to any ofC
throughF
, and Git will believe that this is normal and just do it. I can movebr2
forward toF
. I can't movebr3
forward as there are no commits beyondF
.Suppose, though, I add a new commit
G
:Since
G
's parent isB
, I can movebr1
forward to point toG
.G
points back toB
, just asC
points back toB
, so movingbr1
to eitherC
orG
is OK.Once I do this move, though, Git resists having
br1
move back in any way.Suppose that I copy
G
to a new commitG'
that I tack onto the end ofF
:If I now ask Git to move
br1
to point toG'
, what happens toG
?With nothing pointing to
G
, we'll "lose" it. Git won't be able to find it, and if I did not save its hash somewhere (in my reflogs, or on my screen), so will I.1 Hence, a request to movebr1
fromG
toG'
is a non-fast-forward.2The basic assumption here is that when you are pushing to a bare repository, if your requested branch label change is not a "fast forward", you probably weren't aware that commit
G
even exists on the bare repository, as it was probably sent there by someone else, working in parallel, who merely beat you to thegit push
step. In this case, you need togit fetch
from the bare repository to pick up commitG
, then figure out how to get your own work to append to the existing commits, rather than removingG
. (That would usually be by rebasing or merging.)To tell a Git server that it should move a branch, even though the motion will lose some commits, you must apply one of the various "force" flags. The easy one to apply is
--force
or-f
, which just tells the server: "I know what I'm doing, lose some commits already." The slightly longer one, which is safer, is--force-with-lease
: you have your Git tell their Git "I believe the branch points to commitG
, and if so, make it point toG'
instead."The basic assumption is wrong, and
G
is your commit. You do know it's there, and you are intending to remove it. So, since your Git knows about it, you can use--force-with-lease
to check that their (the bare repo's)BitworksDevelopment
still points to the same commit, and if so, override it with your new replacement. Or, if you know you're the only one doing things to the bare repository, you can just use plain old--force
.Of course, as in footnote 2, it's actually simpler to just delete the name entirely, as is the more typical pattern with squash-merge. In this case, since the graph looks more like this:
the commits you will be losing by just deleting the branch name are the ones you've squash-merged in as commit
C
. The namemaster
suffices to hold commitC
; you don't need two names for it. At least, not yet: you will want another name forC
as soon as you go to make new commits that will come afterC
, but that you don't want to find via the namemaster
.Note that Git's protectiveness in bare repositories applies only to non-fast-forward branch name updates. Deleting a branch, losing the commits, is perfectly fine! You do have to use
git push --delete origin BitworksDevelopment
to send the delete-request to the server, though, so it's not like this will happen by accident. This is quite different from normal development pushes, where the race between two developers, both pushing to the same branch, resulting in non-fast-forwards, is a normal, everyday accident, that deserves protecting-against.1Bare repositories generally don't have reflogs, and do run a garbage-collect right after pushes, so
G
might get really-removed pretty quickly.2The technical definition of a fast-forward branch-name move is that the commit to which the name currently points is an ancestor of the commit to which you propose to point it. Since regular merge commits point back to two or more commits, moving a branch name to a new merge commit that simply combines that branch with another branch, is a fast-forward. The same is true for most squash "merges" as well: we add onto the main branch a single commit that represents all the work done in several commits on the other branch (and then we discard that other branch entirely). The difference in your case is that you're deliberately doing this without discarding the other branch; instead, you're trying to alter the other branch, in a non-fast-forward manner.