Как заставить git использовать fast-forward при применении тайника? - PullRequest
0 голосов
/ 23 июня 2018
  • Я пишу сценарий предварительной фиксации / предварительного нажатия
  • Но сценарий должен действовать только на поэтапных изменениях
  • Для достижения этого я использую git stash push --keep-index дляудалите неотмеченные изменения
  • , а затем git stash pop, чтобы повторно применить их после сценариев

Однако git stash pop всегда будет создавать конфликт слияния, если в промежуточных и неразмеченных изменениях имеютсята же строкаНапример,

$ echo "print('a')" >> main.py  # main.py already exists
print('a')
$ git add main.py
$ sed -i 's/a/b/g' main.py  # now it's print('b')
$ git status --short
## master
MM main.py
$ git stash push --keep-index
Saved working directory...
$ git stash pop
Auto-mergin main.py
CONFLICT (content): Merge conflict in main.py

Как мне заставить git применять изменения в тайнике преимущественно к поэтапным изменениям?


У меня есть идея, что это может быть связано су спрятанных изменений есть два родителя - первый - ГОЛОВА, а второй - индекс.Затем Git пытается выполнить трехстороннее слияние.

Но в моем случае это не имеет смысла.Сценарий не изменяет файлы в любом случае, так что на самом деле я просто смотрю, как быстрая перемотка вперед применяется.Или мне нужно «перебазировать» тайник, чтобы индекс был только родительским.

1 Ответ

0 голосов
/ 24 июня 2018

TL; DR

Вам необходимо git reset --hard HEAD (или что-нибудь эквивалентное) перед применением с --index, как это. Применяются все обычные предостережения в отношении жесткого сброса.

Long

Я связал в комментарии к Как правильно сделать git stash / pop в хуках перед фиксацией, чтобы получить чистое рабочее дерево для тестов? , которое показывает, как сделать финальное всплывающее окно (или эквивалентное), и некоторые предостережения вокруг этого. Тем не менее, ответ на заданный вопрос - в частности Как заставить git использовать ускоренную перемотку вперед при применении тайника - заключается в том, что вы не можете, и на самом деле вопрос даже не имеет смысла : быстрая перемотка вперед - это не то же самое, что спрятать и убрать. 1

Git stash - это просто набор коммитов (два, если вы не используете опцию --all или --include-untracked, тогда вы получите три) со специальным соглашением. Сохранение коммитов:

  • указатель на момент git stash (используется git write-tree);
  • содержимое рабочего дерева во время git stash (с использованием довольно сложного кода);
  • и последний в этом списке, но на самом деле сделано раньше, если вы использовали --all или --include-untracked, файлы, которые не были отслежены, включая игнорируемые файлы (--all), или файлы, которые не были отслежены, за исключением игнорируемых файлов (--include-untracked).

Затем Git сбрасывает рабочее дерево, обычно в соответствии с коммитом HEAD, и, если использовались --all или --include-untracked, удаляет также файлы, сохраненные в третьем коммите. Однако, когда вы используете --keep-index, Git сбрасывает рабочее дерево, чтобы соответствовать содержимому индекса.

Ссылка с именем refs/stash изменена, чтобы указывать на фиксацию рабочего дерева. Этот коммит имеет в качестве своих родителей коммит HEAD (родительский номер 1), индексный коммит (родительский номер 2) и, если он присутствует, фиксацию неотслеживаемых файлов (родительский номер 3). Индекс имеет в качестве родителя фиксацию HEAD. Фиксированный файл без отслеживания не имеет родителя (является корневым):

...--o--o--o   <-- refs/heads/somebranch (HEAD)
           |\
           i-w   <-- refs/stash
            /
           u

или более типично, то же самое без u.

Когда git stash сбрасывается до HEAD (т.е. без --keep-index), все, что вам нужно сделать, чтобы отменить то, что git stash сделал, это запустить git stash pop --index (примечание: не --keep-index!). Он запускает git stash apply с теми же параметрами и аргументами, 2 и, если это удается без конфликтов слияния, запускает git stash drop в том же хранилище.

Приложение может использовать как фиксацию индекса, так и фиксацию рабочего дерева для восстановления того, над чем вы работали, но по умолчанию оно игнорирует фиксацию индекса. Добавление --index указывает Git применить индексную фиксацию (превращенную в diff для текущего содержимого индекса) к текущему содержимому индекса, используя git apply --index. Если это не удается, git stash останавливается и ничего не делает. В этом я бы предложил превратить тайник в новую ветку, используя git stash branch, хотя git stash просто предлагает применить без --index. 3

В любом случае Git затем пытается применить фиксацию рабочего дерева к текущему рабочему дереву. 4 Если вы спрятали без --keep-index и не внесли изменений к текущему рабочему дереву это всегда будет успешным: текущий индекс и рабочее дерево будут соответствовать фиксации HEAD, так что это оставит текущий индекс без изменений и применит все различия в фиксации рабочего дерева к рабочему дереву само по себе, что приводит к восстановлению скрытого рабочего дерева.

На данный момент проблема в том, что вы использовали --keep-index, поэтому рабочее дерево current соответствует настроенному вами index , а не чем соответствие фиксации HEAD. Следовательно, прежде чем применять тайник (с --index или без него), вы должны сначала сбросить рабочее дерево, чтобы оно соответствовало фиксации HEAD, то есть git reset --hard. Состояния индекса и рабочего дерева, которые вы хотите, находятся в хранилище, которое вы собираетесь применить, так что это безопасно, если текущий индекс и рабочее дерево не были изменены каким-либо кодом предварительной фиксации / предварительной загрузки, который у вас есть. .

Как только вы это сделаете, git apply --index коммитов stash восстановит и индекс, и рабочее дерево (по модулю этой ошибки в связанном вопросе!).


Сноска

Они специально вышли из строя, потому что сноска 1 слишком длинная.

2 Аргумент git stash apply по умолчанию равен refs/stash.Если вы дадите ему какой-либо аргумент, его поведение будет немного изящнее: в последних версиях Git, если вы дадите ему полностью числовой аргумент n , он проверяет stash@{<em>n</em>}, в противном случае он использует все, что вы ему дали.Она передает эту строку в git rev-parse, чтобы убедиться, что она преобразуется в действительный хэш-идентификатор, и что при суффиксе :, ^1, ^1:, ^2 и ^2: они также преобразуются вдействительные хэш-идентификаторы.Если строка создает действительный идентификатор хеша с ^3 и ^3:, они также запоминаются.Все вместе они образуют w_commit, w_tree, b_commit, b_tree, i_commit и i_tree, а также u_commit и u_tree, если они существуют.Подробнее о том, как это работает, см. В документации gitrevisions .

Суть этого сводится к тому, что любой аргумент, передаваемый вами git stash apply, должен иметь форму совершить слияние, по крайней мере, с двумя родителями.Git не проверяет, есть ли дополнительные родители помимо предполагаемых трех, и не является ли этот коммит слияния действительно тайником: он просто предполагает, что, если у него правильный набор родительского возраста, вы намереваетесь использовать его как один.

3 Это может быть достаточно разумно для неофитов Git, которые не пытаются хранить индекс отдельно и использовали --index на git stash apply или git stash pop, не понимая этого.Как только вы действительно поймете индекс, это явно не так: вы хотели восстановить изменения сохраненного индекса относительно вашего текущего индекса в вашем текущем индексе, а не игнорировать их полностью!Фиксация текущего индекса, если это уместно, затем фиксация текущего рабочего дерева, если это уместно, а затем превращение тайника в ветку и фиксация его рабочего дерева дает вам все необходимое для построения правильных конечных результатов.

4 Технические подробности: приложение использует git merge-recursive - это то, что реализует git merge -s recursive - с некоторыми секретными переменными среды для установки имен на маркерах конфликта, если возникает конфликт.База слияния - это фиксация, которая была HEAD, когда был сделан тайник, текущее дерево - результат записи текущего (в нерабочее время) индекса, а объединяемый элемент - фиксация рабочего дерева, илиточнее, его дерево.Это использует тот факт, что некоторые слияния могут выполняться с незафиксированными изменениями.Команда переднего плана git merge запрещает попытки слияния с незафиксированными изменениями, так как результаты могут быть очень запутанными при возникновении проблемы.

1 Концепция fast-forward также немного сложнее, чем обычно кажется на первый взгляд.То есть мы видим это при слиянии - см. В чем разница между `git merge` и` git merge --no-ff`? - но на самом деле это относится к обновлению ссылки, такой как ветвьназвание.Обновление имени ветви - это ускоренная перемотка в том и только в том случае, если хеш нового коммита имеет хэш старого коммита в качестве предка, т. Е. Если git merge-base --is-ancester $old_hash $new_hash возвращает нулевой статус выхода.

Когда git merge выполняет один из нихиз этих операций ускоренной пересылки это означает, что Git изменил коммит HEAD, чтобы он указывал на новый хеш, а также обновил индекс и рабочее дерево по мере необходимости.Если бы вам пришлось быстро перейти к коммиту с рабочим деревом в тайнике, это раскрыло бы странную техническую коммит с рабочим деревом для остальной части Git, где это было бы, по крайней мере, очень запутанным.

Нетт. е. что git fetch и git push также выполняют операции ускоренной перемотки или, с помощью --force, разрешают не пересылку изменений в ветви и (для извлечения) имен удаленного отслеживания.Получатель push-сообщения обычно требует быстрой перемотки вперед, потому что это означает, что обновленное имя ветви содержит все коммиты, которые он использовал, плюс некоторые дополнительные коммиты.Принудительное обновление без ускоренной пересылки отменяет фиксацию из ветви (независимо от того, добавляет ли она новые).Несколько загадочный вывод git fetch записывает, было ли имя для удаленного отслеживания переадресовано или форсировано тремя (!) Способами:

$ git fetch
remote: Counting objects: 1701, done.
remote: Compressing objects: 100% (711/711), done.
remote: Total 1701 (delta 1363), reused 1318 (delta 989)
Receiving objects: 100% (1701/1701), 975.29 KiB | 3.65 MiB/s, done.
Resolving deltas: 100% (1363/1363), completed with 284 local objects.
From [url]
   3e5524907..53f9a3e15  master     -> origin/master
   61856ae69..ad0ab374a  next       -> origin/next
 + fc16284ea...4bc8c995a pu         -> origin/pu  (forced update)
   9125ddae1..9db014fc5  todo       -> origin/todo
 * [new tag]             v2.18.0    -> v2.18.0
 * [new tag]             v2.18.0-rc2 -> v2.18.0-rc2

Обратите внимание на + перед строкой, записывающее обновление origin/pu и добавлены слова (forced updated).Это два из трех способов.Обратите внимание на точки между двумя сокращенными хэшами фиксации: все другие строки, которые не являются принудительными обновлениями, показывают две точки, но это обновление показывает три точек.Это потому, что мы можем использовать git rev-list или git log с тем же синтаксисом из трех точек для просмотра добавленных и удаленных коммитов:

$ git log --oneline --decorate --graph --left-right fc16284ea...4bc8c995a
>   4bc8c995a (origin/pu) Merge branch 'sb/diff-color-move-more' into pu
|\  
| > 76db2b132 SQUASH????? Documentation breakage emergency fix
| > f2d78d2c6 diff.c: add white space mode to move detection that allows indent changes
| > a58e68b88 diff.c: factor advance_or_nullify out of mark_color_as_move
[massive snippage]
<   fc16284ea Merge branch 'mk/http-backend-content-length' into pu
|\  
| < 202e4a2ff SQUASH???
| < cb6d3213e http-backend: respect CONTENT_LENGTH for receive-pack
< | 4486a82e5 Merge branch 'ag/rebase-p' into pu
< |   a84cc85f3 Merge branch 'nd/completion-negation' into pu
[much more snippage]

Опция --left-right вместе с синтаксисом из трех точек,говорит Git, чтобы отметить, с какой стороны поступили коммиты.В этом случае коммиты > теперь находятся в ветке раскладки, а коммиты < сняты с нее.Эти конкретные удаленные коммиты теперь полностью не ссылаются и скоро будут собирать мусор (ish).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...