Почему несвязанная история появляется при запуске «git log -m --follow» для файла после слияния нескольких репозиториев в один монолитный репозиторий? - PullRequest
0 голосов
/ 05 июня 2018

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

Вот вывод, который у меня был:

git log --oneline

выход из комбинированного репо

------- (HEAD -> master) Merge repoC into mono repo
------- Merge repoB into mono repo
------- Merge repoA into mono repo
------- initial commit
------- Add README to repoC
------- Add README to repoB
------- Add README to repoA

git log --oneline repoA/README.md

выход из комбинированного репо

------- Merge repoA into mono repo

git log --oneline -m --follow repoA/README.md

выход из комбинированного репо

 ------- (from -------) (HEAD -> master) Merge repoC into mono repo
 ------- (from -------) Merge repoB into mono repo
 ------- (from -------) Merge repoA into mono repo
 ------- (from -------) Merge repoA into mono repo
 ------- initial commit
 ------- Add README to repoC
 ------- Add README to repoB
 ------- Add README to repoA

Начиная со всех отдельных репо, какДля создания моего монолитного репо я делаю следующее:

Для репо A / B / C

git init
echo "repo" > README.md
git add .
git commit -m 'Add README to repo'
git bundle create ../repo{A,B,C}.bundle --all

Создание комбинированного репо git init echo "initial"> README.md gitдобавлять .git commit -m 'initial commit'

Для каждого репо

mkdir repo{A,B,C}
git fetch ../repo{A,B,C}.bundle master
git merge --allow-unrelated-histories -s ours --no-commit FETCH_HEAD
git read-tree --prefix=repoA -u FETCH_HEAD
git commit -m "Merge repo{A,B,C} into mono repo"

Почему я получаю несвязанную историю git commit для определенных файлов при запуске с -m --follow?Я ожидаю увидеть только коммиты, которые относятся к файлу.

ОБНОВЛЕНО (пытается журналы для файлов с разными именами и содержимым):

  git log -m --follow --oneline repoB/sue.md`
  -------(from  -------) (HEAD -> master) Merge repo C into mono repo`
  -------(from  -------) Merge repo B into mono repo`
  -------(from -------) Merge repo B into mono repo`

1 Ответ

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

Чтобы развернуть комментарий Марка Адельсбергера , вы должны понимать, что в Git идентичность файла определяется довольно странным образом.

Идентификация файла в системах контроля версий (VCSes) является основной концепцией.Как VCS должен знать, что файл include/lib.h является или не является "тем же" файлом, что и файл lib/lib.h?

Некоторые VCS используют подход, который, когда файл впервые представлен в VCS, вы говорите VCS что-то особенное, такое как hg add <em>path</em>.С этого момента в любое время, когда файл переименовывается , вы также сообщаете VCS что-то особенное, например hg mv [--after] <em>old-name</em> <em>new-name</em>.VCS может использовать это для отслеживания идентичности файла в некоторых сериях коммитов: lib/lib.h в ревизии X является или не является «тем же» файлом, что и include/lib.h в рев R, в зависимости от того, сказали ли выVCS, в которой произошла операция переименования между R и X.

Git, с другой стороны, делает что-то радикально другое: он пытается идентифицировать пары файлов при любых двух ревизиях по content .То есть, учитывая ревизии R и X как пару, Git просматривает каждый файл в R и каждый файл в X .Если оба R и X имеют файлы с именем include/lib.h, то это почти наверняка тот же файл , поэтому lib/lib.h (в R илиX) определенно не тот же файл , что и include/lib.h (в другой ревизии), но это может быть тот же файл, что и lib/lib.h (в другой ревизии).Однако, если ровно одна из двух ревизий имеет include/lib.h, а другая - lib/lib.h, этот файл может быть переименован в между этими двумя ревизиями.

В общем случае для CPU-Применительно ко времени, учитывая любую пару ревизий, если в обеих ревизиях существует какой-либо путь P , Git предполагает, что файл не был переименован.С git diff - но не git merge и не git log - вы можете добавить флаг, чтобы сказать не предполагать, что файлы не были переименованы только потому, что они существуют в обеих ревизиях .Это параметр -B (разрыв пары).

Тогда, пока разрешено обнаружение переименования (опция -M в git diff, --follow в git logи другие различные условия): для всех файлов, которые un -парными, либо из-за -B, либо из-за того, что указанный путь существует только в одной из двух ревизий, Git ищет файлы с схожий контент , вычисляющий для них «индекс сходства» и / или схожие имена .(Существует бонус +1 к соответствующим именам компонентов, если оба файла оканчиваются, например, на /lib.h. В качестве ключевой оптимизации, потому что это легко сделать изнутри и работает хорошо, Git быстро соединит файлы со 100% -ым идентичным контентом,и только после этого не удается вычислить индекс сходства.) Затем он сопоставляет любые файлы с индексом сходства, который соответствует или превышает заданное вами процентное требование: -M50 является значением по умолчанию, но вы можете потребовать «сходство 75%» с -M75, например.

Эти парные файлы являются "одинаковыми" файлами в двух ревизиях.Это верно для git diff, который затем создает разность между парными файлами, и для типичного git merge, который запускает два git diff с, один от базы слияния до одного изкоммиты с двумя концами, а затем второй из той же базы слияния с коммитами из двух кончиков.Что наиболее важно, для --follow это также верно и для git log: парные имена файлов направляют операцию --follow на изменяют имя искомого файла на , если файл ранееревизия имеет другое имя.

(Ваш merge -s ours равен , а не типичное слияние: стратегия ours игнорирует все, кроме фиксации HEAD, при вычислении источникакод для нового коммита, чтобы он вообще не беспокоился о различий.)

Как это влияет git log --follow

Чтобы git log --follow <em>path</em> следовал за файлом с именем path при переименованиях, Git должен выполнять эти сравнения по паре, чтобы он мог обнаружить, что файл действительно был переименован.Используемыми парами являются родительский элемент для C и самого C , где C - это фиксация, найденная из-за обхода графа, т. Е. Фиксация, которой является git logсобирается показывать или не показывать, в зависимости от того, коснулся ли он файла с именем пути path .

Коммиты слияния представляют проблему здесь.Само определение коммита слияния состоит в том, что он имеет по крайней мере двух родителей.Вот тут-то и появляется опция -m (разделить слияние): разделить слияние означает сделать вид, что на время этой одной операции git log, что фиксация слияния с N parentна самом деле N отдельные разные коммиты.Первый из этих N коммитов имеет одного родителя: первого родителя слияния.Второй коммит имеет одного родителя: второй родитель слияния.N-тый коммит имеет N-го родителя в качестве одиночного родителя и так далее.Поэтому, если у слияния три родителя, оно разделяется на три виртуальных коммита, каждый с одним родителем.

Это решает проблему спаривания: у каждого из этих виртуальных коммитов теперь есть только один родитель, иGit может запустить diff обычным способом, чтобы обнаружить любые переименования.Если Git находит переименованием, это просто означает, что когда он показывает родительский коммит - после завершения каждого из этих N виртуальных коммитов - он долженпрекратите искать имя пути path и вместо этого начните искать файл с именем old в diff.

Поскольку вы ищете repoA/README.md, Git начинает искать этот конкретный путь.Git находит это имя, repoA/README.md, в разделенном виртуальном коммите каждый раз, когда он смотрит.Родитель каждого разделенного виртуального коммита имеет этот файл с именем README.md, поэтому после того, как Git напечатает разделенный виртуальный коммит один раз для каждого родителя - каждая пара родитель / потомок содержит repoA/README.md, поскольку каждый такой дочерний коммит (само слияние)содержит repoA/README.md - он переходит к родителям по одному, ища файл с именем README.md.Он обнаруживает, что каждый родительский коммит имеет такой файл, поэтому он печатает каждый родительский коммит.

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