У меня есть следующая git история, не совсем понятно, как я туда попал ...
Вы, я предполагаю / верю, получили, используя git rebase
- возможно в форме git rebase -i
, как в ответе Карана Диллона - и затем git pull
. Сама по себе ребас хороша, но вам нужно знать, что она делает и какие у нее последствия.
Есть ли способ исправить это, удалив копии и создав историю без форка?
Да, но есть некоторые несущественные, незначительные или, может быть, довольно серьезные, неприятные последствия для других людей в процессе ... то есть , если там Другие пользователи origin
участвуют в репозитории.
Метод rebase -i
может работать очень хорошо (см. ответ Карана Диллона снова), но значительно проще - просто:
git reset --hard HEAD~1
(но сначала убедитесь, что с git status
вы не возражаете против замены всех файлов в вашем индексе и рабочем дереве, т. Е. Что git status
говорит nothing to commit, working tree clean
). Проверьте результат с помощью git log
, чтобы убедиться, что он вам нравится.
Важный шаг в конце, независимо от того, как вы получаете набор коммитов, который вы хотите, состоит в использовании варианта git push --force
для обновления другой Git репозиторий, задействованный здесь. То есть, в конце концов, вы должны направить другому Git, первому, чтобы отказаться от некоторых из его коммитов. Тот факт, что у вашего Git есть ваш origin/master
- ваша копия исходного происхождения master
- означает, что в этот момент они также имеют все эти коммиты (и, возможно, еще больше!).
Если репозиторий Git по любому URL, который у вас есть, ваш Git, помнящий имя origin
, активно используется другими, вы должны предупреждать их всякий раз, когда вы принудительно используете общий репозиторий Git потерять некоторые коммиты. Если в другом репозитории Git нет других пользователей, например, если это ваша личная копия GitHub, больше не нужно никого предупреждать, так что все в порядке.
Как только вы получите «правильная» история, вам нужно запустить:
git push --force-with-lease origin master
(что относительно безопасно, но вы все равно должны предупреждать других пользователей) или:
git push --force origin master
(что менее безопасный, особенно если это общий репозиторий в начале). Вариант -with-lease
использует ваши собственные Git origin/master
- его память их Git * master
- чтобы убедиться, что их Git * master
не имеет изменилось с тех пор, как вы в последний раз имели возможность посмотреть вещи и решили, что да, вы действительно хотите, чтобы они забыли их коммитов.
Множество деталей
Давайте посмотрим на история здесь снова:
* b4c394e (HEAD -> master, origin/master) I
|\
| * 17738ee G-copy
| * 6300744 F-copy
| * 62dbb41 E-copy
| * 951a5d9 D-copy
* | 05bfe4f H
* | 2701442 G
* | 242cf72 F
* | 2c9c7b6 E
* | b4bf474 D
|/
* 0f445d1 C
* ac8114f B
* 87759e6 A
Одним из приятных свойств git log --graph
является то, что он показывает происхождение коммитов первый-второй-второй, рисуя первого родителя в качестве прямой вертикали внизу, т. е. первый родительский элемент b4c394e
(коммит слияния) равен 05bfe4f
(коммит H
). секунда - это 17738ee
(G-копия).
Это указывает на то, что вы на самом деле сделали или имели коммит 951a5d9
(который вы называете D-copy здесь), 62dbb41
, 6300744
и 17738ee
- в таком порядке - сначала, и либо отправил их другому Git через origin
с git push
, либо получил их от другого Git через origin
.
Но, возможно, в оригинальном коммите D
, который вы называете D-copy
, было что-то, что вам не понравилось. Возможно, это было так же просто, как опечатка в сообщении коммита. По какой-то причине вы использовали коммит, вероятно, git rebase
, чтобы сообщить ваш Git: Мне не нравится 951a5d9
, затем 62dbb41
, затем 6300744
, затем 17738ee
Вместо этого я хотел бы извлечь 951a5d9, немного изменить его, а затем сделать новую и улучшенную копию.
Эта новая и улучшенная копия оказалась b4bf474
: коммит, который вы вызываете D
сейчас. Rebase автоматически копирует все последующие коммиты, потому что он должен: копия оригинала E-copy
, которую вы называете E
, должна использовать b4bf474
в качестве родителя, поэтому это должна быть копия с чем-то измененным. Новый коммит оказывается 2c9c7b6
. По той же причине коммит, который вы называете F-copy, должен быть скопирован в тот, который вы называете F, и так далее. В итоге у вас есть четыре новых и улучшенных коммита, последовательно, начиная с b4bf474
и заканчивая 2701442
.
Ни одна из этих новых и улучшенных копий не существует в других Git хранилище закончено на origin
. Он все еще имеет 17738ee
как последний master
коммит. Ваши собственные Git выбросили ваши оригиналы (тот, который вы называете копиями), потому что новые и улучшенные копии (те, которые вы называете оригиналами), ну, в общем, новые и улучшенные.
Затем вы сделали коммит H
, который является совершенно новым. Затем вы пошли отправлять свои новые коммиты - новые и улучшенные, за которыми следует совершенно новый - на Git в origin
, с git push origin master
. Но по какой-то причине вы бежали git pull
до того, как ваш git push origin master
был успешным.
(Если вы бежали git push origin master
до , вы бежали git pull
, другой Git, более в origin
сказал "нет". Более конкретно, ваш Git отправил им пять новых коммитов D
-through- H
и попросил их изменить свое имя master
, чтобы вспомнить H
в качестве последнего commit на их ветке. Они заметили, что это отбросит D-copy
-through- G-copy
. Поэтому они сказали нет, я бы потерял некоторые коммиты , что происходит как rejected (non-fast-forward)
сообщение об ошибке вместе с подсказкой, предлагающей использовать git pull
.)
К сожалению, git pull
означает, что запустить git fetch
, затем запустить git merge
в зависимости от того, что Прицепная пила . Выборка получила их master
, которые все еще идентифицировали коммит 17738ee
в это время. Затем ваш Git сделал:
git merge -m "merge branch master of <url>" 17738ee
(более или менее), который создал коммит слияния b4c394e
(коммит I
), который вы видите в верхней части вывода git log
. Этот коммит объединяет (объединяет) коммиты 05bfe4f
и 17738ee
. Затем вы запустили:
git push origin master
На этот раз вы отправили им коммиты, включая I
(не только H
), которых у них еще не было, и попросили их установить master
указать, чтобы совершить I
. С ними все было в порядке: это не потеряло коммит 17738ee
, потому что начиная с b4c394e
и работая в обратном направлении, они все еще могут найти коммит 17738ee
: это второй родительский элемент b4c394e
.
То, что вы в конечном итоге будете делать, как бы вы это ни делали, - это создать некоторую строку коммитов, которая заканчивается коммитом H
- то есть, тем же идентификатором ha sh, который у вас есть сейчас - или каким-то новым - и - улучшенная копия коммита H
(т. е. несколько другой идентификатор ha sh: идентификатор ha sh - это real имя коммита; тема и сообщение - это просто данные в рамках фактического коммита ). Затем ваше имя ветки master
будет указывать (сохранить ha sh ID) этого последнего коммита. Сам коммит хранит идентификатор ha sh предыдущего коммита, который также следует считать содержащимся в ветке master
. Предыдущий коммит содержит ha sh ID следующего коммита назад и т. Д.
Как только ваш master
имеет правильный ha sh ID, вы будете использовать git push
для отправки любого нового фиксирует, что у вас есть, но они этого не делают, другому Git по URL-адресу, хранящемуся под именем origin
. Эта строка коммитов заканчивается коммитом 05bfe4f
или его новой улучшенной заменой. Затем вам нужно приказать им - а не просто вежливо попросить их - установить их master
для хранения того же идентификатора ha sh, который вашего master
держит. Это шаг git push --force
.
Если вы используете простой (и намного более старый) git push --force
, ваш Git просто отправляет команду: установите master
! Если вы используйте более новую (но уже очень стандартную) git push --force-with-lease
, ваш Git отправляет более сложную команду: Я думаю, что ваш master
- это ______. Если это так, установите вместо этого своего мастера на _____. Скажите, обновляете ли вы свои master
. Ваши Git заполняют эти пробелы соответствующими идентификаторами га sh.
Их Git затем подчиняется или не отправляет и отправляет ваш Git результат. Затем ваш Git либо сообщает вам, что pu sh преуспел, и обновляет ваш origin/master
, либо сообщает вам, что он потерпел неудачу, но этого не происходит. Если это произошло из-за того, что хэши не совпадали, их master
изменилось, и вам нужно запустить git fetch
, чтобы обновить origin/master
, а затем посмотреть, что происходит, черт возьми.