Я удивлен, что изменения, сделанные после выбора вишни в git, устаревают при слиянии. Вот полный пример.

Следующее - обычное дело.

  1. Создать репо
  2. Добавляем файл с тестом "ротумс канонер оч круть"
  3. Зайдите в новую ветку и добавьте строку "mutors kanoner och krut"
  4. Проверьте мастер и выберите коммит с помощью "mutors kanoner och krut"
Mac:git user1$ mkdir myrepo; cd myrepo; git init
Initialized empty Git repository in /Users/user1/tmp/git/myrepo/.git/

Mac:myrepo user1$ echo "rotums kanoner och krut" > rotum.txt

Mac:myrepo user1$ git add rotum.txt 

Mac:myrepo user1$ git commit -m "Added file"
[master (root-commit) 1044abb] Added file
 1 file changed, 1 insertion(+)
 create mode 100644 rotum.txt

Mac:myrepo user1$ git checkout -b mybranch
Switched to a new branch 'mybranch'

Mac:myrepo user1$ echo "mutors kanoner och krut" >> rotum.txt  

Mac:myrepo user1$ git commit -am "Added mutor"
[mybranch 19afeba] Added mutor
 1 file changed, 1 insertion(+)

Mac:myrepo user1$ git checkout master
Switched to branch 'master'

Mac:myrepo user1$ git cherry-pick 19af
[master cce2ca5] Added mutor
 Date: Wed May 19 16:12:04 2021 +0200
 1 file changed, 1 insertion(+)

Mac:myrepo user1$ cat rotum.txt  
rotums kanoner och krut
mutors kanoner och krut

Сейчас происходит неожиданное поведение.

  1. Я удаляю строку, которая была добавлена ​​и отобрана (я делаю это путем переопределения файла, нетрадиционный метод, но полезный в данном случае).
  2. Потом сливаю свою ветку в master. Я бы ожидал, что изменения, сделанные в f63dc50, удаление строки, останутся, но таинственным образом исчезнет. Вернулась линия "муторс канонер оч круть".
Mac:myrepo user1$ echo "rotums kanoner och krut" > rotum.txt  

Mac:myrepo user1$ cat rotum.txt  
rotums kanoner och krut

Mac:myrepo user1$ git commit -am "Removed mutor"
[master f63dc50] Removed mutor
 1 file changed, 1 deletion(-)

Mac:myrepo user1$ git merge mybranch
Merge made by the 'recursive' strategy.
 rotum.txt | 1 +
 1 file changed, 1 insertion(+)

Mac:myrepo user1$ cat rotum.txt
rotums kanoner och krut
mutors kanoner och krut

Это ожидаемое поведение или ошибка?

1
Teitur 19 Май 2021 в 17:23

2 ответа

Лучший ответ

Со схемой:

   Initial commit: create file
   |    Add a line in file (cherry-pick b)
   |    |    Remove line, return file to its state in a.
   v    v    v
   a----c----d  <- master
    \
     b <- mybranch
     ^
     Add a line in file

Когда вы объединяете mybranch в master:

  • git ищет общего предка Closes (он же база слияния, commit a. на диаграмме),
  • он смотрит на master (commit d) и видит, что по сравнению с a. файл остается без изменений
  • он смотрит на mybranch (commit b) и видит, что по сравнению с a. следует добавить строку

Поэтому слияние проходит без конфликтов и "вносит" изменения из mybranch.


Вы попадаете в эту ситуацию, потому что:

  1. git merge не проверяет промежуточные коммиты в истории master и mybranch,
  2. git cherry-pick создает новые, несвязанные коммиты, и в графе фиксации ничего не сохраняется, чтобы указать, что "эти изменения уже были включены",
  3. Так получилось, что изменения можно комбинировать без конфликтов (ваш пример действительно прост, но в реальных ситуациях вполне может произойти «отсутствие конфликтов»).

Чтобы дать дальнейшую перспективу:

  • если вы используете git rebase:
git checkout mybranch
git rebase master

В отличие от git merge, git rebase действительно сравнивает список коммитов, и если перебазированная фиксация вводит те же самые изменения, что и другая фиксация в целевой ветви, эта фиксация отбрасывается.

В вашем примере: b не будет применяться повторно, потому что он вводит те же изменения, что и c, который уже есть на master.

  • если вы объединили фиксацию b вместо выбора вишни:
       merge 'mybranch'
       v
   a---c----d  <- master
    \ /
     b <- mybranch

Тогда git merge mybranch сказал бы already merged и не применил бы повторно фиксацию b

3
LeGEC 19 Май 2021 в 15:27

Ключевым моментом здесь является то, что git не записывает тот факт, что фиксация была выбрана из одной ветки в другую; он просто создает новую фиксацию на основе указанной вами (то же самое относится и к «git rebase»).

Что касается git, у вас есть следующие коммиты:

  • 1044abb, который создает файл
  • 19afeba, который добавляет строку
  • cce2ca5, который добавляет строку
  • f63dc50, который удаляет линию

Обратите внимание, что я не описал эти коммиты как «на» одной или другой ветке, потому что, строго говоря, это не имеет значения в git; ветвь указывает на фиксацию, а другие коммиты доступны через «родительские» указатели.

В момент объединения двух веток:

  • 1044abb, cce2ca5 и f63dc50 доступны с "мастера"
  • 1044abb и 19afeba доступны из mybranch
  • файл в "master" имеет только одну строку
  • файл в "mybranch" состоит из двух строк

Когда вы выполняете слияние, git определяет «базу слияния» на основе самого последнего коммита, доступного из обеих веток; в данном случае это 1044abb. Затем он смотрит на различия между этой фиксацией и двумя объединяемыми ветвями:

  • между 1044abb и f63dc50 ("master") файл не изменяется
  • между 1044abb и 19afeba ("моя ветка") в файле есть добавленная строка

Затем он объединяет эти два изменения и применяет их для создания новой версии файла. Поскольку одна сторона слияния хочет добавить строку, а другая не хочет ничего делать, решение состоит в том, чтобы добавить строку.

Результат:

  • cce2ca5, f63dc50 и 19afeba доступны от "мастера"
  • 19afeba доступна с "моей ветки"
  • у вас есть мастер, у которого снова есть линия

Или, говоря короче: вы добавили строку, удалили ее, а затем снова добавили.

3
IMSoP 19 Май 2021 в 15:25