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:
- Начинается с текущего коммита (
git log
) или последнего коммита в master
и показывает, что вы совершаете а-ля git show
. 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
, даже если вы позже передумаете очто.
Если слияние не , что вы хотите, то коммит (ы) плюс сообщения журнала могут быть тем, что вам нужно.