Git не пу sh меняет , а фиксирует . Это потому, что Git не не сохраняет изменений.
Каждый коммит содержит полный, полный снимок всех своих файлов. Это data часть коммита. Каждый коммит также содержит некоторые метаданные , например, кто его сделал (имя и адрес электронной почты), когда и почему (сообщение журнала). Вы можете просмотреть коммит как изменения, сравнив снимок с моментальным снимком предыдущего коммита, но это все еще полный снимок.
Каждый коммит имеет свой уникальный уникальный идентификатор ha sh. Этот ha sh ID означает , что коммит: никогда никакой другой коммит, и никакой другой репозиторий Git нигде не может использовать этот коммит ha sh ID для любого другого коммита. Каждый Git репозиторий, имеющий этот коммит, использует , который га sh ID для него. (Вот почему идентификаторы коммитов ha sh такие большие и некрасивые: они должны быть уникальными!)
Между тем, внутри метаданных для каждого коммита коммит содержит необработанный идентификатор ха sh некоторых ранее или родительский коммит. Мы говорим, что этот коммит указывает на его родителя. Если мы нарисуем диаграмму нескольких коммитов, мы увидим, что они имеют тенденцию становиться обращенными назад, линейными цепочками:
... <-F <-G <-H
, где H
- это последний или последний коммит. У него большой некрасивый идентификатор ha sh, который я только что назвал H
, и commit H
содержит фактический идентификатор ha sh предыдущего коммита G
, который содержит идентификатор ha sh еще более ранний коммит F
и так далее. Эти обратные цепочки позволяют с Git по показывать коммит в виде набора изменений, который намного меньше (и полезнее), чем просто каждый раз показывать весь снимок.
Что означает ветвь в Git, так это то, что она позволяет вам - и Git - быстро найти этот последний коммит. Я перестану рисовать внутренние стрелки как стрелки (из-за лени), но продолжайте рисовать имена ветвей в виде стрелок:
...--F--G--H <-- master
Имя master
содержит га sh ID последний коммит H
.
Я клонировал репо, изменил несколько файлов, и теперь я хочу добавить sh в исходный мастер.
Технически, вы клонировали репо, что дало вам все их коммитов , и сделали вас своим собственным именем master
. Ваш клон также запомнил идентификатор sh последнего коммита в их master под вашим именем origin/master
, который мы можем нарисовать так:
...--G--H <-- master (HEAD), origin/master
(This HEAD
дело в том, как Git запоминает, какое имя ветви вы используете.)
Когда вы сделали новый коммит, он получил новый уникальный идентификатор ha sh. Давайте просто назовем это I
для краткости:
...--G--H <-- origin/master
\
I <-- master (HEAD)
Обратите внимание, как ваш master
теперь указывает на фиксацию I
, а не H
. Но вы все равно можете найти H
двумя способами: I
указывает на H
и origin/master
указывает на H
.
Между тем, в их хранилище Возможно, кто-то добавил новый коммит или два:
...--G--H--J--K <-- master
Помните, это их хранилище (и их master
), а не ваше.
Когда вы запускаете git push origin master
, ваши Git вызывают их Git. Ваш Git говорит им, что вы совершили I
, и вы хотели бы передать его им. Ваш Git говорит им, что родитель I
H
, и они говорят хорошо, у меня уже есть этот коммит, просто дайте мне I
. Вы отправляете им I
:
I [your commit]
/
...--G--H--J--K <-- master
Неважно, будем ли мы рисовать I
выше или ниже или рисовать более ответвленно как:
I [your commit]
/
...--G--H
\
J--K <-- master
пока мы рисуем все соответствующие коммиты и их соединения.
Теперь ваш Git просит их - их Git - установить их master
для указания на коммит I
, который у вас обоих сейчас есть. Если они выполнят этот вежливый запрос , у них будет:
I <-- master
/
...--G--H--J--K
Они больше не смогут найти коммиты J-K
, потому что их Git находит коммиты, используя имя своей ветви. Он находит I
, что приводит к H
, а затем G
и т. Д., Но никогда не идет вперед.
Если вы используете git push --force
, вы изменяете свой вежливый запрос на команду: Установите свой master
! Если они подчинятся, они потеряют коммиты J-K
.
Что вы должны сделать с этим?
Что вам понадобится, если у них есть эти коммиты , это один из способов отправки ваших коммитов, чтобы они не потеряли своих .
У вас есть несколько вариантов. Одним из них является использование git merge origin/master
до совмещения вашей работы с их работой. Другой - использовать git rebase
, что более сложно.
Объединение
Для объединения используйте git fetch
, чтобы убедиться, что ваши собственные Git соответствуют их, а затем использовать git merge origin/master
. 1 Это делает новый коммит слияния , который я нарисую как M
:
I-----M <-- master (HEAD)
/ /
...--G--H--J--K <-- origin/master
Что делает коммит слияния объединение состоит в том, что он указывает на более одного родителя . Здесь два родителя - коммиты I
и K
. Если теперь у вас есть Git, позвоните на их Git и отправьте master
, ваш Git предложит им совершить M
. Они возьмут M
. Поскольку у M
есть два родителя, ваш Git предложит I
и K
. У них есть K
, но не I
, 2 , поэтому они тоже возьмут I
, и ваш Git предложит H
. У них есть H
, поэтому они просто возьмут M
и I
в конце.
Затем ваш Git попросит их установить master
для указания M
. ничего не потеряет , поэтому они, вероятно, примут этот запрос. Ваши собственные Git теперь будут обновлять ваши собственные origin/master
- ваши воспоминания об их master
- соответственно.
1 Если вам действительно нравится git pull
, вы можете используйте git pull
до , комбинируйте шаг git fetch
с шагом git merge
. Все, что git pull
делает, это запускает обе команды. Это лишает вас возможности вставлять Git команды между двумя командами, а для Git новичков я нахожу это более запутанным, поэтому я предпочитаю хранить их как отдельные команды.
2 Они могут иметь I
с вашей предыдущей попытки или нет, в зависимости от их Git версии ... и от того, предприняли ли вы попытку после всех , Если у них есть I
, это нормально: I
га sh ID уникален для , который фиксирует.
Rebase
Что git rebase
в двух словах означает копировать некоторые коммиты в новые и улучшенные версии, а затем перемещать имя ветки.
Ничего - не вы и не Git сам по себе - может изменить любой существующий коммит. Все коммиты заморожены на все время. Вот что заставляет работать идентификаторы ha sh. Но вы можете взять коммит, и поработать над ним, и сделать новый коммит, который так же хорош, как и оригинал, с точки зрения того, что изменения он делает, и лучше чем новый коммит в том, что он идет в нужное место.
То есть, предположим, у вас есть:
I <-- master (HEAD)
/
...--G--H--J--K <-- origin/master
Что если есть простой способ получить Git копия существующего коммита I
в новый и улучшенный коммит, который приходит после коммита K
?
Есть, и это git rebase
. Мы запускаем:
git rebase origin/master
и Git перечисляет все коммиты на нашем master
(I
, H
, G
, ... на спине), а затем вычеркивает из В этом списке перечислены все коммиты, которые находятся на origin/master
(K
, J
, H
, G
, ...). Это оставляет коммит I
. (Если бы у нас было больше коммитов, таких как I-L
, у нас был бы этот список здесь, и следующий шаг скопировал бы два коммита вместо одного.)
Наш Git затем использует detached Режим HEAD (который мы просто замаскируем) для копирования каждого коммита, по одному, после комита K
, который мы назвали origin/master
:
I <-- master
/
...--G--H--J--K <-- origin/master
\
I' <-- HEAD
Скопировав все коммиты, наш Git затем восстанавливает наше имя master
здесь, до последнего скопированного коммита, и повторно присоединяет HEAD
к master
:
I [abandoned]
/
...--G--H--J--K <-- origin/master
\
I' <-- master (HEAD)
Поскольку коммит I
не имеет имени , мы не найдем его напрямую, а поскольку I'
указывает на K
, что указывает на J
, что указывает до H
, что указывает на G
, мы также не найдем его таким образом. Может показаться, что мы каким-то образом изменили существующий коммит I
.
У нас - I'
не было совершенно другого идентификатора ha sh от I
, но поскольку мы единственные Git что у было I
, никто больше не должен знать это. Теперь мы можем запустить:
git push origin master
, чтобы отправить коммит I'
другому Git и попросить их установить их master
, чтобы указать I'
. Пока их master
все еще указывают на K
прямо сейчас, это будет успешным.