Вы уже исправили (?) Это, используя --allow-unrelated-histories
, и нет никакой реальной причины не оставлять это. Но если тебе все еще интересно ...
Что случилось и как вы (вероятно) попали сюда
Первый ключ к использованию Git - это понимание того, что Git - это commits . Конечно, для этого также необходимо, чтобы вы достаточно глубоко понимали, что такое коммит . Что это, довольно коротко и просто: это перманент (в основном) и неизменный (полностью) снимок плюс некоторые метаданные . Снимок содержит все ваши файлы - ну, все они на момент совершения фиксации - и метаданные имеют:
- ваше имя и адрес электронной почты, а также время, когда вы сделали коммит;
- ваше лог-сообщение, т. Е. почему вы сделали этот коммит; и, что особенно важно,
- идентификатор хеша коммита, который приходит за до этого коммита, определяется как родительский этого коммита.
Каждый коммит уникален - по многим причинам, включая отметку времени, упомянутую выше, - и каждый уникальный коммит получает уникальный хэш-идентификатор. Этот хеш-идентификатор, какая-то большая уродливая строка шестнадцатеричных символов, кажется случайным, но на самом деле это криптографическая контрольная сумма содержимого коммита. Это также Истинное Имя того коммита: этот идентификатор означает этот коммит и только этот коммит. Ни у какого другого коммита никогда не будет такого хеш-идентификатора. Этот идентификатор хеша всегда будет означать , что commit.
Git на самом деле находит фиксацию по хэш-идентификатору. Таким образом, идентификатор хеша имеет решающее значение. Конечно, людям также невозможно запомнить. Таким образом, Git дает нам способ запомнить последний идентификатор хэша, и таким способом будет имя ветви , подобное master
или dev
.
Имя должно помнить только последний коммит , поскольку каждый коммит запоминает хэш своего родителя самостоятельно. То есть, имея крошечный репозиторий всего с тремя коммитами, где мы заменяем фактические идентификаторы хеша одной заглавной буквой, мы можем нарисовать это:
A <-B <-C <-- master
Имя master
запоминает хэш-идентификатор C
. C
сам - фактический коммит, полученный по хеш-идентификатору - запоминает хеш-идентификатор B
, а B
сам запоминает хеш-идентификатор A
.
Когда что-то запоминает хэш-идентификатор какого-то другого коммита, мы говорим, что это что-то указывает на коммит. Таким образом, имя master
указывает на C
, C
указывает на B
, а B
указывает на A
. Эти три коммита - C
, затем B
, затем A
- являются историей в хранилище.
Обратите внимание, что A
никуда не указывает. Это буквально не может, потому что это был первый коммит. Не было более раннего коммита. Так что это просто не так, и Git называет это root commit. Все непустые репозитории должны иметь как минимум один корневой коммит. У большинства, наверное, ровно один ... а у тебя два.
Нормальное ветвление и слияние
Давайте кратко рассмотрим более обычный способ создания веток. Предположим, у нас есть только эти три коммита, на которые указывает master
. Мы просим Git создать новое имя ветки, указывающее на тот же коммит, что и master
:
A--B--C <-- dev (HEAD), master
Оба имени обозначают фиксацию C
, поэтому фиксация C
включена и является tip commit of - обеих ветвей, и все три фиксации находятся в обеих ветвях. Но теперь мы делаем новый коммит. Процесс создания нового коммита - с обычным редактированием и git add
и git commit
- создает новый снимок, добавляет наше имя, адрес электронной почты, метку времени и т. Д., Использует текущий коммит C
как сохраненный хеш, так и строит новый коммит. Новый коммит получает большой уродливый хэш-идентификатор, но мы просто назовем его D
:
A--B--C <-- dev (HEAD), master
\
D
С D
парent C
, D
указывает на C
. Но теперь происходит волшебство: Git записывает хэш-идентификатор D
в текущее имя ветви - к которому присоединено HEAD
- так что теперь у нас есть:
A--B--C <-- master
\
D <-- dev (HEAD)
и вуаля, у нас есть новая ветвь. (Ну, у нас было это раньше, указывая на C
. Большинство людей не любят думать об этом, хотя, они хотят назвать D
ветвью. Фактически, ветвь D-C-B-A
!)
Со временем мы добавляем коммиты в обе ветви:
A--B--C-----J----K----L <-- master
\
D--E--F--G--H--I <-- dev
Мы git checkout master
и git merge dev
. Git найдет для нас коммит base merge , где dev
и master
расходятся. Это, очевидно, коммит C
, поскольку именно здесь две ветви воссоединяются в прошлом. Git будет сравнивать C
против L
, чтобы увидеть, что мы изменили на master
, сравнить C
против I
, чтобы увидеть, что мы изменили на dev
, и объединить изменения. Git применяет объединенные изменения к снимку в C
- к базе слияния - и делает новый коммит слияния M
, который идет на текущая ветка HEAD
, как обычно, обновляет имя этой ветви так, что master
указывает на M
:
A--B--C-----J----K----L--M <-- master (HEAD)
\ /
D--E--F--G--H--I <-- dev
Что особенного в M
то, что он имеет две обратных ссылки: он возвращается к L
, как и все коммиты, но у него есть второй родительский элемент I
, который является текущим Тип коммит ветки dev
. Однако, кроме двух родителей, он довольно обычный: у него, как обычно, есть снимок, а также наше имя, адрес электронной почты, отметка времени и сообщение журнала.
Аномальное ветвление
В Git нет ничего, что помешало бы вам делать дополнительные root-коммиты. Это просто немного сложно. Предположим, что вы каким-то образом сделали это:
A <-- master
B--C--D--...--L <-- dev (HEAD)
Как только у вас возникнет такая ситуация, git checkout master; git merge dev
просто выдаст вам ошибку. Это связано с тем, что обычный метод поиска базы слияния - начиная с двух кончиков ветвей и работая в обратном направлении - никогда не находит общий коммит.
Добавление --allow-unrelated-histories
говорит Git , притворяясь , что существует специальная пустая фиксация перед обеими ветвями:
A <-- master (HEAD)
0
B--C--D--...--L <-- dev
Теперь Git может различать 0
против A
, чтобы увидеть, что вы изменили на master
, и 0
против L
, чтобы увидеть, что они изменили на dev
. На master
вы добавили каждый файл. На dev
вы также добавили каждый файл. Поскольку это разные файлы, способ объединить эти два изменения состоит в том, чтобы добавить master
файлы из commit A
в dev
файлы из commit L
, применить эти изменения к пустой нулевой коммит и фиксация результата, когда родители возвращаются к A
и L
:
A---------------M <-- master (HEAD)
/
B--C--D--...--L <-- dev
Как вы (вероятно) попали сюда
Существует опция git checkout
, git checkout --orphan
, которая устанавливает это состояние. Но это, вероятно, не то, что вы сделали. Состояние, которое это устанавливает, является таким же состоянием, в котором вы находитесь, когда создаете новый пустой репозиторий с git init
:
[no commits] <-- [no branches]
Нет веток, и все же Git скажет, что вы on branch master
. Вы не можете быть на master
: его не существует. Но вы, , хотя его не существует. Способ, которым Git управляет этим, заключается в том, что он помещает имя master
в HEAD
(на самом деле .git/HEAD
) без предварительного создания ветви с именем master
. Он не может создать ветвь, потому что имя ветки должно содержать действительный идентификатор хеша, а их нет.
Итак, когда вы запускаете git commit
, Git обнаруживает это аномальное состояние: HEAD
говорит master
, но master
не существует. Это то, что вызывает Git, чтобы сделать наш корневой коммит A
. Затем Git записывает хеш-код A
в ветвь, которая создает ветвь, и теперь мы имеем:
A <-- master (HEAD)
это именно то, что мы хотели.
Но предположим, что пока мы находимся в этом странном состоянии без коммитов, мы запускаем:
git checkout -b dev
Это говорит Git: Поместите имя dev
в HEAD
. Он делает это без жалоб, хотя master
тоже нет. Затем мы делаем наш первый коммит, но без видимой причины мы выберем B
в качестве однобуквенного заменителя для его хеш-идентификатора:
B <-- dev (HEAD)
Meanwhиль, запустив git init
здесь, затем git checkout -b dev
, затем что-то сделал и git commit
, мы перейдем к $ WebHostingProvider - будь то GitHub, или GitLab, или Bitbucket, или что-то еще - и используя его , сделайте меня новый репозиторий кликающие кнопки. У них обычно есть опция: создать начальный коммит с файлами README и / или LICENSE и такими . Если этот параметр установлен - или параметр не не отмечен, - они делают первый коммит и master
:
A <-- master (HEAD)
Теперь вы подключаете свой репозиторий к их и заставляете ваш Git загружать любые свои коммиты, которых у вас нет:
A <-- origin/master
B <-- dev (HEAD)
Теперь вы можете добавлять множество коммитов, не замечая, что ваша ветка dev
не связана с их веткой master
(которую ваш Git вызывает origin/master
).
Позже вы запускаете:
git checkout master
Ваш Git замечает, что у вас нет a master
, но у вас есть origin/master
. Поэтому ваш Git создает для вас a master
, указывая на тот же коммит, что и origin/master
, и присоединяет ваш HEAD
к вашему новому master
:
A <-- master (HEAD), origin/master
B--C--D--...--L <-- dev
и вуаля , вы в рассоле, в котором вы были.