Это совсем не вишня. Не используйте git cherry-pick
, чтобы сделать это: используйте git commit
, чтобы сделать это. Вот очень простой рецепт 1 , чтобы сделать это:
$ git checkout <branch> # get on the target branch
$ cd $(git rev-parse --show-toplevel) # ensure you're at the top of the work-tree
$ git rm -r . # remove all tracked files from index and work-tree
$ git checkout <hash> -- . # create every file anew from <hash>
$ git commit # make a new commit with all new info
Если вы хотите скопировать сообщение о коммите и тому подобное из коммита <hash>
, попробуйте добавить -c <hash>
в строку git commit
.
1 Это не самое простое, но это должно быть понятно. Более простые используют сантехнические команды после начального git checkout
, например ::
git read-tree -u <hash>
git commit
или
git merge --ff-only $(git commit-tree -p HEAD <hash>^{tree} < /tmp/commit-msg)
(не проверено и для второго вам нужно будет создать сообщение коммита).
Long
Помните, что Git хранит коммиты, причем каждый коммит является полным снимком всех исходных файлов, а также некоторых метаданных. Метаданные для каждого коммита включают имя и адрес электронной почты того, кто делает коммит; отметка даты и времени, когда был сделан коммит; сообщение в журнале, чтобы сказать , почему была сделана фиксация; и, что особенно важно для Git, хэш-идентификатор родителя коммита.
Когда у вас есть хэш-идентификатор какого-либо коммита, мы говорим, что вы указываете на коммит. Если один коммит имеет хэш-идентификатор другого коммита, коммит с хэш-идентификатором указывает на другой коммит.
Это означает, что эти хэш-идентификаторы, встроенные в каждый коммит, образуют обратную цепочку. Если мы будем использовать отдельные буквы для фиксации или нумеруем их по порядку C1
, C2
и так далее, мы получим:
A <-B <-C <-D ... <-G <-H
или
C1 <-C2 <-C3 ... <-C7 <-C8
Фактическое имя каждого коммита - это, конечно, какой-то большой некрасивый хеш-идентификатор, но использование таких букв или цифр значительно упрощает нам, как людям, работу с ними. В любом случае, ключ в том, что если мы каким-то образом сохраним хеш-идентификатор последнего коммита в цепочке, мы получим возможность следовать за остальной частью цепочки в обратном направлении, по одному коммиту за раз.
Место, где у нас есть Git, хранит эти хэш-идентификаторы в названиях ветвей . Таким образом, имя ветви, такое как master
, просто хранит реальный хеш-идентификатор коммита H
, тогда как H
сам хранит хеш-идентификатор своего родителя G
, в котором хранится хеш-идентификатор его родителя F
, и т. Д. на:
... <-F <-G <-H <-- master
Эти обратные ссылки, от H
до G
до F
, плюс снимки, сохраненные с каждым коммитом, плюс метаданные о том, кто сделал коммит и почему, являются историей в ваш репозиторий. Чтобы сохранить историю, которая заканчивается на H
, вам просто нужно убедиться, что следующий коммит, когда вы его делаете, имеет H
в качестве родителя:
...--F--G--H--I <-- master
Создавая новый коммит, Git меняет имя master
, чтобы запомнить хэш-идентификатор нового коммита I
, чей родитель H
, чей родитель (все еще) G
и т. Д.
Ваша цель - сделать коммит I
, используя снимок , связанный с другим коммитом, например K
ниже:
...--F--G--H <-- master
\
J------K------L <-- somebranch
Git фактически создает новые коммиты из всего, что есть в index , а не из дерева исходных текстов. Итак, мы начинаем с git checkout master
, чтобы сделать commit H
текущим коммитом и master
текущим ответвлением, которое заполняет индекс и рабочее дерево из содержимого commit H
.
Далее мы хотим, чтобы индекс соответствовал commit K
- без других файлов, кроме тех, которые находятся в K
- поэтому мы начнем с удаления каждого файла из индекса. Для здравомыслия (то есть, чтобы мы могли видеть, что мы делаем), мы позволяем Get делать то же самое с рабочим деревом, что он делает автоматически. Поэтому мы запускаем git rm -r .
после того, как убедимся, что .
относится ко всей паре индекс / рабочее дерево, убедившись, что мы находимся на вершине рабочего дерева, а не в некотором подкаталоге / подпапке.
Теперь в нашем рабочем дереве остаются только неотслеживаемые файлы. Мы также можем удалить их, если захотим, используя обычные rm
или git clean
, хотя в большинстве случаев они безвредны. Если вы хотите удалить их, не стесняйтесь делать это. Затем нам нужно заполнить индекс - рабочее дерево снова отправляется в поездку - из коммита K
, поэтому мы запускаем git checkout <hash-of-K> -- .
. -- .
важен: он сообщает Git не switch commits, просто извлеките все из указанного здесь коммита. Наш индекс и рабочее дерево теперь соответствуют commit K
.
(Если в commit K
есть все файлы, которые есть в H
, мы можем пропустить шаг git rm
. Нам нужно только от git rm
до удалить файлы, которые находятся в H
, но не в K
.)
Наконец, теперь, когда у нас есть индекс (и рабочее дерево), соответствующий коммиту K
, мы можем сделать новый коммит, который похож на K
, но не подключается к K
.
Если вы хотите объединить, используйте git merge --no-commit
Приведенная выше последовательность приводит к:
...--F--G--H--I <-- master
\
J-------K-----L <-- somebranch
, где сохраненный снимок источника в коммите I
в точности совпадает с коммитом K
. Тем не менее, история генерируется чтением master
, обнаружением, что оно указывает на I
, а затем чтением commit I
и обратно в H
и G
и F
и так далее, никогда упоминает коммит K
вообще.
Вместо этого вам может потребоваться история, которая выглядит следующим образом:
...--F--G--H--I <-- master
\ /
J-------K-----L <-- somebranch
Здесь коммит I
возвращается к и коммитам H
и K
.
Создание этого варианта коммита I
немного сложнее, потому что кроме использования git commit-tree
сантехнической команды, единственный способ make commit I
- это использование git merge
.
Здесь проще всего запустить git merge -s ours --no-commit
, например:
$ git merge -s ours --no-commit <hash> # start the merge but don't finish it
$ git rm -r . # discard everything
$ git checkout <hash> -- . # replace with their content
$ git commit # and now finish the merge
Мы используем -s ours
здесь, чтобы все шло быстрее и плавнее. То, что мы строим, действительно является результатом git merge -s theirs
, за исключением того факта, что git merge -s theirs
нет. -s ours
означает игнорировать их коммит, просто сохранить содержимое нашего коммита H
. Затем мы выбрасываем это и заменяем его контентом из их коммита K
, и затем мы завершаем слияние получить коммит слияния I
, который указывает на H
и K
.
Как и прежде, есть хитрости командных команд, которые делают это еще проще. Они просто не очевидные , если вы не понимаете формат хранения низкого уровня, который Git использует внутри себя. Метод «удалить все, затем проверить содержимое другого коммита» действительно очевиден и его легко запомнить.