开发者

git non-fast-forward updates. How to overcome?

Apologies for the double post, but I wasn't getting any further replies in my previous question, so thought I'd try my luck explaining it better ...

I've set my git working structure as follows (thanks to a recommendation in another question git : Small project work)

 /live/website
       |
       |
/path/to/staging (bare)
  |          |
  |          |
dev1        dev2

Currently, both dev1 and dev2, push the projects to the /path/to/staging repo. I have a hook in /path/to/staging, that automatically triggers a cd /live/website && git pull from my /path/to/staging. This way, I have a working copy of all files there. So really dev1, dev2 and /live/website are git clones of /path/to/staging.

Rudi, recommended finding out if receive.denyNonFastforwards was set to true on path/to/staging by doing:

cd path/to/staging/ && git config --get-all receive.denyNonFastforwards

And unfortunately it is set to true. So for example if I did a git reset --hard <SHA-1> on the /live/website server, it does so successfully. However, when I try to push my changes from /live/website to /path/to/staging:

error: failed to push some refs to 开发者_JAVA技巧'/path/to/staging/' To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See the 'non-fast-forward'

Assumably, I face the same problem when I make the changes within dev1 or dev2 and try to push the changes to /path/to/staging.

I want to have a kind of master reset where I could just log onto /path/to/staging or /live/website and step-down to a stable version instead of figuring out which developer made the change and then doing a git reset. Can this be done? I basically want to have the ability to step down to a different version of a file, if I choose to do so ...


From your other questions I guess your situation looks like line 1 in the following diagram.

    current workflow:
                     /live/website    /path/to/staging     dev
1   now              1-2-3-F          1-2-3-F              1-2-3-F
2   reko             1-2-3(-F rm) ->  1-2-3                1-2-3-F
3   hack hack hack   1-2-3            1-2-3                1-2-3-F-5-6-7
4   dev push         1-2-3            1-2-3-F-5-6-7 <-     1-2-3-F-5-6-7
5   auto update      1-2-3-F-5-6-7 <- 1-2-3-F-5-6-7        1-2-3-F-5-6-7

6   revert workflow:
7   now              1-2-3-F          1-2-3-F              1-2-3-F
8   backout          1-2-3-F-~F   ->  1-2-3-F-~F           1-2-3-F
9   hack hack hack   1-2-3-F-~F       1-2-3-F-~F           1-2-3-F-5-6-7
10  dev try push     1-2-3-F-~F       1-2-3-F-~F           1-2-3-F-5-6-7
11  dev pull         1-2-3-F-~F       1-2-3-F-~F       ->  1-2-3-F-5-6-7-8
                                                                \-~F--/

12  dev push         1-2-3-F-~F       1-2-3-F-5-6-7-8  <-  1-2-3-F-5-6-7-8
                                             \-~F--/              \-~F--/

13  auto update      1-2-3-F-5-6-7-8  1-2-3-F-5-6-7-8  <-  1-2-3-F-5-6-7-8
                            \-~F--/          \-~F--/              \-~F--/

In your current workflow you have one defect top commit(F) which you remove(2) with rebase or reset, and then push this removed commit into the central repository. The problems of this workflow arise, when the developer which introduced F did other commits(4) on top of F(which is the default behavior in git), and then pushes this work again. Since the F commit was only stripped from the central repo the developer does not know that F should not be there, and so F reappears(4). Since you set up a hook on the central repo which automatically updates /live/website this means that after this push the fault also reappears on the webside. It's now even worse, because F now is not the HEAD anymore, so you can't easily remove F (you can rebase now, but the problem that F lives in a developer repo is still there).

In the revert workflow you tell git revert F(8), which causes git to create a commit which undoes commit F, here called ~F. Your working copy is now at a state which looks as if F never happened, and you can safely push ~F to the central repository. Now when the other developer has new work done and want to push(10), git will refuse this push, since the ~F commit in the central repository is not an ancestor of any commits in the developers repository, and git now forces the developer to pull ~F from the central repository(11). Then the developer must merge (or rebase) ~F into his branch(12), before the new changes can be pushed to the central repo again(13).

One word about the /live/website repo: Since you have a hook in /path/to/staging which automatically updates this repo on a push, it is not a good idea to do changes in /live/website. The main problem is that if you have local changed files there, or commits which cause merge conflicts, the push-hook in /path/to/staging can fail and leave /live/website in a very ugly state. In a scenario with automatic updates it is better to give the ownership of the directory directly to the updating machinery, which means all file changes there should only come from a git pull, but not from direct edits/commits in there. You can do corrections in a clone of /live/website, and then push these changes into /path/to/staging, which then distributes your changes back to /live/website (but do not push into /live/website[which cause git to screw up your index], nor do pull your clone changes in there, because then you may need to merge different development histories from your clone and /path/to/staging in /live/website, which is definitely not what you want).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜