Можно ли исключить определенные коммиты при выполнении git merge?

Допустим, я хочу объединить ветку релиза с веткой master, и в ветке релиза есть некоторые коммиты, которые я не хочу включать в ветку master. Есть ли способ сделать слияние, чтобы один или несколько из этих коммитов не были объединены?

Моя стратегия до сих пор заключается в следующем (в мастер):

git merge --no-commit release-branch
# Resolve conflicts and apply reverse patch of the commits that I don't want included
git commit # Edit commit message so that it lists the commits that have been reverse-patched

Есть лучший способ сделать это?

8 ответов

Решение

Создайте новую ветку, перебазируйте ветку в интерактивном режиме и удалите коммиты, которые вам не нужны, а затем объедините их.

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

Я нашел решение, которое работает для меня в книге Pro Git.

Допустим, вы хотите исключить файл config.php,

На ветке А:

  1. Создайте файл с именем .gitattributes в том же каталоге, с этой строкой: config.php merge=ours, Это говорит git, какую стратегию использовать при объединении файла. При этом всегда сохраняйте свою версию, т.е. версия ветки, в которую вы сливаетесь.

  2. Добавить .gitattributes файл и коммит

На ветке B: повторите шаги 1-2

Попробуйте объединить сейчас. Ваш файл должен быть оставлен нетронутым.

Если у вас есть ветка поддержки, где вы исправляете ошибки и создаете новые версии. На master у вас есть следующая версия, где вы также часто создаете новые версии.

Каждый раз, когда вы создаете новую версию, вы изменяете версию в каком-то файле, фиксируете этот новый файл, создаете тег и нажимаете. Теперь слияния от поддержки к основному всегда будут конфликтовать в файле, содержащем информацию о версии.

Если файл, содержащий информацию о версии, содержит только информацию о версии, вы можете пойти с ответом fcurella. Но если он действительно может также содержать объединяемую информацию (pom.xml, gradle.properties, MANIFEST.MF, ...), вы должны выполнить некоторые дополнительные действия.

Давайте использовать следующий пример

      C---D*---E---F* support
     /
A---B---G---H*---I master

где коммиты со звездами содержат только изменения из-за изменений версии, которые следует игнорировать при слиянии.

Чтобы включитьподдержку в мастер без конфликтов слияния из-за сборок версий, вы можете сделать одно из следующего:

Несколько коммитов слияния

git checkout master
git merge C
git merge D -s ours
git merge E
git merge F -s ours

С-s oursВ качестве аргумента мы говорим git записывать только слияние без изменения рабочей области. Это сопоставимо с --record-only вариант свн.

Выше приведут к следующему макету

      -------------C---D*---E---F* support
     /              \   \    \   \
A---B---G---H*---I---J---K----L---M master

Один коммит слияния с использованием cherry-pick

git checkout master
git merge support -s ours --no-commit
git cherry-pick C E --no-commit
git commit -m 'merged support into master'

Сначала мы начинаем слияние, но записываем только слияние, не изменяя рабочее пространство и не делая коммит слияния. Затем мы выбираем коммиты для слияния, опять же без коммитов. Наконец мы совершаем слияние.

Выше приведут к следующему макету

      C---D*---E---F* support
     /              \
A---B---G---H*---I---J master

Можно даже автоматизировать сбор вишни.

git checkout master
git merge support -s ours --no-commit
for id in `git log support --reverse --not HEAD --format="%H [%an] %s" |
  grep -v "bump version" |
  sed "s/\(\w*\)\s.*/\1/g"`
do
  git cherry-pick --no-commit $id
done
git commit -m 'merged support into master'

Причина, по которой это не может быть сделано напрямую, заключается в том, что каждый коммит содержит ссылки на родительские коммиты (обычно только один, но несколько для слияний). Таким образом, если у вас есть один коммит (по сумме SHA1), вся история также фиксируется, поскольку родители также содержат ссылки на своих родителей и так далее. Таким образом, единственный способ оставить патчи в истории - это написать новый. git rebase -i во вновь созданной ветви, вероятно, самый простой способ добиться этого.

Главный вопрос: как вы хотите представлять коммиты, которые вы хотите пропустить?

  1. хитро спрятать их (не мой любимый)
  2. явно пропустить их
  3. явно отменить их

К сожалению нет. 2 невозможно выразить в графе истории.

Номер 1 возможен, но я бы никогда этого не сделал: коммит слияния может содержать изменения. - Обычно коммит слияния указывает на результат слияния еще двух ветвей: все, что разрабатывается в этих ветках, должно быть в коде после слияния (т.е. в коде, указанном коммитом слияния). И больше ничего не должно быть в этом коммите.

Но удивительно, удивительно, вы можете изменить всю кодовую базу и представить ее как объединение. Эффект слияния имеет два аспекта: он объединяет дерево истории и должен объединять две базы кода. Первый он делает наверняка (в противном случае никто не называет это слиянием), для второго он может потерпеть неудачу, например, когда возник конфликт слияния и он был разрешен неправильно (тогда базы кода не были объединены должным образом).

Некоторые другие ответы предполагают это сокрытие. Я рекомендую явный способ: объединить плюс отменить коммиты.

Также возможно изменить файл.git/info/attribute и сохранить его в папке.git вместо того, чтобы добавлять файлы.gitattribute повсюду, что в конечном итоге потребует их добавления в систему контроля версий.

Если вы хотите исключить только некоторые коммиты, которые находятся в конце, вы можете просто зафиксировать конкретный номер коммита:

git checkout partlyMergedFrom
git whatchanged
--> find the commit hash up to where you want to merge
git checkout partlyMergedInto
git merge e40a0e384f58409fe3c864c655a8d252b6422bfc
git whatchanged
--> check that you really got all the changes you want to have

Чтобы избежать проблемы с перезаписью, возникшей с ответом , начните с определения драйвера слияния, который всегда будет поддерживать нашу текущую версию файла, используя существующую команду true. Мы назовем этот драйвер нашим, чтобы соответствовать аналогичным стратегиям слияния:

git config --global merge.ours.driver true

После этого мы можем реализовать решение @fcurella@fcurella :

Допустим, вы хотите исключить файл .env

На ветке A (например, Master):

  1. Создайте файл с именем .gitattributes в том же каталоге с этой строкой:

             .env merge=ours 
    

    Это сообщает git, какую стратегию использовать при слиянии файла. В этом случае он всегда сохраняет свою версию, т.е. версия ветки, в которую вы вливаетесь.

  2. Добавьте файл .gitattributes и зафиксируйте

На ветке B (например, Develop): повторите шаги 1-2.

Попробуйте объединиться сейчас. Ваш файл должен остаться нетронутым.

Прочитайте это для дальнейших объяснений

Другие вопросы по тегам