Pergunta

I have a repo with a master branch and a branch called febupdate. A week ago (or so) I committed some new files to the febupdate branch and pushed the update to github. Since that time there were three more commits that were pushed to that branch.

Then someone came back and said that all those files from the commit a few weeks ago need to be pulled out (because they are duplicates of other data). I wasn't sure how to do it so I asked for some advise and was told to git rebase HEAD~3. So I did that and I removed the bad commit and everything in my local copy was now the way it should be. But we couldn't figure out how to make the repo on github reflect that. Eventually we did a git push --force.

Now anytime someone does a git pull of that branch, they get asked to provide a reason for merging something in. And the user ends up with all of the duplicate files too. The only thing we've gotten to work is to blow away the whole folder and do a fresh git clone.

Here are my questions

  1. After we merge the febupdate branch into master will this behavior continue? Will anyone that does a git pull from master get a dialog about merging in data and end up with the extra files that I tried to kill? Do we need to tell everyone to blow away their local copy and do a fresh git clone?
  2. What was the proper way for me to have gotten rid of the files from the previous commit. I should point out that it was like 60-some files so it's not like I could have just done a git rm on two files. I suppose I could have tried to make a list of the files and done some looping to git rm all of them.
  3. Any way to un-mess-up the febupdate branch?
Foi útil?

Solução 3

In order:

  1. They don't have to re-clone (although that will work), but they do have to do some extra work.

    When you did the rebase you made copies of your old commits. Let's say commit B was the one you "removed":

    A - B - C - D    [old "febupdate", is what you pushed originally]
      \
        C'- D'       <-- HEAD=febupdate
    

    Anyone who grabbed a copy of the first "push" has all three original commits, B C and D. Then when they go to grab the update they get the new commits C' and D'. Let's say they added commit E themselves. So they now have (before bringing in your update):

    A - B - C - D       <-- origin/febupdate
                  \
                    E   <-- HEAD=febupdate
    

    Once they bring in your update they get this:

    A - B - C - D - E   <-- HEAD=febupdate
      \
        C'- D'          <-- origin/febupdate
    

    As far as git is concerned, when it look at this new state, it thinks they wrote commits B, C, D, and E, and you wrote commits C' and D'. Git offers them a chance to merge their four commits (B through E) with your two.

    Even if they did not make a new commit E, git still offers them a chance to merge "their" three commits (B through D) with your two. This will, in effect, "resurrect" commit B.

    If they have no commits of their own (edit: and no unsaved work of course!), they can easily recover by simply forcing their febupdate to match origin/febupdate:

    $ git checkout febupdate; git fetch; git reset --hard origin/febupdate
    

    If they do have commits of their own (such as E), they need to bring those (and not any to-be-deleted ones) onto the new tip commit D', which they can do with either git cherry-pick or git rebase --onto. The latter is a bit trickier to work1 but does everything in one fell swoop, as it were. The former (cherry-pick) is easier to think about: they just rename febupdate to oops, create a new local branch febupdate to track origin/febupdate, then git cherry-pick each of their commits (the ones that are really theirs) from oops onto the new febupdate.

  2. The "proper" way is a matter of who's willing to do what kind of work and whether you and they are willing to allow the bad commit B to remain in the commit history. If everyone is willing to do all the above work, and you really don't want B to remain, that's the way to do it.

    If it's OK for B to remain, and you want to make it easy for people, you can use git revert. The job git revert does is to add a new commit whose effect is simply "undo what was done in an old commit". Let's say that in commit B you added a file badfile and removed a good line from goodfile. The command git revert <sha-1-of-B> will make a commit that removes badfile and puts the good line back into goodfile. Whatever happened in B, revert "un-does" that and adds a new commit:

    A - B - C - D - R   <-- HEAD=febupdate
    

    Everyone expects new commits to be added, so this "just works" with whatever work-flow everyone already has.

  3. Sort of, but if some people have already grabbed commits C' and D', whatever you do now will still cause someone some extra work (over and above their already-some-extra-work). In this case I'd probably just press on with making people do the recovery (as outlined in part (1) above).


1There is an upcoming feature in git 1.9/2.0 that should (at least in theory) make it less tricky to work. Also, as a special case, git pull --rebase does the right thing in current git, but only if you have not already run git fetch. (This is a little hard to describe.)

Outras dicas

Firstly a new git clone is not necessary for other guys. If they know this "rebase", they can use

git fetch  
git reset --hard origin/febupdate

For your first question:

Answer is no, why your febupdate branch got messed up is because of the "rebase". The rebase cause git can not fast forward the changes. For the master branch, we only add new commits when we merge febupdate to it. In this case other guys can just use pull with no problem. (git can fast forward)

So I guess you know the answer of question 2 and 3. If you only add new commits, others can pull with no problem. To do this, a trick is:

  1. backup current HEAD (you may use a tmp branch).
  2. use any way to go to the correct status, your "git rebase HEAD~3" is one way.
  3. copy all files of this correct status to a tmp dir (outside the git dir).
  4. go back to the current HEAD by restoring from the backup branch.
  5. replace your whole git dir by the tmp dir files.
  6. now git status will show you deleted the files you want, commit changes, push

The merge problem should stop once everyone gets there histories updated. Or they can do a git reset --hard <sha before the reset> on the branch and do a pull (this will cause them to lose any commits that they have the branch). They should back up the changes by creating a new branch that they can use to cherry-pick their commits from.

This article should give some insight to what is going on. And why doing git rebase on pushed commits is a really bad thing.

http://git-scm.com/book/en/Git-Branching-Rebasing#The-Perils-of-Rebasing

The next time you want to undo changes that have been pushed, you should use git revert <sha of commit>. This will create a new commit that is the inverse of the problem commit. If you want to undo only some of the changes you do git checkout <sha of commit with good files> -- <files to fix> and make a commit.

If you pushed the changes, you need to make a new commit that fixes the changes rather than trying to revise history by removing the commit.

It's because they still have objects you reverted in their repos under an older hash. Git has to merge the new pulled data in with that hash in the graph and it is likely asking you for a commit message because it is trying to fast forward the merge. If you could have solved the problem by rm'ing the files that probably would have been cleaner.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top