TL; DR
Да, git branch -f
может делать то, что вы хотите. Осторожно, однако, что это будет делать то, что вы говорите, даже если это, возможно, не то, что вы хотели в конце концов. Попробуйте вместо этого использовать слегка магический git push .
. Еще лучше, не делайте этого вообще - см. Ниже.
Long
предположим, что у меня есть две ветви, A и B, и 3 фиксации C, D и E. Cродитель D и E, и я на ветви B, которая находится на коммите E.
Давайте нарисуем это. Давайте воспользуемся именами branch-A
и branch-B
, чтобы заглавные буквы всегда заменяли одинарные заглавные буквы (в противном случае слишком просто подумать, что, возможно, A
тоже стоит для хеш-кода фиксации):
D
/
C
\
E <-- branch-B
Вы не сказали нам, какое из трех коммитов branch-A
имен / точек, так что я не знаю, где поставить метку branch-A
на этом чертеже.
Можно ли без проверки на ветке A сбросить ветку A для принятия D?
Да. Поскольку вы упомянули «без проверки branch-A
», мы можем предположить, что вы в данный момент используете branch-B
(поскольку branch-A
и branch-B
являются только ветвями в этом хранилище). Мы также можем предположить, что имя branch-A
должно указывать на коммит C
сейчас, поэтому нарисуем , что:
D
/
C <-- branch-A
\
E <-- branch-B (HEAD)
Откуда вы знаете хеш-идентификатор фиксации коммита D
на первом месте? Там нет имен, по которым его можно найти. Но все же, пока вы знаете знаете это:
Я видел git branch -f
, и я, насколько я понимаю, выполнение git branch -f branch-A D
может делать то, что я хочу .. .
Это именно то, что он делает. (Существует предостережение, к которому мы вскоре доберемся, но это - это то, что он делает.) Учитывая хэш-идентификатор commit D
, git branch -f branch-A
заставляет имя branch-A
бытьуказывая там, независимо от того, куда он указывал ранее:
D <-- branch-A
/
C
\
E <-- branch-B (HEAD)
Обратите внимание, что ни один коммит никогда не изменяется. Ветвь names перемещается, но график фиксации остается прежним (даже если вам нужно изменить способ его рисования, чтобы сжать имена в :-)).
После того, как вы нарисовали коммиты - используя их настоящие хэш-идентификаторы, или эти замещающие однобуквенные имена, как я, - они останутся такими же навсегда, включая ссылки в обратном направлении от более позднего коммита, например D
илиE
, его непосредственному родителю (C
в данном случае). Однако, если вы уберете все имена из некоторого коммита - как это было в нашем рисунке с коммитом D
ранее - эти коммиты станут очень трудно найти. В конце концов - иногда через 14 или 30 дней, как правило, с подробностями, зависящими от настроек и записей reflog, и т.п. - Git действительно действительно удалит все коммиты, которые вы не можете найти, в следующий раз, когда git gc
запустится.
Остерегайтесь проблемы XY
Мы только что решили некоторую проблему Y, которую вы планируете использовать для решения несколько иной проблемы X. Давайте рассмотрим проблему X и представим оговорку, о которой я упоминал.
Вариант использования: я хочу выполнить git fetch, а затем обновить ... master
до [origin/master
].
Итак, предположительно, branch-A
фактически был заменой для master
, а коммит D
был указан origin/master
. Давайте нарисуем , что case:
D <-- origin/master (after `git fetch`)
/
C <-- master
\
E <-- branch-B (HEAD)
Здесь вы можете запустить git branch -f master origin/master
и получить:
D <-- master, origin/master
/
C
\
E <-- branch-B (HEAD)
Обратите внимание, что коммит C
остается достижимым: master
теперь указывает на фиксацию D
, но коммит D
указывает на фиксацию C
, которая является корневым коммитом графа. (У нас нет коммитов A
и B
, но если бы мы сделали, предположительно, они пришли бы до C
и, следовательно, все еще были бы достижимы.)
Но предположим, что ваши master
указывают на новый коммит G
, и фактическая картина такова:
D <-- origin/master
/
C--F--G <-- master
\
E <-- branch-B (HEAD)
Команда git branch -f master origin/master
будет неподвижным сделать master
точным указанием для фиксации D
:
D <-- master, origin/master
/
C--F--G
\
E <-- branch-B (HEAD)
Commit G
больше не может его найти! git log --all
не покажет. В конце концов, через 30 дней или около того, вероятно, git gc
удалит его по-настоящему вместе с коммитом F
, который также не может быть найден.
Для предотвращения мпереместив master
с G
назад на C
и затем снова на D
, вы, вероятно, захотите либо выполнить реальное слияние, выполнив git checkout master
, а затем git merge origin/master
:
D__ <-- origin/master
/ `--.
C--F--G--H <-- master (HEAD)
\
E <-- branch-B
или, возможно, перебазирование, выполнив git checkout master && git rebase origin/master
:
F'-G' <-- master (HEAD)
/
D <-- origin/master
/
C--F--G
\
E <-- branch-B
Перебазировка также теряет F
и G
, но только после копирования их в новые и улучшенные F'
и G'
, так что все в порядке, чтобы их потерять.
Если вы захотите найти их снова, в течение ~ 30 или более дней, в течение которых они задерживаются, они будут в выводе git reflog master
.
Работа с проблемой X
Давайте назовем задачу X «поддержание master
в актуальном состоянии, без потери коммитов».
Один из способов справиться с этим - просто git checkout master
изатем запустите merge или rebase в зависимости от ситуации. Мне нравится использовать git merge --ff-only
, который обновляет master
в режиме ускоренной перемотки вперед, перемещая его для фиксации D
тогда и только тогда, когда это можно сделать как ускоренную перемотку вперед, а не слияние-все операции. Если нет, я переоцениваю свои коммиты F
и G
. Должны ли они все-таки быть на ветке, чтобы я смог перемотать вперед master
? Должен ли я их перебазировать? Что должен делать с ними?
Если это нарушит мое текущее дерево работы, я могу использовать git worktree add
для выполнения этой работы. Добавленное рабочее дерево может быть на ветке master
, и тогда я могу делать там все, что мне нужно.
Помимо выполнения git checkout master
, однако, есть еще несколько способов решения этой проблемы:
Самое простое это , просто полностью удалить имя master
. Зачем вам это нужно? Вы начинаете с:
C <-- origin/master
\
E <-- branch-B (HEAD)
Затем запускаете git fetch
и получаете:
D <-- origin/master
/
C
\
E <-- branch-B
Вам вообще не нужен ваш master
.
Но если вам нравится иметь master
(по любой причине), вы можете использовать:
git push . origin/master:master
(Или:
git fetch . origin/master:master
Я нена самом деле они не используются, но если бы я это сделал, я бы предпочел вариант push
. Давайте рассмотрим только вариант push
.)
Имя .
действует как URL файла для«Другой» Git ваш Git будет вызывать здесь. Этот другой Git, ну, сам по себе! Так что ваш Git вызывает себя и просит или говорит ему что-то сделать. Оставшийся аргумент - это origin/master:master
- говорит вашему Git, что сказать вашему Git:
Эй, другой Git (это действительно я): убедитесь, что у вас есть коммит, чей хеш-идентификатормой origin/master
идентифицирует. Ах, я вижу, что у вас (у меня) есть этот коммит. Теперь, пожалуйста, если все в порядке - если это операция ускоренной перемотки вперед - настройте свой master
(который на самом деле мой master
), чтобы он тоже указывал на этот коммит.
Поскольку ваш Git говорит сам с собой, то, что он действительно делает, это использует тест «это операция ускоренной перемотки вперед», как это делал бы git merge --ff-only
. Ваше имя master
переместится в ускоренном порядке, чтобы соответствовать вашему имени origin/master
. Если это невозможно - например, если существует коммит F
- ваш Git отклонит свой собственный push-запрос.
Вы можете только использовать это в ветке, которую вы делаетеnot извлекли. 1 Если у вас есть разветвленная ветвь, Git хочет, чтобы вы вместо этого использовали git merge --ff-only
, чтобы ваш индекс и рабочее дерево обновлялись правильно.
1 В том, что представляет собой довольно серьезную ошибку, тест Git "проверена ли эта ветвь" проверяет только первичное рабочее дерево,без проверки каких-либо добавленных рабочих деревьев. Если вы используете git worktree add
, будьте осторожны!