Git перемещать каталог из одной ветви в другую в пределах одного и того же хранилища, сохраняя историю? - PullRequest
1 голос
/ 28 мая 2019

Как перемещать каталог из одной ветви в другую, сохраняя историю?

Я хочу перемещаться в том же репо.

1 Ответ

1 голос
/ 28 мая 2019

TL; DR

Вы не можете (совсем) получить то, что хотите, но если вы попробуете иногда, вы можете просто найти, вы получите то, что вам нужно.100

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

git checkout otherbranch -- path/to/directory
git commit                                      # optional, but see below

, а затем в любом случае выполнить:

git mv path/to/directory new/path/to/dir

, а затем git commit результат.Это не делает то, что вы хотите , но оно может делать то, что вам нужно - особенно если вы делаете первый коммит, который не имеет переименование,так что у вас есть два смежных коммита, один со старыми именами и один с новыми.

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

Long

Здесь важно понимать две вещи:

  • Git хранит только файлы, а не каталоги. 1 Каждый коммит хранит полный снимок всех файлов в вашем проекте.
  • Файлы не имеют историю в Git,В Git коммитов являются историей.

Люди часто возражают против идеи, что Git хранит снимки, потому что git log -p показывает патчей, т. Е. Изменения.Например, вы просматриваете коммит 0a36ca1 и видите некоторые изменения в README.md.Затем git log продолжает коммит, скажем, 0a36ca1 родительского коммита *1050*, и вы видите другое изменение README.md и / или некоторых других файлов и так далее.Разве это не означает, что 0a36ca1 просто хранит изменения в README.md?И ответ: нет, 0a36ca1 хранит полную копию из README.md и всех других файлов.Git показал изменения, проверив обоих 922bf37 - родителя 0a36ca1, т. Е. Фиксации, предшествующей 0a36ca1 - и 0a36ca1.Оба коммита имеют копии каждого файла.Git сравнил файлы двух коммитов.Все файлы в этих двух коммитах соответствуют , кроме для README.md.Затем Git сравнил две версии README.md, чтобы увидеть, что изменилось, и показал, что изменило в этом файле.

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

Когда вы запрашиваете историю с git log, запуская git log или git log master, Git:

  1. Начинается с текущего коммита (git log) или последнего коммита в master и показывает, что вы совершаете а-ля git show. 2
  2. Затем возвращается к родителю коммита, который он только что показал.Это усложняется при коммитах слиянием, но сейчас просто подумайте о хорошей простой линейной цепочке.

Это повторяется до тех пор, пока у Git не закончатся родители, или вы не устанете перелистывать через вывод git log.Имея хорошую простую линейную цепочку коммитов, например:

A <-B <-C <-D <-E <-F <-G   <-- master

(здесь заглавные буквы обозначают большие уродливые хеш-идентификаторы, которые Git действительно использует), Git начинает с показа G (какнайти по имени master), затем перейти к F и показать вам F, затем перейти к E и показать E, и так далее.Коммит A - самый первый коммит в репозитории - у него нет родителя;стрелка назад не выходит из A, чтобы позволить Git двигаться влево - поэтому git show показывает, что каждый файл создан с нуля.Это означает, что git log -p показывает то же самое.И, конечно же, без родителя, нет стрелки, чтобы следовать назад.


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

2 Предполагается, что вы, конечно, используете git log -p. Между git log и git show есть несколько важных отличий: во-первых, git log делает это в обратном направлении; во-вторых, git log по умолчанию не с патчем; и в-третьих, git show показывает коммиты слияния другим способом по умолчанию: git log -p по умолчанию говорит самому себе: тьфу, коммит слияния, это слишком сложно: я просто распечатаю сообщение журнала и продолжу без Показывать разницу вообще. По умолчанию git show здесь показывается комбинированная разница , которая является сокращенной формой сравнения для нескольких родителей.


git log может показывать подмножество истории

Вместо git log или git log master вы можете запустить:

git log master -- path/to/file.ext

и посмотрите, как выглядит история path/to/file.ext. git log делает здесь, как обычно, ходя по истории коммитов , но затем не показывает некоторых коммитов. То есть, учитывая нашу простую линейную цепочку выше, git log начинается с коммита G. Он сравнивает (снимки) F и G, чтобы увидеть, какие файлы изменились. Если эти файлы do включают path/to/file.ext, git log показывает commit G. Затем он возвращается к фиксации F, даже если ничего не показывал .

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

Это важно, потому что, когда Git делает это искусственное создание истории файлов, git log означает , модифицирующую совершенную прогулку. Документация git log называет это Упрощение истории , и это сложно . Есть около полудюжины или около того git log опций для управления , как будет выполняться упрощение истории. Это означает, что «история файлов», которую вы видите с помощью git log, зависит от того, какие опции вы передаете git log, а также от фактической истории фиксации.

(Прочитайте и изучите раздел «Упрощение истории», по крайней мере, когда-нибудь, потому что в нем много чего. Я давно пользуюсь Git, и мне нравится думать, что я много знаю об этом, но даже тогда я для этого нужно обратиться к документации. В частности, понятие «TREESAME», которое применяется после вычитания ненужных компонентов дерева из каждого коммита, и какие коммиты выполняются при слияниях, особенно сложно.)

С помощью --follow, git log попытается обнаружить переименования

Когда Git выполняет этот коммит за коммитом, обратный обход цепочки коммитов, различие от родителя к потомку может показать, что какой-то файл переименован . Например, файл с именем README может быть переименован в файл с именем README.md. Простое:

git log master -- README.md

покажет вам, как README.md эволюционировал с течением времени (в обратном направлении), но остановитесь, когда README.md был назван README, потому что он ищет README.md и коммиты отсюда не имеют README.md.

Когда вы добавляете --follow к git log, из этого переименования будет следовать один файл - он работает только с одним файлом! - просто изменив файл , который он ищет. Обнаружив, что, скажем, на границе commit- D -to- E, файл, который теперь README.md, был назван README в коммите D, git log прекращает поиск README.md и начинается с D, ища изменения в файле с именем README. Это действительно так просто.

--follow слишком просто для вашего случая использования

Theпроблема в том, что --follow это так просто, что слишком просто.Поэтому он не будет выполнять то, что вы хотите, по двум причинам:

  • Во-первых, вы говорите о копировании файлов через довольно большой разрыв:

    ...--F--G--H   <-- master
     \
      N--O--P   <-- branch
    

    Если ваш каталог, полный файлов, находится в коммите H на master, и вы только что скопировали его в новый коммит, который вы сделаете на branch, который следует после коммита P, хорошо,обратной ссылки от P до H нет.Вот почему я предложил вам зафиксировать файлы без , переименовав их, затем переименовать их и зафиксировать снова.Результатом будет:

    ...--F--G--H   <-- master
     \
      N--O--P--Q--R   <-- branch
    

    , где commit R переименовывает файлы, а Q не переименовывает их, просто копирует из H.В сообщении журнала фиксации для Q вы можете указать, что весь каталог был скопирован из ветви master в тот момент, когда он указывал на фиксацию H (используйте реальную H).идентификатор хеша здесь - запустите git rev-parse master, чтобы увидеть, какой идентификатор хеша master указывает прямо сейчас).Затем вы переименовываете каталог и снова фиксируете, чтобы они отображались как переименования, всякий раз, когда Git уходит от коммита R назад к коммиту Q.

  • Опция git log --follow работает только один файл.То есть, с учетом коммита, который является или происходит от R и, следовательно, имеет новое имя каталога, вы должны выполнить:

    git log --follow [<commit-hash>] [--] new/path/to/dir/file.ext
    

    , который в конечном итоге будет работать как R, показать new/path/to/dir/file.ext (поскольку он переименован в коммит R по сравнению с коммитом Q), затем вернитесь к коммиту Q и начните искать path/to/directory/file.ext.

Сэто одно обнаруженное переименование, а также сообщения журнала в Q и R, вы - умный человек, а не тупая программа Git, которая просто подчиняется действительно простым правилам - можете сделать вывод, что, ага, все изэти файлы были получены из коммита H.

Здесь вы можете захотите реального слияния.Вместо того, чтобы просто копировать файлы из H, вы можете буквально сделать коммит Q как коммит слияния , соединив историю из коммита Q обратно с обоими коммитами: P и H.То есть, предположим, что вы получили:

...--F--G--H   <-- master
 \          \
  N--O--P----Q--R   <-- branch

Теперь, когда Git просматривает историю коммитов, он выглядит так: R, Q, H -and- P, G-и- O, F -и- N и так далее.То есть git log просматривает фактическую историю, по одному коммиту за раз, с неким сложным методом отслеживания через форк в истории, где коммиты H и P объединяются в коммит Q.

Недостаток этого слияния очевиден: это слияние .По умолчанию он вносит всех изменений, произошедших с какого-то общего предка - поскольку любой коммит до N и до F, где branch и master в конечном итоге приводят к общему commit: коммит, который находится в обеих ветках.Вам не обязательно фиксировать эти изменения или даже какие-либо изменения: вы можете сделать коммит Q с моментальным снимком совпадения P, за исключением, конечно, нового каталога, который вы хотите.

(Есть несколько способов сделать это слияние. Как этого добиться - это еще один вопрос StackOverflow, на который уже есть хороший ответ. См. (Git Merging) Когда использовать «нашу» стратегию »наш 'option' и 'yours' option? , а также ответ VonC на другой вопрос здесь . Здесь много вариантов, но вы, вероятно, захотите начать с git merge -s ours --no-commit, если хотите -s ours на всех, а затем извлечение файлов с помощью git checkout <commit> -- <path>, и только затем делает коммит Q в виде слияния.)

ThПреимущество слияния состоит в том, что оно связывает истории вместе, так что git log может идти от слияния Q назад к фиксации H, которая является источником фактической истории для (допереименование) файлов.Недостатком является то, что он связывает истории вместе, так что с тех пор Git считает, что правильный результат слияния H с P равен Q, даже если вы позже передумаете очто.

Если слияние не , что вы хотите, то коммит (ы) плюс сообщения журнала могут быть тем, что вам нужно.

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