Есть ли хороший способ полностью перезаписать / заменить содержимое главной ветки ...
Существует более одного, но прежде чем выбрать какой-либо, необходимо ясно, что вы подразумеваете под ветвью и контентом .
. Следует иметь в виду, что каждая ветвь имя просто говорит мой последний коммит _____ (заполните бланк с необработанным коммитом ha sh ID). Коммиты, которые находятся "на" ветви, зависят от того, что commit .
Каждый коммит имеет уникальный идентификатор ha sh. Этот ha sh ID означает , что коммит, а не любой другой коммит. Как только коммит сделан, ни одна часть не может измениться . Все коммиты полностью заморожены на все времена. Коммиты также в основном постоянны: самое большее, вы можете прекратить использовать коммит и убрать средства нахождения этого коммита, как мы увидим через мгновение.
Каждый коммит содержит две вещи: как основные данные, полный и полный снимок всех файлов, и как его метаданные - информацию о коммите. Это включает в себя имя и адрес электронной почты человека, который его сделал, и сообщение в журнале, объясняющее , почему они совершили этот коммит. Но самое важное, по крайней мере для Git, заключается в следующем: когда вы или кто-либо делаете новый коммит, Git записывает необработанный га sh ID его непосредственного родитель коммит. Этот идентификатор ha sh является идентификатором ha sh идентификатора коммита, который вы только что получили, go, который был последним коммитом в ветви. Опять же, никакая часть этих данных (снимок) или метаданных (включая родительский га sh ID) никогда не может измениться с этого момента, так что это означает, что этот коммит запоминает предыдущий commit-tip commit.
Следовательно, «содержимое ветки» может быть:
- raw ha sh ID последнего коммита в ветке; или
- с идентификатором ha sh и, следовательно, с этим коммитом, плюс идентификатор ha sh, сохраненный в этом коммите и, следовательно, с предыдущим коммитом, плюс другой идентификатор ha sh, сохраненный в этом коммите и, следовательно, с другим commit, plus ...
Если мы рисуем эти коммиты, что требует предположений об их идентификаторах ha sh; здесь я просто заменю каждый фактический идентификатор ha sh одиночными заглавными буквами - мы получим такую картинку:
I--J <-- master
/
...--F--G--H
\
K--L <-- dev
То есть name master
содержит raw ha sh ID commit J
(что бы это ни было на самом деле). Commit J
содержит необработанный идентификатор транзакции ha sh I
, который содержит идентификатор транзакции ha sh H
. Между тем имя dev
содержит га sh идентификатор коммита L
, который содержит K
, который содержит H
.
Обратите внимание, что коммит через H
на обе ветви.
В этой ситуации ветви объединяются на H
, по крайней мере, при просмотре так, как Git просматривает их: начиная с конца и работает в обратном направлении, один коммит за раз.
1 Технически, индекс содержит имена файлов, их режим (+ x или -x) и ссылка на содержимое в замороженном формате.
Как обычно растут ветви
Чтобы сделать новый коммит, вы можете начать с git checkout <em>name</em>
. Допустим, имя в этом случае master
. Это находит фиксацию , на которую указывает имя - в данном случае J
- и копирует содержимое фиксации только для чтения из фиксации, в индекс Git и в вашу работу. -tree. В рабочем дереве вы можете просматривать и редактировать свои файлы. Это также прикрепляет специальное имя HEAD
к имени master
:
I--J <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
Git теперь можно увидеть, что текущая ветвь равна master
(видя где HEAD
прилагается), а текущий коммит равен J
(видя, где master
указывает). Теперь в вашем рабочем дереве есть файлы, с которыми вы работаете - обычные файлы, не подвергнутые сублимационной сушке (сжатые и Git только для зафиксированных файлов), а в индексе Git есть копии 1 из высушенных замораживанием файлов из J
, готовых к go в новый коммит.
Теперь вы можете изменять рабочее дерево так, как вам нравится. Когда вы закончите, вы можете запустить git add
для различных файлов. При этом каждый добавленный файл content копируется обратно в индекс Git, сжимая их в высушенную для замораживания форму, которую Git будет хранить в коммите, заменяя предыдущую копию. Или вы можете git add
новый файл или git rm
существующий файл; в любом случае Git соответственно обновляет индекс.
Затем вы запускаете git commit
. Git просто упаковывает все, что есть в index , добавляет ваше имя и текущее время, добавляет ваше сообщение журнала и записывает это как новый коммит. parent нового коммита - это текущий коммит , а идентификатор ha sh - это уникальная контрольная сумма содержимого нового коммита, которая больше не может быть изменена. Мы назовем этот новый коммит N
(пропуская M
). N
указывает на J
:
N
/
I--J <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
, но теперь мы получаем хитрость, которая делает ветки полезными: Git теперь записывает идентификатор ha sh нового коммита в имя ветви . Итак, теперь у нас есть:
I--J--N <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
Обратите внимание, что HEAD
не изменился: он все еще присоединен к master
.
Теперь давайте избавимся от совершить N
. На самом деле он не будет go удален - он все еще будет в нашем хранилище - но мы организуем для name master
, чтобы снова идентифицировать commit J
. Для этого мы находим фактический идентификатор ha sh, равный J
, или любой прокси для него и используем git reset --hard
:
git reset --hard <hash-of-J>
Теперь у нас есть:
N [abandoned]
/
I--J <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
Если вы сделаете это в вашем хранилище, без использования git push
в отправьте новый коммит N
в какое-то другое Git хранилище, только у вас будет коммит N
. Никто больше никогда не узнает, что вы сделали это!
Использование git reset
произвольно
Вы можете, с помощью git reset
, переместить любую ветвь на любую коммит. Но если вы перемещаете ветвь «назад», как мы только что сделали, коммиты, которые «падают с конца», становятся трудными для поиска. Предположим, вы заставили имя master
указать, чтобы совершить L
. Как же тогда найти коммит J
? Как насчет коммита I
? Кажется, они исчезли!
Этот может быть тем, что вы хотите. Но если у какого-нибудь другого Git репозитория уже есть коммитов I
и J
, возможно, кто-то, использующий этот репозиторий, создал new коммитов, которые ссылаются на J
. Они не будут благодарны вам за то, что вы попросили их забыть все свои коммиты, а также I
и J
.
Если все в порядке, чтобы все остальные забыли I
и J
, вы можете просто сбросить настройки. master
чтобы указать на L
. Затем вам нужно использовать git push --force
или его эквивалент, чтобы убедить какой-нибудь другой Git репозиторий забыть также I
и J
(и, возможно, дополнительные коммиты), и всех остальных, кто использует клон этого хранилище должно убедиться, что их Git забудет I
и J
и т. д.
Это самый быстрый и простой способ сопоставления master
dev
, но это также самый разрушительный для всех остальных. Git "нравится", когда ветви растут, и "не нравится", когда они теряют коммиты.
Как работает слияние (сокращенно)
Давайте теперь посмотрим, что произойдет, если вы запустите git merge dev
(master
снова указывает на J
). Git нуждается, для этой операции слияния, три ввода фиксирует. Одним из них является ваш текущий коммит, слияние которого вызывает ours
. Одним из них является любой другой коммит по вашему выбору, который объединяет вызовы theirs
. третий один - который в некотором смысле является первым; по крайней мере, он получает номер 1 в мгновение - Git находит сам по себе.
Используя git merge dev
, вы выбрали коммит L
в качестве коммита theirs
. Это потому, что имя dev
выбирает коммит L
. Git теперь работает в обратном направлении от обеих подсказок ветвления, чтобы найти лучший общий коммит , который в данном случае явно является коммитом H
. Git называет это базисом слияния .
Помните, что каждый коммит содержит снимок. Так что commit H
имеет снимок, а commit J
имеет его, а commit L
- один. Git может в любое время сравнить любые два снимка , чтобы увидеть разницу. Команда, которая делает это для вас - git diff
. Операция объединения хочет знать, что отличается, поэтому она запускает внутренний эквивалент:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed on master
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed on dev
Git теперь может объединить эти два набора изменений и применить комбинированный изменяет снимок в H
(не J
, не L
, а H
: база слияния). Если мы добавили файл, а они этого не сделали, Git добавит файл. Если мы удалили файл, а они этого не сделали, Git удалит файл. Если они изменили файл, а мы нет, Git изменит файл.
Результатом, если все пойдет хорошо, будет комбинация наших изменений и их изменений. Git вставит этот результат обратно в индекс - место, из которого Git делает коммиты, - одновременно обновляя наше рабочее дерево, чтобы мы могли видеть, что он сделал. Затем, поскольку все прошло хорошо, Git делает новый коммит.
Новый коммит, который мы назовем M
для merge
, имеет не одного, а двух родителей , Родитель first такой же, как обычно: M
ссылается на существующий коммит J
, где master
был моментом go. Но Git добавляет коммит L
, который мы выбрали для слияния, в качестве второго родителя. Итак, теперь картинка выглядит так: 2
I--J
/ \
...--F--G--H M <-- master (HEAD)
\ /
K--L <-- dev
Commit M
имеет снимок. Его снимок был сделан Git (а не вами), который объединил изменения от общей начальной точки - базы объединения - и применил объединенные изменения к базе объединения снимок.
Если вы получаете конфликтов слияния , индекс приобретает расширенную роль и помогает в разрешении конфликтов. Вам решать, а не Git, чтобы определить окончательный снимок в окончательном коммите слияния M
. Но если нет конфликтов слияния, Git обычно выполняет слияние самостоятельно. Обычно является здесь важным словом.
2 Фиксация N
все еще существует, но без возможности найти это, Вы больше не можете видеть его, и нам не нужно беспокоиться, чтобы нарисовать его. В конце концов, Git удалит его полностью - обычно через некоторое время после, по крайней мере, 30 дней. А до тех пор вы можете вернуть его обратно, если хотите: вам просто нужно найти его га sh ID.
Это нормальное слияние; вам может потребоваться перезапись слияния
Предположим, что вы можете сказать Git: Начать слияние, но пока не делать коммит слияния M
. Git найдет слияние Основываясь как обычно, объедините ваши изменения и их изменения как обычно, и обновите свое рабочее дерево и индекс Git как обычно ... и затем остановитесь, не делая фиксацию.
Вы можете сделать точно что, используя git merge --no-commit
. Сделав это, вы можете заменить результат слияния на что угодно. Поместите любой файл в свое рабочее дерево и используйте git add
, чтобы Git скопировал его в индекс. Индексная копия, которая будет go в новом коммите слияния, теперь соответствует любому файлу, который вы поместите в рабочем дереве.
Здесь вы можете добавлять новые файлы и удалять их полностью. Что бы вы ни делали, это до вы: вы контролируете все на этом этапе. Вы можете сделать слияние любым контентом, который вам нужен.
То, что вы хотите - в зависимости от вашего вопроса, в любом случае; подумайте, действительно ли это действительно , что вы хотите - это полностью игнорировать разницу от базы слияния H
до вашего коммита J
, и просто взять разницу от H
до их comimt L
. Конечно, это будет точно соответствовать снимку в коммите L
.
Существует быстрый и простой способ, с помощью git merge -s ours
, сказать Git, что он должен полностью игнорировать изменения их ветви и просто используйте вашу версию всего, т. Е. Для принятия J
. К сожалению, это противоположно тому, что вы хотите здесь: вы запрашиваете способ полностью игнорировать изменения вашей ветки и просто использовать их версию всего, то есть принять коммит L
.
К счастью, есть довольно волшебная команда 3 , которую вы можете использовать, чтобы было быстро и легко, чтобы получить то, что вы хотите здесь:
git merge --no-commit dev
git read-tree --reset -u dev
Эта команда git read-tree -u
сообщает Git: Заменить индекс и содержимое моего рабочего дерева файлами, которые хранятся в коммите, который я здесь называю. Опция --reset
сообщает Git выбрасывать конфликтные записи, если слияние порождает конфликты слияния (и должно быть безвредным, если нет). Итак, теперь индекс и ваше рабочее дерево соответствуют снимку в L
. Теперь вы можете завершить sh слияние: 4
git merge --continue
Это сделает новый коммит слияния M
из содержимого, хранящегося в индексе, что - благодаря -u
опцию команды git read-tree
- вы также можете увидеть в вашем рабочем дереве. Итак, теперь у вас есть:
I--J
/ \
...--F--G--H M <-- master (HEAD)
\ /
K--L <-- dev
, где контент коммита M
точно соответствует контент коммита L
. Первый родительский из M
по-прежнему J
, а второй по-прежнему L
, как это было бы для любого другого слияния. Но вы «убили» изменения, которые были у вас в I
и J
, в то время как только добавили коммит к master
. Все остальные Gits будут достаточно счастливы, чтобы добавить совершить M
к их коллекциям подтверждений.
3 Это не совсем волхвов c на всех. Это команда plumbing , предназначенная для использования в сценариях, а не для пользователей. Команда read-tree поразительно сложна: она реализует (некоторые из) слияния, операции с поддеревьями и все другие виды хитрости. Это существенная часть причины, по которой существует индекс, который в противном случае является болезненным.
4 Если хотите, вы можете вместо этого запустить git commit
. В старых Git версиях, в которых нет git merge --continue
, вам придется использовать git commit
. Можно использовать git commit
напрямую: все, что делает git merge --continue
, это сначала проверяет, что есть слияние с fini sh, а затем запускает git commit
для вас. Используя git merge --continue
, вы проверяете, что действительно завершаете слияние.
Рассмотрите и эту опцию
Предположим, вы не хотите слияния. То есть предположим, что вы хотите сохранить прогрессию:
I--J <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
так, как есть, но добавьте новый коммит O
к master
, у которого parent равен J
, но чей контент совпадает с L
на dev
?
Это тоже легко сделать. Сделав недавно git checkout master
, чтобы ваш индекс и рабочее дерево соответствовали J
, вы теперь запускаете:
git read-tree -u dev
(без предшествующего слияния не может быть конфликтов слияния, поэтому --reset
не требуется).
Это заменяет индекс Git и содержимое вашего рабочего дерева, читая их из коммита L
, как мы это делали в примере слияния. Теперь вы можете запустить git commit
, чтобы сделать O
:
I--J--O <-- master (HEAD)
/
...--F--G--H
\
K--L <-- dev
content commit O
теперь соответствует содержимому L
; история , полученная при запуске с master
- коммит O
- и обратная работа читает O
, затем J
, затем I
, затем H
и т. д.
(Какой из этих вариантов вы хотите? Это ваше дело.)