Git перенос файла из одной ветки в другую
Rebase - команда предназначенная для редактирования коммитов в git. Часто ее воспринимают как альтернативу команде git merge, однако это не так. Хотя команда и позволяет переносить коммиты, мержиться все равно придется обычной командой merge.
Перенос коммитов (rebase)
Основной режим работы команды - это перенос коммитов, он позволяет выбрать группу коммитов и перенести (пересадить) их в конец ветки. Такой перенос коммитов позволяет подтянуть в feature-ветке свежие изменения из master. Такая ситуация часто возникает когда над проектом работает больше одного человека. Второй разработчик успевает замержить свои изменения раньше ваших, и вам остается либо подмерживать master-ветку себе, либо пользоваться командой rebase. Подмерживание коммитов приводит к усложенению истории и созданию лишних merge-коммитов. Если же воспользоваться командой rebase, то ваши изменения логически и хронологически будут расположены после коммитов коллеги.
Наглядно работа команды выглядит следующим образом (git rebase feature/JIRA-123 –onto master)
Более интересные перемещения
Также возможно сделать так, чтобы при перебазировании воспроизведение коммитов применялось к совершенно другой ветке. Для примера возьмём История разработки с тематической веткой, ответвлённой от другой тематической ветки. Вы создаёте тематическую ветку server , чтобы добавить в проект некоторую функциональность для серверной части, и делаете коммит. Затем вы выполнили ответвление, чтобы сделать изменения для клиентской части, и создали несколько коммитов. Наконец, вы вернулись на ветку server и сделали ещё несколько коммитов.
Предположим, вы решили, что хотите внести изменения клиентской части в основную линию разработки для релиза, но при этом не хотите добавлять изменения серверной части до полного тестирования. Вы можете взять изменения из ветки client , которых нет в server ( C8 и C9 ), и применить их на ветке master при помощи опции --onto команды git rebase :
В этой команде говорится: «Переключись на ветку client , найди изменения относительно ветки server и примени их для ветки master ». Несмотря на некоторую сложность этого способа, результат впечатляет.
Теперь вы можете выполнить перемотку (fast-forward) для ветки master (см Перемотка ветки master для добавления изменений из ветки client ):
Представим, что вы решили добавить наработки и из ветки server . Вы можете выполнить перебазирование ветки server относительно ветки master без предварительного переключения на неё при помощи команды git rebase , которая извлечёт тематическую ветку (в данном случае server ) и применит изменения в ней к базовой ветке ( master ):
Это повторит работу, сделанную в ветке server поверх ветки master , как показано на рисунке:
После чего вы сможете выполнить перемотку основной ветки ( master ):
Теперь вы можете удалить ветки client и server , поскольку весь ваш прогресс уже интегрирован и тематические ветки больше не нужны, а полную историю вашего рабочего процесса отражает рисунок Окончательная история коммитов:
Основы ветвления
Предположим, вы работаете над проектом и уже имеете несколько коммитов.
Это то же самое что и:
Вы работаете над своим сайтом и делаете коммиты. Это приводит к тому, что ветка iss53 движется вперед, так как вы переключились на нее ранее ( HEAD указывает на нее).
Но перед тем как сделать это — имейте в виду, что если рабочий каталог либо индекс содержат незафиксированные изменения, конфликтующие с веткой, на которую вы хотите переключиться, то Git не позволит переключить ветки. Лучше всего переключаться из чистого рабочего состояния проекта. Есть способы обойти это (припрятать изменения (stash) или добавить их в последний коммит (amend)), но об этом мы поговорим позже в разделе Припрятывание и очистка главы 7. Теперь предположим, что вы зафиксировали все свои изменения и можете переключиться на ветку master :
Теперь вы можете перейти к написанию исправления. Давайте создадим новую ветку для исправления, в которой будем работать, пока не закончим исправление.
Вы можете прогнать тесты, чтобы убедиться, что ваше исправление делает именно то, что нужно. И если это так — выполнить слияние ветки hotfix с веткой master для включения изменений в продукт. Это делается командой git merge :
Заметили фразу «fast-forward» в этом слиянии? Git просто переместил указатель ветки вперед, потому что коммит C4 , на который указывает слитая ветка hotfix , был прямым потомком коммита C2 , на котором вы находились до этого. Другими словами, если коммит сливается с тем, до которого можно добраться двигаясь по истории прямо, Git упрощает слияние просто перенося указатель ветки вперед, так как нет расхождений в изменениях. Это называется «fast-forward».
Теперь ваши изменения включены в коммит, на который указывает ветка master , и исправление можно внедрять.
После внедрения вашего архиважного исправления, вы готовы вернуться к работе над тем, что были вынуждены отложить. Но сначала нужно удалить ветку hotfix , потому что она больше не нужна — ветка master указывает на то же самое место. Для удаления ветки выполните команду git branch с параметром -d :
Стоит обратить внимание на то, что все изменения из ветки hotfix не включены в вашу ветку iss53 . Если их нужно включить, вы можете влить ветку master в вашу ветку iss53 командой git merge master , или же вы можете отложить слияние этих изменений до завершения работы, и затем влить ветку iss53 в master .
Основы слияния
Результат этой операции отличается от результата слияния ветки hotfix . В данном случае процесс разработки ответвился в более ранней точке. Так как коммит, на котором мы находимся, не является прямым родителем ветки, с которой мы выполняем слияние, Git придётся немного потрудиться. В этом случае Git выполняет простое трёхстороннее слияние, используя последние коммиты объединяемых веток и общего для них родительского коммита.
Вместо того, чтобы просто передвинуть указатель ветки вперёд, Git создаёт новый результирующий снимок трёхстороннего слияния, а затем автоматически делает коммит. Этот особый коммит называют коммитом слияния, так как у него более одного предка.
Теперь, когда изменения слиты, ветка iss53 больше не нужна. Вы можете закрыть задачу в системе отслеживания ошибок и удалить ветку:
Основные конфликты слияния
Git не создал коммит слияния автоматически. Он остановил процесс до тех пор, пока вы не разрешите конфликт. Чтобы в любой момент после появления конфликта увидеть, какие файлы не объединены, вы можете запустить git status :
Это означает, что версия из HEAD (вашей ветки master , поскольку именно её вы извлекли перед запуском команды слияния) — это верхняя часть блока (всё, что над ======= ), а версия из вашей ветки iss53 представлена в нижней части. Чтобы разрешить конфликт, придётся выбрать один из вариантов, либо объединить содержимое по-своему. Например, вы можете разрешить конфликт, заменив весь блок следующим:
В этом разрешении есть немного от каждой части, а строки >>>>>> полностью удалены. Разрешив каждый конфликт во всех файлах, запустите git add для каждого файла, чтобы отметить конфликт как решённый. Добавление файла в индекс означает для Git, что все конфликты в нём исправлены.
Если вы хотите использовать графический инструмент для разрешения конфликтов, можно запустить git mergetool , который проведет вас по всем конфликтам:
Если вы хотите использовать инструмент слияния не по умолчанию (в данном случае Git выбрал opendiff , поскольку команда запускалась на Mac), список всех поддерживаемых инструментов представлен вверху после фразы «one of the following tools». Просто введите название инструмента, который хотите использовать.
Мы рассмотрим более продвинутые инструменты для разрешения сложных конфликтов слияния в разделе Продвинутое слияние главы 7.
После выхода из инструмента слияния Git спросит об успешности процесса. Если вы ответите скрипту утвердительно, то он добавит файл в индекс, чтобы отметить его как разрешенный. Теперь можно снова запустить git status , чтобы убедиться в отсутствии конфликтов:
Если это вас устраивает и вы убедились, что все файлы, где были конфликты, добавлены в индекс — выполните команду git commit для создания коммита слияния. Комментарий к коммиту слияния по умолчанию выглядит примерно так:
Если вы считаете, что коммит слияния требует дополнительных пояснений — опишите как были разрешены конфликты и почему были применены именно такие изменения, если это не очевидно.
Опасности перемещения
Но даже перебазирование, при всех своих достоинствах, не лишено недостатков, которые можно выразить одной строчкой:
Не перемещайте коммиты, уже отправленные в публичный репозиторий
Если вы будете придерживаться этого правила, всё будет хорошо. Если не будете, люди возненавидят вас, а ваши друзья и семья будут вас презирать.
Когда вы что-то перемещаете, вы отменяете существующие коммиты и создаёте новые, похожие на старые, но являющиеся другими. Если вы куда-нибудь отправляете свои коммиты и другие люди забирают их себе и в дальнейшем основывают на них свою работу, а затем вы переделываете эти коммиты командой git rebase и выкладываете их снова, то ваши коллеги будут вынуждены заново выполнять слияние для своих наработок. В итоге, когда вы в очередной раз попытаетесь включить их работу в свою, вы получите путаницу.
Давайте рассмотрим пример того, как перемещение публично доступных наработок может вызвать проблемы. Предположим, вы клонировали репозиторий с сервера и сделали какую-то работу. И ваша история коммитов выглядит так:
Теперь кто-то другой внёс свои изменения, слил их и отправил на сервер. Вы стягиваете их к себе, включая новую удалённую ветку, что изменяет вашу историю следующим образом:
Затем автор коммита слияния решает вернуться назад и перебазировать свою ветку; выполнив git push --force , он перезаписывает историю на сервере. При получении изменений с сервера вы получите и новые коммиты.
Рисунок 46. Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа
Теперь вы оба в неловком положении. Если вы выполните git pull , вы создадите коммит слияния, включающий обе линии истории, и ваш репозиторий будет выглядеть следующим образом:
Перемещение vs. Слияние
Теперь, когда вы увидели перемещение и слияние в действии, вы можете задаться вопросом, что из них лучше. Прежде чем ответить на этот вопрос, давайте вернёмся немного назад и поговорим о том, что означает история.
Одна из точек зрения заключается в том, что история коммитов в вашем репозитории — это запись того, что на самом деле произошло. Это исторический документ, ценный сам по себе, и его нельзя подделывать. С этой точки зрения изменение истории коммитов практически кощунственно; вы лжёте о том, что на самом деле произошло. Но что, если произошла путаница в коммитах слияния? Если это случается, репозиторий должен сохранить это для потомков.
Противоположная точка зрения заключается в том, что история коммитов — это история того, как был сделан ваш проект. Вы не публикуете первый черновик книги или инструкции по поддержке вашего программного обеспечения, так как это нуждается в тщательном редактировании. Сторонники этого лагеря считают использование инструментов rebase и filter-branch способом рассказать историю проекта наилучшим образом для будущих читателей.
Теперь к вопросу о том, что лучше — слияние или перебазирование: надеюсь, вы видите, что это не так просто. Git — мощный инструмент, позволяющий вам делать многое с вашей историей, однако каждая команда и каждый проект индивидуален. Теперь, когда вы знаете, как работают оба эти приёма, выбор — какой из них будет лучше в вашей ситуации — зависит от вас.
При этом, вы можете взять лучшее от обоих миров: использовать перебазирование для наведения порядка в истории ваших локальных изменений, но никогда не применять его для уже отправленных куда-нибудь изменений.
Давайте рассмотрим простой пример рабочего процесса, который может быть полезен в вашем проекте. Ваша работа построена так:
Вы работаете над сайтом.
Вы создаете ветку для новой статьи, которую вы пишете.
Вы работаете в этой ветке.
Переключиться на основную ветку.
Создать ветку для добавления исправления.
После тестирования слить ветку содержащую исправление с основной веткой.
Переключиться назад в ту ветку, где вы пишете статью и продолжить работать.
Примеры
Четыре последних коммита из new-feature будут перенесены в конец ветки MW-1655 и результат сохранится в new-feature.
То же что и в примере 1. Т.к. мы вытаскиваем последние 4 коммита, то последний параметр можно опустить.
Взять за основую master, и заребейзить поверх feature. Изменения в ветке feature.
То же самое, что и выше.
- сделать чекаут ветки MW-1572
- применить последний коммит из ветки MW-1572 в конец develop
- результат поместить в MW-1572
- взять все коммиты с 2.8.2.3 и поместить их поверх develop
- естественно должен быть общий предок между 2.8.2.3 и develop чтобы понятно было какие коммиты брать
Редактирование коммитов
Второй режим использования git rebase - это использование интерактивного режима в котором можно отредатировать коммиты. Интерактивный режим включается флагом -i.
После выполнения этой команды откроется окно примерно с таким содержимым:
Необходимо отредактировать файл, поставив перед каждый коммитом букву, соответствующую команде. Например, так:
В данном случае первый коммит будет удален, а у второго можно будет отредактировать текст коммита. Для применения операции, нужно сохранить файл и закрыть редактор.
Представим себе следующую ситуацию. Команда разработчиков, используя git в качестве системы контроля версий, работает несколько дней над одной из фич (feature). Назовем ее условно mega-feature. Произведено несколько десятков коммитов (commit) в ветку (branch) develop-mega-feature, которая была создана от ветки develop. В определенный момент времени возникает необходимость добавить результат работы над mega-feature в основную ветку (назовем ее master). При этом возникает следующая проблема, требующая нестандартного подхода: как перенести функционал mega-feature в виде отдельных файлов, в которых производились изменения, разбросанные по разным коммитам?
Один из наиболее популярных применяемых подходов к организации структуры веток в git-репозиториях, подробно описанный в следующем посте, изображен ниже:
Основная идея состоит в том, чтобы основная масса коммитов осуществлялась в ветку develop; для каждой из фич создавалась отдельная ветка, которая в последствии мержится (merge) в develop; слияние (merge) из develop в master осуществляется в моменты выхода очередных релизов (release). То есть в основной ветке хранится стабильная весия кода, который был, или будет выпущен.
При вышеописанной схеме решение проблемы добавления части функционала из экпериментальной ветки develop-mega-feature в master стандартными подходами, которые основаны на слиянии отдельных коммитов, довольно проблематично. В git существует возможность добавления из одной ветки в другую произвольно выбранных коммитов (опять же, коммитов, но не файлов!) с помощью git-cherry-pick, однако это не является решением описанной выше проблемы.
Существует достаточно элегантное решение, которое позволяет осуществлять добавление изменений, относящихся к недавно-разработанному функционалу mega-feature.
Итак, представим, что для переноса функционала mega-feature в master, необходимо добавить из ветки develop-mega-feature следующие новые файлы: megafeature/implementation.code, megafeature/description.txt и следующий модифицированный файл projectfeatures.code.
Находясь в ветке master, выполняем
в качестве аргументов указываем ветку, из которой собираемся добавлять функционал и список файлов, относящихся к функционалу mega-feature. Завершаем операцию командой
Цель достигнута. Надеюсь приведенная выше информация будет полезна тем, кто столкнется с подобными проблемами в будущем.
Есть две параллельные ветки разработки, которые работают с общими файлами. Но в одной и в другой ветки надо сделать изменения в этих файлах. Соответственно надо вытащить из определённого коммита одной ветки только определённые файлы и залить их в другую ветку.
Как это сделать средствами git?
git checkout otherBranch -- path/to/file
но это только содержимое файла с края otherBranch, никакой истории изменений.
> Есть две параллельные ветки разработки, которые работают с общими файлами. Но в одной и в другой ветки надо сделать изменения в этих файлах.
> Соответственно надо вытащить из определённого коммита одной ветки только определённые файлы и залить их в другую ветку.
не пойму где переход от первого ко второму. Ветки для того и есть, чтобы делать разные изменения в одних и тех же файлах. Если изменения одинаковые - смержите эти коммиты, сделанные в ветке А, в ветку B, или если мерж невозможен, то cherry-pick конкретных коммитов. Зачем что-то куда-то вытаскивать?
Насколько я понял, проблема в том, что есть коммит, в котором какие-то изменения нужны, а какие-то - нет. Поэтому cherry-pick и не подходит.
@koceg
> есть коммит, в котором какие-то изменения нужны, а какие-то - нет
а, ну если так, то да, придется просто создать новый коммит. Вопрос мутно задан, 3 раза прочитал, так до конца и не понял)
Переключаемся на ветку, куда нужно переместить файл, например
git checkout master
Забираем из другой ветки нужный файл
git checkout $branch_name -- $paths_to_file
где paths путь до файла, возможно можно указать сразу несколько путей через пробел, не помню уже
Ну конечно же в той ветке, в которую вы изначально файлы поместили, они останутся, их нужно удалить самому, если они не нужны там.
В Git есть два способа внести изменения из одной ветки в другую: слияние и перебазирование. В этом разделе вы узнаете, что такое перебазирование, как его осуществлять и в каких случаях этот удивительный инструмент использовать не следует.
Особенности команды
- коммиты переносятся ТОЛЬКО в конец ветки;
- при использовании команды модифицируется только текущая ветка;
- можно переносить коммиты, которые находится в текущей ветки;
- за основу (базу) поверх которой будут пересажены коммиты, можно взять другую ветку (параметро –onto);
- если указан параметр
(см. 1 формат команды), то git предварительно сделает чекаут этой ветки, в других случаях ребейз выполнится в текущей ветке. - КОММИТЫ КОТОРЫЕ будут переноситься - НАХОДЯТСЯ В текущей ветке, либо в ветке которая указана последним параметром (и которая при выполнении ребейза зачекаучится);
Формат команды
Простейшее перебазирование
Если вы вернётесь к более раннему примеру из Основы слияния, вы увидите, что разделили свою работу и сделали коммиты в две разные ветки.
Как мы выяснили ранее, простейший способ выполнить слияние двух веток — это команда merge . Она осуществляет трёхстороннее слияние между двумя последними снимками сливаемых веток ( C3 и C4 ) и самого недавнего общего для этих веток родительского снимка ( C2 ), создавая новый снимок (и коммит).
Тем не менее есть и другой способ: вы можете взять те изменения, что были представлены в C4 , и применить их поверх C3 . В Git это называется перебазированием. С помощью команды rebase вы можете взять все коммиты из одной ветки и в том же порядке применить их к другой ветке.
В данном примере переключимся на ветку experiment и перебазируем её относительно ветки master следующим образом:
Это работает следующим образом: берётся общий родительский снимок двух веток (текущей, и той, поверх которой вы выполняете перебазирование), определяется дельта каждого коммита текущей ветки и сохраняется во временный файл, текущая ветка устанавливается на последний коммит ветки, поверх которой вы выполняете перебазирование, а затем по очереди применяются дельты из временных файлов.
После этого вы можете переключиться обратно на ветку master и выполнить слияние перемоткой.
Теперь снимок, на который указывает C4' абсолютно такой же, как тот, на который указывал C5 в примере с трёхсторонним слиянием. Нет абсолютно никакой разницы в конечном результате между двумя показанными примерами, но перебазирование делает историю коммитов чище. Если вы взглянете на историю перебазированной ветки, то увидите, что она выглядит абсолютно линейной: будто все операции были выполнены последовательно, даже если изначально они совершались параллельно.
Учтите, что снимок, на который ссылается ваш последний коммит — является ли он последним коммитом после перебазирования или коммитом слияния после слияния — в обоих случаях это один и тот же снимок, отличаются только истории коммитов. Перебазирование повторяет изменения из одной ветки поверх другой в том порядке, в котором эти изменения были сделаны, в то время как слияние берет две конечные точки и сливает их вместе.
Меняя базу, меняй основание
Если вы попали в такую ситуацию, у Git есть особая магия чтобы вам помочь. Если кто-то в вашей команде форсирует отправку изменений на сервер, переписывающих работу, на которых базировалась ваша работа, то ваша задача будет состоять в определении того, что именно было ваше, а что было переписано ими.
Оказывается, что помимо контрольной суммы коммита SHA-1, Git также вычисляет контрольную сумму отдельно для патча, входящего в этот коммит. Это контрольная сумма называется «patch-id».
Если вы скачаете перезаписанную историю и перебазируете её поверх новых коммитов вашего коллеги, в большинстве случаев Git успешно определит, какие именно изменения были внесены вами, и применит их поверх новой ветки.
К примеру, если в предыдущем сценарии вместо слияния в Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа мы выполним git rebase teamone/master , Git будет:
Определять, какая работа уникальна для вашей ветки (C2, C3, C4, C6, C7)
Определять, какие коммиты не были коммитами слияния (C2, C3, C4)
Определять, что не было перезаписано в основной ветке (только C2 и C3, поскольку C4 — это тот же патч, что и C4')
Применять эти коммиты к ветке teamone/master
Это возможно, если C4 и C4' фактически являются одним и тем же патчем, который был сделан вашим коллегой. В противном случае rebase не сможет определить дубликат и создаст ещё один патч, подобный C4 (который с большой вероятностью не удастся применить чисто, поскольку в нём уже присутствуют некоторые изменения).
Вы можете это упростить, применив git pull --rebase вместо обычного git pull . Или сделать это вручную с помощью git fetch , а затем git rebase teamone/master .
Если вы используете git pull и хотите использовать --rebase по умолчанию, вы можете установить соответствующее значение конфигурации pull.rebase с помощью команды git config --global pull.rebase true .
Если вы рассматриваете перебазирование как способ наведения порядка и работаете с коммитами локально до их отправки или ваши коммиты никогда не будут доступны публично — у вас всё будет хорошо. Однако, если вы перемещаете коммиты, отправленные в публичный репозиторий, и есть вероятность, что работа некоторых людей основывается на этих коммитах, то ваши действия могут вызвать существенные проблемы, а вы — вызвать презрение вашей команды.
Если в какой-то момент вы или ваш коллега находите необходимость в этом, убедитесь, что все знают, как применять команду git pull --rebase для минимизации последствий от подобных действий.
Читайте также: