Давайте начнем с этого:
Возможная теория, которую я сейчас рассматриваю: может ли это быть результатом git commit --amend
?
Да.
Теперь давайте вернемся к этому, потому что здесь есть скрытое предположение, которое является ключевым фактором:
Я использую git в репозитории, для которого я являюсь единственным участником.Хранилище размещено на GitHub.
Это не совсем верно.Здесь вы имеете в виду, что на GitHub есть репозиторий a .Это не репозиторий : это один из нескольких или многих.В этом случае это один из двух.Другой на вашей собственной машине!
Для правильного объяснения, смотрите любой из моих более длинных ответов.См. Также веб-сайт Think Like (a) Git , который имеет много важных сведений.Но, для краткости, помните, что здесь есть два репозитория: ваш и Git-репозиторий на GitHub.
Когда вы делаете коммит, он однозначно идентифицируется по своему хэш-идентификатору - большой некрасивой строке шестнадцатеричных цифр, напримеркак cfacddfb5395e0fa3676fbc1e19457c45c7c3529
.Это то, что ваш Git хранит в названии вашей ветки master
.Каждый коммит также хранит хэш-идентификатор своего предыдущего или родительского коммита (ов).Фиксация слияния - это фиксация как минимум с двумя родителями.Когда имя ветви или коммит хранит хэш-идентификатор коммита, мы говорим, что это имя или этот коммит указывает на целевой коммит.
Мы можем нарисовать такие вещи как (Горизонтально ориентированные) графики с последним коммитом вправо:
...<-F <-G <-H <--master
Git присоединяет имя HEAD
к имени ветви, так что если у вас есть несколько имен веток, он знает, какая из них является текущей веткой.Стрелки внутри коммитов не могут измениться - ничего внутри любой коммит не может когда-либо измениться, поэтому нам не нужно рисовать внутренние стрелки:
...--F--G--H <-- master (HEAD)
Когдавы запускаете git commit
, обычная процедура состоит в том, чтобы заморозить все, что есть в вашем индексе прямо сейчас, в новый снимок, сделать родительский снимок текущим коммитом , используя указатель из имени ветви, на которое HEAD
присоединено:
...--F--G--H--I
, а затем Git вставляет хэш-идентификатор нового коммита в имя текущей ветви, так что теперь он указывает на новый коммит:
...--F--G--H--I <-- master (HEAD)
Как git commit --amend
работает
Мы только что увидели, что git commit
:
- замораживает текущий индекс в снимок;
- записываетновый коммит с текущим коммитом в качестве родителя;
- записывает хэш нового коммита в текущую ветвь.
Все, что делает --amend
, это изменение, чтобы изменить шаг 2:вместо использования текущего коммита в качестве родителя нового коммита, --amend
usES родителей текущего коммита в качестве родителей нового коммита.(Если существует несколько родителей - если текущий коммит является слиянием - новый коммит получает всех родителей текущего коммита. Если это обычный однократный коммит без слияния, новый коммит получает одного нового родителя.) Поэтому вместопереходя от ...-F-G-H
к ...-F-G-H-I
, мы получаем:
H
/
...--F--G--I <-- master (HEAD)
То есть --amend
выталкивает текущий коммит с пути .
Thisотлично работает, если вы не нажали
Если вы нигде не нажимали коммит H
, его изменение теперь работает нормально.Новый коммит I
вступает во владение;коммит H
исчезает, больше никогда его не увидеть.(Вы можете найти его в своем собственном репозитории некоторое время - по крайней мере, 30 дней по умолчанию, пока не истечет срок действия записей reflog, которые помнят его. Но он не отображается в обычном выводе git log
.)
Ноон потерпит неудачу, если у вас есть
Но если у вас есть отправленный коммит H
в другой Git, например, в хранилище, которое вы храните на GitHub, то каждый раз, когда вы обращаетесь к с ними , они предложат вам снова зафиксировать H
, как важный коммит в их master
ветви.Это то, что случилось в этом случае.
У вас был пуshed commit H
для GitHub.Затем ваш Git вытолкнул ваш H
с пути, как указано выше, но GitHub все еще имел H
.Ваш Git помнит это, хотя, если ваш Git забыл или вы стерли его память, ваш Git будет напоминаться при следующем вызове Git на Git на GitHub.Итак, теперь у вас есть это:
H <-- origin/master
/
...--F--G--I <-- master (HEAD)
С этого момента, когда вы работаете в своем хранилище, коммит H
остается видимым.Вы сделали коммиты J
(271056c1c34444dbb3b8c2b6a67efae67f27b44b
) и K
(798afefabe24027a955853771072755c08318b47
):
H <-- origin/master
/
...--F--G--I--J--K <-- master (HEAD)
В этот момент вы запустили git pull
, когда ваш Git вызывал Git на GitHubи получите любые новые коммиты от GitHub (их не было) и обновите свой origin/master
, чтобы он соответствовал master
(все еще указывая на коммит H
).Затем ваш git pull
получил ваш Git merge их коммит H
с вашим коммитом K
, создав новый коммит с двумя родителями L
:
H_ <-- origin/master
/ `----__
...--F--G--I--J--K-=L <-- master (HEAD)
, и эточто ты видишь сейчас.(Ну, сейчас вы также попросили GitHub Git установить для их master
значение L
, что они и сделали, так что origin/master
вашего хранилища Git тоже перейдет на L
.)
Чтобы исправить это, вы должны заставить всех остальных Git забыть H
После того, как вы "исправите" H
, вы должны найти все другие репозитории Git, которые имели H
и убедить их использоватьI
вместо.В этом случае задействован только один другой Git: тот, что на GitHub.Вы можете запустить:
git push origin master
, который заставит ваш Git вызвать Git на GitHub и вежливо попросить, чтобы они изменили свой master
, чтобы он указывал на ваш коммит замены I
.Но если вы сделаете это, вы обнаружите, что они говорят нет, я не буду этого делать, потому что это потеряло бы коммит H
. Форма этой жалобы:
! [rejected] master -> master (non-fast-forward)
В этом случае вы знаете, в чем проблема, и что вы хотите их потерять (забыть) совершить H
.Таким образом, вы можете использовать git push --force
или более изящный вариант git push --force-with-lease
.
. Они преобразуют ваш запрос (, пожалуйста, обновите master
) в команду: обновите свойmaster
! GitHub все еще может отказаться (и будет, если у вас нет прав на это), но в целом они будут подчиняться команде, в которой они отклонили запрос.Опция --force-with-lease
работает как проверка безопасности: вместо того, чтобы просто сказать сделать это в любом случае , ваш Git говорит им: Я думаю, что ваш мастер настроен на,Если это так, замените его.Если нет, дайте мне знать. Это то, что вы можете использовать в общем хранилище, в случае, если другие люди также подталкивают к GitHub master
.