git merge после переименования всех файлов - PullRequest
3 голосов
/ 21 марта 2019

Существуют и другие ответы, касающиеся обработки merge для переименования, но мой случай достаточно сложен, и я подумал, что он требует отдельного вопроса.

У нас есть проект git, который изначально состоял из 20+ репозиториев. Мы использовали скрипт-обертку для обработки многих стандартных операций git. Поскольку мы сейчас переходим на GitHub, мы не можем обрабатывать проект таким образом.

Итак, мы переместили все репозитории в один репозиторий, по сути, используя метод, описанный в saintgimp . Это, конечно, означает, что все файлы теперь переименованы, но исторически SHA идентичны.

ОК, теперь я хочу объединить ветвь source в ветвь target, отметив, что я убедился, что они были синхронизированы прямо перед финальным переключением. Моя первая попытка использования git merge <source> вызвала тысячи конфликтов, жалоб на файлы, которые были изменены / удалены с одной или другой стороны и т. Д.

Затем я нашел драгоценный камень на странице Advanced Merging :

Если вы хотите сделать что-то подобное, но не имеете Git, попробуйте объединить изменения с другой стороны, есть более драконовский вариант, который является «нашей» стратегией слияния. Это отличается от Опция «нашего» рекурсивного слияния.

Ах, это звучит как то, что мне нужно. ОК, я выполнил следующее:

$ git merge -s ours SHA

, где SHA - последний коммит после воссоединения. Другими словами, я хочу, чтобы вся история, вплоть до SHA, считалась уже объединенной в target. Я надеялся, что это будет единовременное слияние и исправит все будущие слияния.

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

[user@host src] git merge --no-commit next_unmerged_commit
Auto-merging /path/to/file/changed/foo.c
warning: inexact rename detection was skipped due to too many files.
warning: you may want to set your merge.renamelimit variable to at least 5384 and retry the command.
Automatic merge went well; stopped before committing as requested

И, на самом деле, если я установлю renamelimit на 10000, следующее объединение (назовем его B) будет выполнено без предупреждения, но за счет гораздо более низкой производительности. Еще раз, разовая стоимость является приемлемой, и я заплачу эту стоимость, если мои последующие слияния будут сделаны нормальные снова.

Следующее слияние, C, где я использую значение по умолчанию renamelimit, снова выдает предупреждение.

Итак, наконец, мой вопрос: как я могу убедить git, что ветка target синхронизирована с source, так что она перестанет пытаться вернуться в историю до воссоединения? Я хочу иметь возможность слияния без увеличения renamelimit из-за снижения производительности.

1 Ответ

2 голосов
/ 23 марта 2019

Это действительно не очень хороший ответ, так как он больше касается сценария, который вы использовали - или, может быть, я должен сказать, сценарий, который вы не использовали, как ваш комментарий говорит, что вы использовали один на основе сценария, с которым вы связали - но я покажу довольно запутанный график, который я получаю в гипотетическом сценарии-преобразовании некоторых оригинальных репозиториев ниже. Обратите внимание, что этот конкретный скрипт оставляет все преобразования с фиксацией базы слияния, по сути, commit B, а сам коммит B имеет значение empty .

Ваш вопрос говорит:

Теперь я хочу объединить ветвь source в ветвь target, отметив, что я убедился, что они были синхронизированы прямо перед финальным переключением.

Как вы увидите ниже, все новые ветви названы в честь проекта, из которого они вышли - нет четкого способа сопоставить source и target, например, P или Q. Но если бы вы бежать:

git checkout P/master
git merge Q/master

после процесса, показанного ниже, база слияния для этого git merge будет пустой-фиксацией- B, и слияние будет проходить гладко: Git будет смотреть на коммиты, которые я нарисовал, как D и H соответственно , проследите их предков, найдите коммит B в качестве их базы слияния и выполните два git diff s:

git diff <hash-of-B> <hash-of-D>   # what we did on P/master
git diff <hash-of-B> <hash-of-H>   # what they did on H/master

Вывод этих git diff s говорит о том, что каждый файл создается с нуля , и все их имена различны: все в P/master называется P/* и все в H/master назван Q/*. Не было бы конфликтов имен, и слияние завершилось бы самостоятельно.

Очевидно, это не то, что вы делаете, тогда. Но то, что вы делаете и какой коммит является базой слияния, остается загадочным. Похоже, что вы выбираете два коммитов-подсказок, так что база слияния двух коммитов-подсказок является коммитом, в котором имеет файлы, и эти файлы еще не переименованы из базы на чаевые.

Смысл сценария, который вы связали, состоит в том, чтобы настроить все так, чтобы базы слияния каждого из несвязанных проектов представляли собой пустой коммит. Вероятно, что нужно сделать после этого сценария - или вместо этого сценария - сделать одно массивное слияние осьминога со всеми последними коммитами (примечание: это не проверено, что, вероятно, очевидно):

git checkout P/master                 # just to be somewhere that's not master
git branch -d master                  # discard existing master branch name
git checkout --orphan master          # arrange to create new master
git merge P/master Q/master R/master  # make big octopus merge to marry all projects

Базой слияния этого слияния осьминога снова будет фиксация B, а результатом будет одно слияние, в котором все проекты будут под новыми именами project/*. Все исходные репозитории теперь в основном бесполезны, хотя, если в них есть новые коммиты, вы можете извлечь их, добавить коммит переименования и объединить с коммитом переименования (это было бы проще, если бы скрипт импорта этого не делал удалить добавленные пульты).

Наблюдения за работой связанного скрипта

Я никогда не сталкивался с этой конкретной проблемой, но подход в сценарии выглядит как неоправданная отправная точка. Вероятно, я бы сделал это немного по-другому, не беспокоясь о пустой базе слияния и используя git read-tree и git commit-tree для создания и создания конечного слияния осьминога. Основной ключ заключается в добавлении коммита переименования в конце каждой входящей ветви проекта (P/*, Q/* и т. Д.) В скриншоте ниже.

Сценарий, кажется, работает таким образом. Он имеет в качестве входных данных проекты P, Q, R (URL-адреса, последний компонент которых обрабатывается именем проекта).

  1. Сделать пустой репо.
  2. Сделать два начальных коммита:

    A--B   <-- master
    

У коммита А один файл, у коммита Б нет файлов (почему бы не просто зафиксировать пустое дерево как B? но не берите в голову).

  1. Цикл, для всех трех проектов. Здесь я расширил цикл для просмотра что происходит.

  2. (итерация цикла 1) git remote add P <url> и git fetch P--tags !?). Здесь мы предполагаем, что у P есть master и dev.

    A--B   <-- master
    
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  3. Используйте git ls-remote --heads, чтобы найти имена для коммитов в P, т.е. тот же набор имен у нас в refs/remotes/P/*. (Предполагается, что удаленный hsaне изменяется во время выборки - неразумно, но, вероятно, в порядке.)

    Цикл над этими именами. Результат снова развернут в строке для иллюстрации ...

  4. Выполнить git checkout -b P/master master. Эффект:

    A--B   <-- master, P/master
    
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  5. Выполнить git reset --hard без видимой причины: безрезультатно. возможно это может оказать некоторое влияние на более поздний шаг.

  6. Выполнить git clean -d --force без видимой причины: без последствий.

  7. Выполнить git merge --allow-unrelated-histories --no-commit remotes/P/master" (does merge, but does not commit yet) and then run git commit -m ... `. Эффект:

    A--B   <-- master
        \
         \-------C   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  8. Возможно, переименуйте файлы с несколько коротким кодом (строки 160-180): если проект P имеет один каталог верхнего уровня с именем P, ничего не делать, в противном случае создать каталог с именем P (без проверки, если это не удается) и тогда в силу:

    git mv all-but-P P/
    git commit -m "[Project] Move ${sub_project} files into sub directory"
    

    дает:

    A--B   <-- master
        \
         \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    

    Обратите внимание, что git mv дается -k, так что он ничего не делает, если одна из операций git mv не удалась бы. Однако кроме для подкаталога P и .git, все файлы верхнего уровня рабочего дерева должно быть в индексе, а git mv должно успешно, если один из них не назван P (в этом случае, yikes!).

    Я предполагаю, что мы сделали mv, в противном случае D не существует.

  9. Повторите цикл (см. Шаг 5) для dev. Выполнить git checkout -b P/dev master:

    A--B   <-- master, P/dev
        \
         \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  10. Предположительно безрезультатно git reset и git clean снова как в шагах 7 и 8. (Это может что-то сделать, если git mv в шаге 10 все пошло очень плохо?) Смешное двухступенчатое слияние, как в шаг 9, давая:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
         \  Pd   <-- origin/P/dev
          \   \
           \---E   <-- P/dev
    

    где линия вниз от B соединяется с линией вверх от E. На этом этапе график вышел из-под контроля.

  11. Переименуйте и подтвердите, как в шаге 10. Я предполагаю, что проект не находится в подкаталоге, в обоих master, как уже принято и dev.

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
         \  Pd   <-- origin/P/dev
          \   \
           \---E--F   <-- P/dev
    
  12. Действительно безобразная попытка переименовать теги в строках 190-207. это должно было быть сделано во время получения, используя умный refspec. Кто бы ни написал это, вероятно, не знал аннотированных против легкие метки Мне не ясно, работает ли это правильно и я не присматривался. Давайте просто предположим, что нет тегов на данный момент.

  13. Удалить удаленный P. Это также удаляет имена origin/P/*, но, конечно, коммиты остаются вокруг, как они удерживаются новые P/* филиалы:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
  14. Повторите внешний цикл (шаг 3) с удаленным Q. Мы добавим Q и получить (снова с --tags, плохой план, как отмечено в шаге 14, но давайте просто предположим, нет тегов). Так что теперь мы получаем еще непересекающийся подграф с origin/Q/* именами. Для простоты давайте просто предположим, что на этот раз существует только origin/Q/master:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  15. Выполнить git checkout -b Q/master master:

    A--B   <-- master, Q/master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  16. Запустить (вероятно, неэффективно и все еще загадочно) git reset --hard и git clean шагов.

  17. Используйте смешное двухступенчатое слияние с --allow-unrelated-histories создать новый коммит G, например:

         ---------------G   <-- Q/master
        /               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  18. Опять же, необязательно: переименуйте все файлы в G, чтобы жить в Q / и совершить. Снова давайте предположим, что это происходит:

         ---------------G--H   <-- Q/master
        /               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  19. Уродливая попытка переименовать теги; мы проигнорируем это.

  20. Удаление удаленных имен Q и origin/Q/*. (Не нужно рисовать это.)

  21. Повторите внешний цикл для хранилища R. Предполагая, что он имеет только его собственный master, мы получим запутанный граф, подобный этому:

         --------------------I--J   <-- R/master
        /                    | (down to Rm)
       /
       | ---------------G--H   <-- Q/master
       |/               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm
                    / (up to I)
                   /
    R1-R2-...----Rm
    

(конец анализа)

...