git fast forward что такое
Режим fast-forward в Git
April 17, 2015
Слияние ( merge ) ветвей является весьма распространенной и обыденной операцией, выполняемой в системе Git. Однако, в некоторых случаях Git выполняет слияние ветвей в режиме fast-forward. Но что такое режим fast-forward и чем он отличается от обычного режима слияния ветвей.
Давайте разберем этот вопрос на конкретном примере. Допустим, при работе в репозитории мною была создана ветка speedup из текущей ветки master :
Поработав некоторое время в этой ветке, я создал несколько commit ‘ов (три коммита, три белых кружка на рисунке):
После этого я решил, что работа в ветке speedup мною выполнена и я сделал push этой ветки на свой удаленный репозиторий.
В ситуациях подобного рода система Git всегда старается применить режим fast-forward, если это возможно. Однако, такое поведение Git можно легко изменить на режим no fast-forward и сделать его поведением системы по умолчанию.
Внимательно посмотрите на приведенный выше рисунок. На нем хорошо видно, что некоторые слияния ( merge ) веток могут быть выполнены в режиме fast-forward. Но своеобразный подход GitHub к слиянию веток привел к тому, что линейная история коммитов была превращена в нечто похожее на рисунок железнодорожного пути.
Если в нескольких словвах подвести общий итог вышесказанного, то он окажется следующим.
Режим no fast-forward хранит всю информацию о слияниях веток. Такой подход может оказаться запутанным и сложным, если необходимо прочитать историю коммитов.
С другой стороны, режим fast-forward хранит не всю информацию о слияниях веток. Такой подход более простой для чтения, но в этом случае становится неочевидным история веток проекта.
Объяснение полезных Git команд с помощью визуализации
Это перевод замечательной статьи Лидии Холли, где она объясняет основные гит команды в очень понятном виде с помощью визуализаций.
Я подумала, что это будет идеальный вариант для создания наглядных примеров наиболее распространенных и полезных команд! Многие из описываемых мной команд имеют необязательные аргументы, которые вы можете использовать для изменения их поведения. В моих примерах я расскажу о поведении команд по умолчанию, не добавляя (слишком много) параметров конфигурации! 😄
Merge
Наличие нескольких веток чрезвычайно удобно для того, чтобы новые изменения были отделены друг от друга, а также чтобы вы случайно не запушили несанкционированные или поврежденные изменения в продакшин. Как только изменения будут одобрены, мы хотим получить эти изменения в нашей прод ветке!
Fast-forward merge когда текущая ветка не имеет дополнительных коммитов по сравнению с веткой, которую мы мержим. Git у нас ленив и сначала попытается выполнить самый простой вариант: Fast-forward! Этот тип менжа не создает новый коммит, а скорее объединяет коммит(ы) в ветку, которую мы объединяем прямо в текущей ветке
Хорошо, если ваша текущая ветка не имеет каких-либо дополнительных коммитов по сравнению с веткой, которую вы хотите смержить, но, к сожалению, это случается редко! Если мы зафиксировали изменения в текущей ветке, которых нет в ветке, которую мы хотим объединить, git выполнит объединение без fast-forward merge. При слиянии без fast-forward Git создает новый коммит мержа в активную ветку. Родительский коммит указывает на активную ветку и ветку, которую мы хотим объединить!
Merge конфликты
Хотя Git хорошо решает, как объединять ветки и добавлять изменения в файлы, он не всегда может принять это решение сам по себе. Это может произойти, когда две ветки, которые мы пытаемся смержить, имеют изменения в одной строке в одном и том же файле, или если одна ветка удалила файл, который изменила другая ветка, и так далее.
В этом случае Git попросит вас помочь решить, какой из двух вариантов мы хотим сохранить. Допустим, что в обеих ветках мы отредактировали первую строку в файле README.md.
При попытке объединить ветки, Git покажет вам, где происходит конфликт. Мы можем вручную удалить изменения, которые не хотим сохранять, сохранить изменения, снова добавить файл и закоммитить изменения.
Rebase
Git rebase копирует коммиты из текущей ветки и помещает эти скопированные коммиты поверх указанной ветки.
Отлично, теперь у нас есть все изменения, которые были сделаны в master ветке и в dev ветке. 🎊
Большая разница по сравнению с мержем заключается в том, что Git не будет пытаться выяснить, какие файлы сохранить и не сохранить. В ветке, которую мы обновляем, всегда есть последние изменения, которые мы хотим сохранить! Таким образом, вы не столкнетесь ни с какими мерж конфликтами и у вас будет хорошая линейная история.
Этот пример показывает rebase в master ветке. Однако в больших проектах вы обычно не захотите этого делать. Git rebase изменяет историю проекта, поскольку для скопированных коммитов создаются новые хэши.
Rebase отлично подходит, когда вы работаете над feature branch, а master ветка была обновлена. Вы можете получить все обновления в своей ветке, которые предотвратят будущие merge конфликты 😄
Interactive Rebase
Перед rebas’ом коммитов мы можем их изменить! 😃 Мы можем сделать это с помощью Interactive Rebase. Interactive Rebase также может быть полезен для ветки, над которой вы сейчас работаете, и хотите изменить некоторые коммиты.
Есть 6 действий, которые мы можем выполнить над коммитами, которые мы rebas’им:
Таким образом, мы можем иметь полный контроль над нашими коммитами. Если мы хотим удалить коммит, мы можем просто drop’нуть его.
Или, если мы хотим объединить несколько коммитов вместе, чтобы получить более чистую историю, нет проблем!
Interactive rebasing дает вам большой контроль над коммитами, которые вы пытаетесь rebase’нуть, даже в текущей активной ветке.
Reset
git reset избавляет от всех текущих промежуточных файлов и дает нам контроль над тем, куда должен указывать HEAD.
Soft reset
Soft reset перемещает HEAD к указанному коммиту (или индексу коммита по сравнению с HEAD), не избавляясь от изменений, которые были внесены в коммиты позже.
Набрав git status, вы увидите, что у нас все еще есть доступ ко всем изменениям, которые были сделаны во время предыдущих коммитов. Это здорово, так как это означает, что мы можем исправить содержимое этих файлов и закоммитить их позже.
Hard reset
Иногда мы не хотим сохранять изменения, внесенные некоторыми коммитами. В отличие от soft reset, нам не нужно больше иметь к ним доступ. Git должен просто сбросить свое состояние обратно туда, где он был в указанном коммите: это даже включает изменения в вашей working directory и stage файлах! 💣
Git отменил изменения, которые были внесены в 9e78i и 035cc, и сбросил свое состояние до того, где он был при коммите ec5be.
Revert
Cherry-pick
Когда определенная ветка содержит коммит, который внес изменения, которые нам нужны в нашей активной ветке, мы можем черипинуть коммит в нашу ветку. Cherry-pick создает новый коммит в нашей активной ветке, который содержит изменения, которые были в чери-пикнутом коммите.
Fetch
Если у нас есть remote Git ветка, например ветка на Github, может случиться так, что remote ветка имеет коммиты, которых нет у текущей ветки! Возможно, другая ветка была объединена, или ваш коллега добавил hot fix и так далее.
Мы можем получить эти изменения локально, выполнив git fetch на remote ветке. Это никак не влияет на вашу локальную ветку: fetch просто загружает новые данные.
Reflog
Все делают ошибки, и это совершенно нормально! Иногда может показаться, что вы испортили свой репозиторий Git настолько сильно, что просто хотите полностью его удалить.
Если вы допустили ошибку, вы можете легко отменить ее, сбросив HEAD на основе информации, которую нам предоставляет reflog.
Как мы видим, последнее действие было перенесено в рефлог.
Тонкости благополучного git-merge
Вступительное слово
Основными командами пользовательского уровня для ветвления в Git являются git-branch, git-checkout, git-rebase, git-log и, конечно же, git-merge. Для себя я считаю git-merge зоной наибольшей ответственности, точкой огромной магической энергии и больших возможностей. Но это достаточно сложная команда, и даже достаточно длительный опыт работы с Git порой бывает недостаточным для освоение всех ее тонкостей и умения применить ее наиболее эффективно в какой-либо нестандартной ситуации.
Попробуем же разобраться в тонкостях git-merge и приручить эту великую магию.
Здесь я хочу рассмотреть только случай благополучного слияния, под которым я понимаю слияние без конфликтов. Обработка и разрешение конфликтов — отдельная интересная тема, достойная отдельной статьи. Я очень рекомендую так же ознакомиться со статьей Внутреннее устройство Git: хранение данных и merge, содержащей много важной информации, на которую я опираюсь.
Анатомия команды
Если верить мануалу, команда имеет следующий синтаксис:
По большому счету, в Git есть два вида слияния: перемотка (fast-forward merge) и «истинное» слияние (true merge). Рассмотрим несколько примеров обоих случаев.
«Истинное» слияние (true merge)
Мы отклоняемся от ветки master, чтобы внести несколько багов улучшений. История коммитов у нас получилась следующая:
Выполним на ветке master git merge feature :
Посмотрим историю коммитов в тестовом репозитории, который я создал специально для этого случая:
А теперь посмотрим информацию о коммите (M):
Мы видим двух родителей, объект-дерево, соответствующее данному состоянию файлов репозитория, а так же информацию о том, кто виновен в коммите.
Посмотрим, куда ссылается указатель master:
Действительно, он теперь передвинут на коммит (M).
Squash и no-commit
В случае применения такого слияния коммиты ветки feature не будут включены в нашу историю, но коммит Sq будет содержать все их изменения:
Позже, в случае выполнения «классического» git merge feature можно исправить это. Тогда история примет следующий вид:
Перемотка (fast-forward merge)
Рассмотрим другой случай истории коммитов:
Все как и в прошлый раз, но теперь в ветке master нет коммитов после ответвления. В этом случае происходит слияние fast-forward (перемотка). В этом случае отсутствует коммит слияния, указатель (ветка) master просто устанавливается на коммит Y, туда же указывает и ветка feature:
Стратегии слияния
Стратегия resolve
Здесь C — общий коммит двух веток, дерево файлов, соответствующее этому коммиту, принимается за общего предка. Анализируются изменения, произведенные в ветках master и feature со времен этого коммита, после чего для коммита (M) создается новая версия дерева файлов в соответствии с пунктами 4 и 5 нашего условного алгоритма.
Стратегия recursive
Для иллюстрации этой стратегии позаимствуем пример из статьи Merge recursive strategy из блога «The plasticscm blog»:
Итак, у нас есть две ветки: main и task001. И так вышло, что наши разработчики знают толк в извращениях: они слили коммит 15 из ветки main с коммитом 12 из ветки task001, а так же коммит 16 с коммитом 11. Когда нам понадобилось слить ветки, оказалось, что поиск реального предка — дело неблагодарное, но стратегия recursive с ее конструированием «виртуального» предка нам поможет. В результате мы получим следующую картину:
]
Выполнение рекурсивного слияния с этой опцией будет более продвинутым вариантом стратегии subtree, где алгоритм основывается на предположении, как деревья должны совместиться при слиянии. Вместо этого в этом случае указывается конкретный вариант.
Стратегия octopus
Эта стратегия используется для слияние более чем двух веток. Получившийся в итоге коммит будет иметь, соответственно, больше двух родителей.
Данная стратегия предполагает большую осторожность относительно потенциальных конфликтов. В связи с этим порой можно получить отказ в слиянии при применении стратегии octopus.
Стратегия ours
Не следует путать стратегию ours и опцию ours стратегии recursive.
Стратегия ours — более радикальное средство.
Стратегия subtree
Для иллюстрации данной стратегии возьмем пример из главы Слияние поддеревьев книги «Pro Git».
Добавим в наш проект новые удаленный репозиторий, rack:
Ясно, что ветки master и rack_branch имеют абсолютно разные рабочие каталоги. Добавим файлы из rack_branch в master с использованием squash, чтобы избежать засорения истории ненужными нам фактами:
Теперь файлы проекта rack у нас в рабочем каталоге.
Что такое «git push problem: non fast forward»
Данная мини-заметка в первую очередь является ответом на вопрос. Так как мой аккаунт read-only, то вот такой вот способ ответа. «А жизнь-то налаживается!» ©
Первый вывод после прочтения вопроса и ответов — не делайте так, как предложил defuz. Он не понимает суть проблемы, и если вы сделаете как им предложено — скорее всего, вы потеряете данные.
Второй: alekciy тоже не совсем прав, но тут шансов на потерю данных гораздо меньше. Почти никаких.
Ну и третий: блин, ну когда же люди поймут, что владеть используемым инструментом это реально необходимо? Читайте документацию!
Скажу сразу, многие детали и «внутренности» работы git’а опущены либо упрощены, но не в ущеб пониманию. Именно появление понимания и цель статьи, и если оно у вас уже есть, и вы уверены, что оно верное 🙂 — то ничего нового для вас тут не будет. Если же понимания пока нет — то поехали!
Для начала, надо выяснить, что такое fast-forward. Это просто один из вариантов (методов, стратегий) выполнения операции merge. Он возможен только в том случае, если текущий коммит (тот, к которому будет происходить слияние, обозначим его А), является предком «сливаемого» коммита (тот, с которым будет происходить слияние, обозначим его Б). Графически это выглядит так (х — просто другие, не важные нам коммиты):
Если история коммитов выглядит вот так:
то выполнить слияние от А к Б методом 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 сервер не откажется. Для убедительности — картинка:
Вариант второй: rebase. Rebase — это операция «переноса» части истории таким образом, чтобы изменить «корень» ветки, но не изменить саму ветку. Для наглядности — снова картинка. Начальное состояние — всё тот же рисунок 2, а состояние после rebase будет вот таким:
В полученном результате роль коммита Х выполняет Б — но это уже другой Б, скажем так, Б’. В этом легко убедиться, посмотрев commit-id до rebase и после. Но как уже было сказано, сама ветка (то есть содержимое коммитов, её составляющих), не поменяется — поменяются только commit-id. Но цель достигнута — можно делать push.
Какой из вариантов выбрать — вам решать. В сети уже не одну стаю собак съели по этому поводу. Моя рекомендация для новичков — первый вариант, так как он проще и менее поджвержен ошибкам.
Есть и третий вариант решения, и именно его предложил defuz — forced push, например так:
git push origin +master
Он приведёт к тому, что сервер примет ваши коммиты, и безусловно изменит свой HEAD так, чтобы он указывал на последний из ваших коммитов — на Б. Таким образом, все «свои» коммиты, которых у вас нет (от В до А) сервер «забудет». Это и есть та самая потеря данных, о которой я говорил в начале. Тем не менее, иногда бывают случаи, когда это именно та операция, которая вам необходима. Но если у вас именно такой случай, и вы это понимаете, то вероятно эта статья вам уже давно не нужна.
Ну и чтоб уж расставить совсем все точки над и — по поводу ответа alekciy. Поскольку его вариант не использует forced push, то потери данных не будет. Но вполне очевидно, что после merge во-первых, нечего будет положить в stash (кроме новых файлов, которые ещё не под наблюдением git’а), а во-вторых, rebase уже не нужен (и если есть новые файлы — он обломится).
Надеюсь, этот материал прояснил происходящее и поможет решить возникшую проблему.
Раскладываем Git по полочкам: терминология
Что это за зверь?
Git — распределённая система управления версиями. Git поддерживает быстрое разделение и слияние версий, включает инструменты для визуализации и навигации по нелинейной истории разработки.
Ничего не понятно, давай еще раз
Какие такие версии?
Версия — это состояние файла (или нескольких файлов) в какой-то конкретный момент времени. Например, пустой файл (1), тот же файл с каким-то текстом (2) и этот же файл, в котором была исправлена опечатка (3) — три разные версии одного файла, которые были получены последовательной модификацией (изменением) файла.
Системы чего.
Система управления версиями — программа, позволяющая сохранять состояние файлов (те самые версии), возвращаться к ранее сохраненному состоянию, сохранять последовательность изменений внесенных в файлы, отменять или заново применять эти изменения, отслеживать авторство изменений.
Что там с этими версиями делают?
Разделение версий — независимые изменения одного файла.
Версионность на примере текстового файла
Например, у нас есть файл с каким-то текстом (версия этого файла). Файл отправляется на проверку, там обнаруживается и исправляется опечатка (получаем новую версию файла). Независимо от этого в старый (неисправленный) файл дописывается еще что-то (получаем еще одну версию этого файла). Т.е., на данный момент у нас есть два разных файла (две версии одного файла), которые были независимо друг от друга созданы на основе одной общей версии.
Слияние версий — объединение двух и более независимых версий. Для примера выше, слиянием будет объединение двух существующих версий нашего файла в одну — файл, в котором будет и новый текст, и исправленная опечатка.
А при чем тут история?
История разработки — совокупность всех версий файлов, над которыми ведется работа. Историей разработки в данном случае будет список изменений:
добавление изначального текста
добавление нового текста
объединение двух версий файла (при выполнении слияния)
Нелинейная история — история, в которой изменения вносятся не одно за другим последовательно, а может быть внесено несколько независимых изменений на основе одной версии файла (исправление опечатки и добавление нового текста). Т.е. мы создаем две параллельные истории изменений файла.
Немного терминологии
Репозиторий (repository) — совокупность файлов, состояние которых отслеживается, и история их изменений. По факту, репозиторий — это проект, над которым ведется работа, и все изменения в этом проекте. Для отслеживания состояния файла его необходимо добавить в репозиторий.
Коммит (commit) — сохраненное состояние (версия) файлов репозитория.
Ветка (branch) — последовательность коммитов (история изменения состояния репозитория). Каждый коммит в ветке имеет «родителя» (parent commit) — коммит, на основе которого был получен текущий. В репозитории может быть несколько веток (в случаях, когда к одной версии репозитория применяется несколько независимых изменений).
HEAD — указатель на текущий коммит (указатель на состояние, в котором репозиторий находится на данный момент).
Мастер (master, main) — основная ветка репозитория, создается автоматически при создании репозитория.
Мердж (слияние, merge) — объединение двух или более веток. В процессе мерджа изменения с указанной ветки переносятся (копируются) в текущую.
Целевая ветка мерджа — ветка, изменения с которой объединяются с текущей веткой.
База слияния (merge base) — последний общий коммит двух веток.
Мердж коммит (merge commit) — коммит, который создается автоматически по завершению процесса слияния веток. Мердж коммит содержит в себе все изменения целевой ветки мерджа, которые отсутствуют в текущей (все коммиты целевой ветки, которые начиная с базы слияния, но не включая её).
Слияние перемоткой (fast-forward merge) — слияние веток, при котором в текущей ветке отсутствуют новые коммиты (последний коммит текущей ветки является базой слияния). При таком мердже текущая ветка просто переходит в состояние целевой ветки (указатель HEAD переносится на последний коммит целевой ветки). Мердж коммит при этом не создается.
Слияние без перемотки (non fast-forward merge) — слияние, при котором новые коммиты (относительно базы слияния) присутствуют как в текущей, так и в целевой ветках.
В пустом репозитории, в основной ветке, создаем пустой файл, добавляем в репозиторий (теперь git будет отслеживать состояние файла) и коммитим (коммит А).
Добавляем в файл текст, коммитим еще раз (коммит B).
Создаем новую ветку (как бы копируя состояние репозитория), вносим изменения в файл и снова коммитим (коммит С).
Возвращаемся на предыдущую ветку (теперь репозиторий находится в состоянии, которое сохранилось при коммите B, без изменений, которые вносились в новой ветке).
Вносим новые изменения, создаем новый коммит (D).
История изменений при создании новой ветки
После этого новая ветка останется в состоянии С (со своими собственными изменениями), а в основной ветке будут изменения из обеих веток (изменения, внесенные коммитами С и D). Эта ветка перейдет в состояние Е.
Возможна и обратная ситуация, когда изменения из основной ветки вливаются в новую (мердж производится из новой ветки).
При этом мы можем вернуться в новую ветку и продолжить работу в ней (внести новые изменения и создать коммит F).
История изменений после слияния и нового коммита
Мердж конфликт (merge conflict) — ситуация, когда при слиянии веток в один или несколько файлов вносились независимые изменения. В некоторых случаях (например, если изменялись разные, не пересекающиеся части одного файла) git способен самостоятельно решить, как выполнять слияние таких файлов. Если автоматически это сделать не удалось — возникает конфликт. В таком случае необходимо самостоятельно указать, как выполнять слияние конфликтующих версий (решить конфликт, resolve merge conflict). Изменения, внесенные в процессе решения конфликта автоматически попадают в мердж коммит.
Чекаут (checkout) — переход на другое (существующее) состояние репозитория (на другой коммит или ветку). При этом все файлы в репозитории возвращаются в состояние, в котором они находились на момент указанного коммита. Если перед переходом в репозиторий были внесены изменения, которые были добавлены в репозиторий, но не попали в коммит — они будут перенесены «поверх» состояния после перехода. Как и при мердже, git попробует применить эти изменения к новому состоянию автоматически, при неудаче — возникает конфликт и изменения необходимо применить вручную.
Дифф (diff) — разница двух состояний (коммитов, веток, подготовленных или модифицированных файлов).
Трехсторонний дифф (three-way diff) — дифф, возникающий при мердже и решении конфликтов. Является разницей трех состояний: состояния репозитория в текущей ветке, состояния в целевой ветке слияния и общего состояния между этими ветками (состояния в базе слияния).
Черри-пик (cherry-pick) — процесс добавления в текущую ветку одного (или нескольких) коммитов из другой ветки, без необходимости выполнять слияние веток.
Реверт (revert) — отмена внесенных изменений (коммита или группы коммитов). В процессе реверта создается дополнительный коммит, который так же можно отменить при необходимости (вернув репозиторий в изначальное состояние). Реверт мердж коммита позволяет отменить выполненное ранее слияние веток.
Ребейз (rebase) — перенос изменений текущей ветки «поверх» другой ветки. При этом все коммиты текущей ветки, которых нет в целевой, удаляются из текущей и заново создаются в целевой ветке (последовательно применяются к состоянию в целевой ветке). Поскольку ребейз пересоздает коммиты заново и меняет существующую историю, его использование не рекомендуется при командной разработке. Ребейз в ветке, над которой работает несколько человек, может привести к потере чужих изменений и/или невозможности корректно выполнить слияние.
В данном случае коммит Е содержит изменения, которые вносились с момента последнего слияния или создания ветки (с момента коммита В). Т.е. коммит Е содержит в себе изменения из D. При выполнении ребейза git понимает, что эти изменения (E) уже присутствуют в целевой ветке (и окажутся в текущей после ребейза), потому нет необходимости копировать коммит Е, дублируя этим D.
Командная разработка
Наличие удаленного репозитория может быть полезным и при одиночной разработке: оно позволяет синхронизировать состояние проекта на разных компьютерах и просто сохранить проект на внешнем сервере.
Есть два варианта синхронизации изменений:
пулл (pull) — слияние состояния удаленного репозитория и локального (обычно — в отдельной ветке). Пулл может выполняться как для одной и той же ветки (с одинаковым именем), так и для разных. Пулл являет собою обычный мердж, но целевая ветка при этом находится не в том же репозитории, в котором выполняется пулл, а в удаленном. Как следствие, при пулле так же создается мердж коммит, пулл можно отменить (заревертить) и в его процессе может возникнуть мердж конфликт.
пуш (push) — обратный пуллу процесс. При пуше изменения из локального репозитория переносятся в удаленный. Пуш обновляет состояние текущей ветки в удаленном репозитории и не является мерджем (не создает дополнительные коммиты и не может привести к конфликтам). Если в ветке удаленного репозитория присутствуют коммиты, которых нет в локальном репозитории, сигнализируется ошибка о несовпадении истории изменений (non fast-forward merge), пуш выполнить не получится. В таком случае необходимо сначала синхронизировать состояние локального репозитория (получить недостающие коммиты с помощью пулла), и только после этого повторить процесс пуша.
Нередко возникает необходимость обновить информацию о состоянии удаленного репозитория (существующих ветках и коммитах в них) без выполнения слияния (пулла). Такой процесс называется фетчем (fetch). Таким образом, пулл является комбинаций фетча и мерджа: сперва обновляется информация о состоянии целевой ветки в удаленном репозитории, а затем ее изменения вливаются в текущую ветку в локальном репозитории.
основные понятия и термины
простые примеры терминов
применение git’a при командной разработке
некоторые проблемы, которые могут возникать при использовании git’a