Что такое «git push problem: non fast forward»

Данная мини-заметка в первую очередь является ответом на вопрос. Так как мой аккаунт read-only, то вот такой вот способ ответа. «А жизнь-то налаживается!» ©

Первый вывод после прочтения вопроса и ответов — не делайте так, как предложил defuz. Он не понимает суть проблемы, и если вы сделаете как им предложено — скорее всего, вы потеряете данные.
Второй: alekciy тоже не совсем прав, но тут шансов на потерю данных гораздо меньше. Почти никаких.
Ну и третий: блин, ну когда же люди поймут, что владеть используемым инструментом это реально необходимо? Читайте документацию!


Скажу сразу, многие детали и «внутренности» работы git'а опущены либо упрощены, но не в ущеб пониманию. Именно появление понимания и цель статьи, и если оно у вас уже есть, и вы уверены, что оно верное :) — то ничего нового для вас тут не будет. Если же понимания пока нет — то поехали!

Для начала, надо выяснить, что такое fast-forward. Это просто один из вариантов (методов, стратегий) выполнения операции merge. Он возможен только в том случае, если текущий коммит (тот, к которому будет происходить слияние, обозначим его А), является предком «сливаемого» коммита (тот, с которым будет происходить слияние, обозначим его Б). Графически это выглядит так (х — просто другие, не важные нам коммиты):

......-x-x-x-x-A-x-x-x-Б


Если история коммитов выглядит вот так:
......-В-x-x-x-A
         \
          \x-x-x-Б

то выполнить слияние от А к Б методом fast-forward не получится — А не является предком для Б. Точно так же, в этой ситуации нельзя выполнить слияние с помощью метода fast-forward от Б к А. Но, любое из слияний «от В к А» и «от В к Б» — можно, и при выполнении одного из этих слияний git (по умолчанию) будет использовать именно метод fast-forward.

Теперь следующая вещь, которую необходимо уяснить — это что такое push. Ключевых моментов в этой операции два. Первый: это не просто передача данных о ваших коммитах «на ту сторону», а ещё и обязательная последующая операция merge «на той стороне». И второй: «та сторона» для операции merge будет использовать только fast-forward. Почему всё именно так?

Ну, с первым очевидно. Чтобы поместить что-то в историю, нужно создать новый объект-коммит в истории (который будет потомком текущего) и изменить указатель на последний коммит в ветке (который называется HEAD), на этот новый коммит. По большому счёту, для этого есть только две операции: commit и merge (слияние, которое в большинстве случаев будет приводить к появлению нового коммита). Очевидно, на сервере коммиты «вручную» никто не делает. И если на сервер просто передать вашу историю (новые коммиты), то это никак не изменит HEAD. Так что делать merge с вашими новыми коммитами сервер просто вынужден — у него просто нет другого варианта изменить HEAD.

Второй момент тоже не сложный, но не сразу очевиден. Почему сервер, делая merge, использует только fast-forward? Ведь есть же куча других замечательных методов, тот же octopus, но их сервер не использует. Почему? Всё очень просто: fast-forward это единственный вариант, который не порождает новых коммитов (помимо тех, что уже добавили вы). Ведь сервер не может порождать новые коммиты сам, так как для создания коммита git требует указания автора — сервер может только хранить ваши коммиты. И что ещё главнее — этот метод (fast-forward) при слиянии никогда не даёт конфликтов: просто по природе самого метода fast-forward конфликты при слиянии не возможны. Для сервера это важно, так как в случае возникновения конфликта решать его будет некому. На то он и конфликт, чтоб его решал человек, потому как компьютер разобраться не смог.

Понимание этих базовых вещей должно быть достаточно для «прояснения» ситуации. Но как она может возникнуть?

Всё очень просто: репозитарий используется более чем одним человеком (или одним, но на нескольких машинах), или же в локальном депозитарии был сделан rebase. В любом из этих вариантов возможно возникновение того, что отображено на второй картинке. И раз уж у вас такая ситуация возникла (то есть вы сделали «git fetch», и увидели, что всё так и есть), то давайте договоримся, что на сервере HEAD указывает на А (remotes/origin/master), на у вас локально — на Б (master).

Как же решить возникшую проблему? Вариантов два, и оба они приводят к тому, что А будет сливаться с таким коммитом (назовём его Х), для которго А будет предком. Как же этого добиться?

Вариант первый: слияние. Вам необходимо локально выполнить слияние вашего кода (Б) и того, который есть на сервере (А). Результатом слияния будет новый коммит в вашей локальной истории, тот самый Х — и сделать для него push сервер не откажется. Для убедительности — картинка:
......-В-x-x-x-A-
         \       \  <- операция слияния А в Б: git merge origin/master
          x-x-x-Б-X


Вариант второй: rebase. Rebase — это операция «переноса» части истории таким образом, чтобы изменить «корень» ветки, но не изменить саму ветку. Для наглядности — снова картинка. Начальное состояние — всё тот же рисунок 2, а состояние после rebase будет вот таким:
......-В-x-x-x-A
                \         <- операция rebase: git rebase origin/master
                 x-x-x-Б

В полученном результате роль коммита Х выполняет Б — но это уже другой Б, скажем так, Б'. В этом легко убедиться, посмотрев commit-id до rebase и после. Но как уже было сказано, сама ветка (то есть содержимое коммитов, её составляющих), не поменяется — поменяются только commit-id. Но цель достигнута — можно делать push.

Какой из вариантов выбрать — вам решать. В сети уже не одну стаю собак съели по этому поводу. Моя рекомендация для новичков — первый вариант, так как он проще и менее поджвержен ошибкам.

Есть и третий вариант решения, и именно его предложил defuz — forced push, например так:
git push origin +master

Он приведёт к тому, что сервер примет ваши коммиты, и безусловно изменит свой HEAD так, чтобы он указывал на последний из ваших коммитов — на Б. Таким образом, все «свои» коммиты, которых у вас нет (от В до А) сервер «забудет». Это и есть та самая потеря данных, о которой я говорил в начале. Тем не менее, иногда бывают случаи, когда это именно та операция, которая вам необходима. Но если у вас именно такой случай, и вы это понимаете, то вероятно эта статья вам уже давно не нужна.

Ну и чтоб уж расставить совсем все точки над и — по поводу ответа alekciy. Поскольку его вариант не использует forced push, то потери данных не будет. Но вполне очевидно, что после merge во-первых, нечего будет положить в stash (кроме новых файлов, которые ещё не под наблюдением git'а), а во-вторых, rebase уже не нужен (и если есть новые файлы — он обломится).

Надеюсь, этот материал прояснил происходящее и поможет решить возникшую проблему.

PS1: Для просмотра истории очень удобно использовать «gitk -a» (linux)
UPD: Terentich В Windows тоже прекрасно работает.
UPD: eyeofhell И в OSX это тоже работает :)
PS2: Stash (как впрочем и многие другие операции) — тема для отдельного разговора.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 50

    0
    О, пока вы тут. Подскажите, как правильно изменять историю коммитов (объединить несколько в один, например), чтобы потом не пришлось делать push с --force?
      +5
      «git rebase -i HEAD~5» даст возможность переделать историю последних 5-ти коммитов (что-то выкинуть, объединить несколько в один, или поменять местами). Но перед тем как это делать, настоятельно рекомендуется прочитать мануал по rebase («git help rebase»). Причём весь, т.к. rebase — весьма обширная команда, с приципиально разными возможностями в интерактивном и неинтерактивном вариантах.

      С большой вероятностью, первая попытка не сростётся, так что приготовтесь к «git rebase --abort» :) Но если цель избежать forced push — то перезапись истории в этом НЕ поможет.

      И если вы задаёте вопрос таким образом, то возникает ощущение, что сами не знаете, что надо спросить :) Единственный вариант дать нормальный ответ/совет в такой ситуации — это если вы расскажете ваш use-case, и что хочется получить в результате (а не «а как сделать Х?»).
        0
        Нененене. Про rebase -i HEAD~5 я знаю. Проблема в том, что после того как я затираю часть коммитов (тем же fixup), вся эта конструкция отказывается заливаться обратно на сервер без --force.
          0
          Смотрите ниже
            0
            Посмотрите на вот такой вариант. Ключевым является

            git merge -s ours old-feature

            который мержит старый вариант вашей текущей ветки в новый вариант, полученный после rebase, но при этом никаких изменений не добавляет (т.е. мерж фиктивный, только чтобы иметь возможность это дело впушить обратно).

            Такой способ позволяет сделать rebase, но сохранить возможность пушить без форса. Однако он больше подходит для ситуаций, когда действительно нужен rebase на обновленный upstream. Если же вам надо просто слепить несколько коммитов для более нарядного лога, то без форса не обойтись — как никак вы историю переписываете.
              0
              Тоже как бы способ, но опять же — без понимания происходящего возможна потеря информации. Такова цена фиктивности мерджа.
          +15
          GIT — система децентрализированная. Поэтому самый правильный ответ — вы можете изменять историю комитов как хотите. Но только до того, как эти коммиты попали на сервер. Ибо после того, как они попали на сервер — их мог скачать кто-то другой. У него — ещё кто-то. И т.д. В результате, если вы перепишете историю на сервере ( а push --force) сделает именно это, то все другие юзеры будут иметь те самые проблемы с «git push problem: non fast forward». Потому что они не знают, что на сервере кто-то переписал историю.

          Поэтому ещё раз — не меняйте историю, если вы уже успели запушить её куда-либо. Вы создадите кучу проблем другим.
            0
            Подписываюсь под каждым словом, с поправкой:

            Изменять историю можно и после push, делая потом forced push. Но при этом надо чётко понимать, что и где будет происходить — как у вас, так и на сервере, а особенно — у других пользователей. И надо чётко осознавать количество и уровень проблем, которые принесёт такой шаг (push, изменение истории, forced push).

            Иногда такой шаг и нужен, но лучше так действительно не делать. Особенно если ваш репозиторий публичный (например, на github).

            Но на практике необходимость в этом возникает крайне редко. У вас ведь локальный ропзиторий. Вы там — единоличный хозяин, и никакие изменения в нём (пока вы их не обнародовали) не могут помешать другим участникам. Веток можно наплодить сколько угодно. И смоделировать любую ситуацию — например, что будет после пуша на сервер. Просто завести еще одну ветку, назвать её например server, и мерджить туда. Хоть мульйон раз. Проверить всё, досконально. И только потом — заливать на сервак по-настоящему.
          +4
          На первых парах работы с Гитом меня частенько интересовал этот вопрос: Что б*я такое «git push problem: non fast forward»?
          Так что спасибо Вам от «меня из прошлого» :)
            –3
            Для меня на этом знакомство и закончилось.
            +2
            Ну тогда сделайте «git fetch --all» и потом «gitk -a» — сразу станет видно, какова ситуация у вас и на сервере. Эта картинка должна прояснить ситуацию полностью. Если не прояснит — заливайте картинку куда-нибудь, давайте ссылку и будем разбираться.

            PS: статью-то читали? ради ответа на такой вопрос она собственно и написана.
            +2
            Как автор того самого вопроса — Что мне в итоге делать то =) статья замечательная но ответа на мой вопрос, почему мне говорит при пуше нон фаст форвард при том что пулить нечего я так и не понял
              0
              Совет тот же — сделайте «git fetch --all» и потом «gitk -a», и всё станет видно. Если нет — показывате картинку сюда, разберёмся.
              0
              Проблема моя в том случае была такая — 2 человека работали в бранче девелопмент
              1 из них залил изменения
              потом «непонятным магическим образом» на сервере образовался мердж мастера и девелопмента от моего имени, хотя я абсолютно точно ничего такого не делал
              Более того — с самого утра того дня когда присутствует мердж ничего не мог запулить с проблемой — описанной в вопросе
              Решилось коммитом-отменой последнего мерджа с другого клона репозитория
                +2
                Ну вы же взрослый человек, и прекрасно понимаете, что бывает только «непонятный» вариант, а «магическим образом», да ещё и «само» — не бывает :) Если ничего не делать — то ничего не поменяется. Если что-то поменялось, значит таки кто-то что-то сделал.

                С одной стороны, все коммиты в git содержат всю информацию как об авторе (тот, кто создал исправление или новый код), так и о коммитере (тот, кто разместил это всё в истории проекта) — в общем случае, это могут быть разные люди.

                С другой стороны, сам git не заморачивается такими вещами, как аутентификация и авторизация. И потому «прикинуться» кем-то не проблема: если кто-то в своём ~/.gitconfig указал ваши данные, то в истории всё будет выглядеть так, как будто коммит именно вы и сделали. Так что разбирайтесь.

                PS: кстати, из последнего можно и выгоду извлечь. Например, можно в user/email указывать не свой реальный email, а username@host. Например, так: cub@moon, cub@saturn и т.д. Или так: cub+git.moon@gmail.com, cub+git.saturn@gmail.com — вариантов неограниченное количество. Таким образом, можно будет понять с какой именно машины в историю закоммитились отдельные коммиты.
                  0
                  Это да, но админ гит сервера я и нас всего 4 человека, так что вариант с подставой отпадает я гарантируюю это

                  я вот думаю а не мог я как нибудь пушем сделать мердж? указав напримерно неверно таргет ветку или что то другое
                    0
                    Вы для начала определитесь, когда у вас была ошибка (во время push или pull), и какая именно (т.к. pull про non-fast-forward ругаться не будет).

                    Предполагаю, что чуть выше вы ошиблись, и хотели написать «ничего не мог запушить».

                    В таком случае повторюсь — сервер таки делает мердж, но только с использованием fast-forward метода. А это значит, что никаких конфликтов быть не может, и у вас проблемы «ничего не мог запушить» не возникло бы.

                    Другое дело, если запушил кто-то, и вы не вытащили его апдейты, и попытались запушить свои. В таком случае вы 100% получите ошибку, и не какую-нибудь, а именно эту. Этот случай и способы его решения описаны в статье.

                    Но это всё градание на кофейной гуще. Давате факты (хотябы скриншот в «gitk -a») с историей, и будем говорить предметно.
                      0
                      да конечно я дам гитк как буду на работе
                      имелось в виду что запушить не мог
                  0
                  А какой комментарий был для этого мерджа? Уже не с кучей ли id-шников?
                  +2
                  К автору статьи- очень интересно и подробно все расписано — а можете еще про stash так же рассказать =)
                    0
                    Ну просто так писать времени не особо, потому всячески приветствуется инициатива по задаванию конкретных вопросов — чтоб не писать «в общем и целом», а сделать реально подезную статью.
                    +2
                    Мы сейчас отучиваем сотрудников от варианта а в пользу варианта б (git используется примерно полгода). Force push сделать невозможно (нет прав). В перспективе возможно ограничим на этапе push мерджи из Active в Active (вариант а).
                      0
                      Мы сейчас отучиваем сотрудников от варианта а в пользу варианта б (git используется примерно полгода). Force push сделать невозможно (нет прав). В перспективе возможно ограничим на этапе push мерджи из Active в Active (вариант а).
                        0
                        Мы сейчас отучиваем сотрудников от варианта а в пользу варианта б (git используется примерно полгода). Force push сделать невозможно (нет прав). В перспективе возможно ограничим на этапе push мерджи из Active в Active (вариант а).
                          0
                          Мы сейчас отучиваем сотрудников от варианта с merge в пользу варианта с rebase (git в компании используется примерно полгода). Force push сделать невозможно (нет прав). В перспективе возможно ограничим на этапе push мерджи из Active в Active (вариант а).
                            0
                            И получаете урезанный SVN с плюшкой в виде децентрализации? Однако :)
                              0
                              Похоже я привел недостаточно деталей:
                              — force push запрещен для разработчиков, чтобы избежать переписывания истории на сервере. При push-е ошибочных комитов рекомендуется push-ить откатывающие коммиты. Гипотетически, если историю все-таки нужно переписать на сервере (например удалить файл, который обязан отсутствовать в репозитории в принципе), нужно просить об этом админов
                              — у нас нет и не предвидится ограничений на создание feature/developer/release веток (есть только определенные ограничения на имена веток). Конечно, для подобных веток rebase после push не делается (это было бы переписыванием истории на сервере)
                              — для работы с активной веткой (master) не рекомендуется делать merge при pull в пользу rebase при pull. Это делается для того, чтобы избежать ничем неоправданных ромбиков в истории на сервере, которые сильно ухудшают ее читабельность.

                              Мне кажется не очень ограничительным запрет на переписывание опубликованных коммитов и рекомендации линеаризовывать неопубликованные изменения перед push. Все остальные плюшки остаются.
                                0
                                Да, третий пункт всё расставляет по местам.
                            +1
                            Всем советую известную книжку Pro Git (http://progit.org/). Онлайн она бесплатна. Есть русский перевод на том же сайте, но я читал по-английски, поэтому о его качестве судить не могу.
                            Читается быстро, легко и с интересом. Правда.
                            Когда прочитаете, большинство вопросов отпадут сами собой.
                              +1
                              Есть еще полезная практика, чтобы не порождать пачку мерджей делать git pull --rebase и потом уже push
                                +1
                                В любом случае — хоть «git pull --rebase», хоть «git rebase origin/master» — будет выполняться операция rebase, которая для начинающих может оказаться сложной, а в случае конфликтов во время rebase — так и вообще неподъемной или черезмерно запутанной.

                                Вообще, на мой взгляд, натуральное применение для rebase — это в первую очередь приведение истории к «красивому» виду. И если а) вы новичок или б) вам «ехать, а не шашечки» — то про rebase можно вообще забыть и не использовать совсем. И ваши волосы... Ну, вы поняли :)
                                  –1
                                  как же вам отребейзить ваш бранч относительно текущего мастера, без git rebase origin/master
                                    0
                                    Как-то не понятно, а в чём суть вашего вопроса?

                                    Если его понимать буквально, то ответ есть в статье. Но вы ведь в курсе, вы же статью прочитали?..

                                    А если не буквально — так никто ж запрет на rebase не накладывал, это всего лишь рекомендации, это же очевидно.
                                      0
                                      ну вы говорите что rebase сложен для новичка, но это базовый функционал, наравне с push, pull, merge
                                      это как сказать
                                      «вам «ехать, а не шашечки» — то про PUSH можно вообще забыть и не использовать совсем.» но звучит это как то странно… я просто не понимаю как можно обойтись без одной из основ гита… я лично делаю «git rebase -i origin/master» примерно так же часто как и «git push»
                                0
                                Может кто из знающих подскажет для Ubuntu аналог TortoiseGit. С такими же удобными инструментами просмотра лога, коммита и разрешения конфликтов.
                                Пока приходится использовать gitk и cola, но их интерфейс далеко не то, что мне нужно.
                                  +1
                                  Git Extensions очень хорошо зарекомендовал себя под windows (мне нравится больше черепашки).

                                  На сайте проекта утверждается следующее: «Git Extensions runs on multiple platforms using Mono.»
                                  –1
                                  Спасибо, очень хорошо описано.
                                  Что могу из конструктивной критики сказать. Вот об этот фрагмент взгляд очень сильно спотыкается:

                                  Он возможен только в том случае, если текущий коммит (тот, к которому будет происходить слияние, обозначим его А), является предком «сливаемого» коммита (тот, с которым будет происходить слияние, обозначим его Б):


                                  ИМХО можно как-то попроще переписать без «к которому», «с которым», «сливаемого». Например, используя термины «начальный коммит» и «конечный коммит»:

                                  Он возможен только в том случае если начальный коммит «А» является предком конечного коммита «Б» («начальный» и «конечный» означают, что в результате merge мы хотим начальные исходники с цепочкой коммитов, заканчивающейся на «A», превратить в исходники с цепочкой коммитов, которая будет заканчиваться на «Б»)
                                    0
                                    Я не журналист, потому стилистика может и хромает… но мне, как технарю, мой вариант понятнее. Да и другий технари пока не жаловались, в том чиле и в личку. Но ежели плюсов за ваш комментарий зашкалит — придумаем что-нибудь компромисное :)
                                    0
                                    Mercurial смотрит на эту «проблему» с недоумением. Он просто спрашивает «эй, парень, ты уверен, что хочешь создать новую голову на удалённом репозитории? а то делай merge или rebase. или если очень надо, то форсируй (-f) (но и в этом случае совсем ничего не потеряется)». И термин «fast-forward» у него отстутствует за ненадобностью.
                                      0
                                      скучно и неинтересно. ни тебе превозмоганий, ни тебе challenge.
                                      0
                                      >Есть и третий вариант решения, и именно его предложил defuz — forced push, например так:
                                      >git push origin +master

                                      Страшные вещи вы говорите, получается кто то практикует forced push для master, это очен-очень -очень плохо, если только это не форс-мажор…
                                        +1
                                        «Тем не менее, иногда бывают случаи, когда это именно та операция, которая вам необходима. Но если у вас именно такой случай, и вы это понимаете, то вероятно эта статья вам уже давно не нужна.»

                                        Вы же дочитали до этого места?..

                                        Как уже было упомянуто, Git — система децентрализованная, что тоже важно. Но если вы один на один со своим репозитарием — то конечно сам себе хозяин, и помешать кому-то ещё — тяжело :)

                                        А для новичков, читающих статью для повышения своего уровня — да, рекомендация именно такая: по возможности не использовать forced push и rebase.
                                          0
                                          вот я и говорю, формулировка «по возможности не использовать forced push и rebase» применительно к мастеру должна звучать примерно так «никогда не ребазируйте ветку master, слышите НИКОГДА! „

                                          на счет других веток — “Ребейзь @ Мощно пуш» без ограничений ну и конечно надо коллег предупреждать если кто то с этой веткой ещё работает. Всё никаких больше ограничений.
                                        +4
                                        PS1: Для просмотра истории очень удобно использовать «gitk -a» (linux)

                                        Почему только Linux? В Windows тоже прекрасно работает.
                                          0
                                          Ну я не использую Windows, потому не в курсе, и написал только о том, что точно знаю сам.
                                          Спасибо за информацию, внёс в статью.
                                            +1
                                            … И в OSX это тоже работает :). Кроссплатформенность, однако.
                                              0
                                              И это тоже добавил, спасибо.
                                                0
                                                Для OSX есть gitx, который симпатичнее и удобнее.
                                                  0
                                                  SourceTree мне нравится больше

                                          Only users with full accounts can post comments. Log in, please.