LINUX.ORG.RU

GIT почему-то влепил коммит в середину истории. Теперь не могу откатиться.


0

1

Ох, не любит меня Git.

Сейчас произошло нечто, что выше моего понимания. Всегда делаю коммит и заливку в репозитарий командами:

git add . 
git commit -a 
git push

У меня, по большей части, все линейно. Есть всего две ветки - master и try_django. Ветка try_django - это тупиковая ветка (была раньше тупиковой), и должна была остаться тупиковой.

Картинка:

http://i.piccy.info/i9/7e33e6eee876e90a8a2425c8480d95ae/1385497698/73131/5974...

При последнем коммите 27.11.13 0:02 вылезло предупреждение, что что-то не так (я особенно не понял), и предложение сделать git pull:

 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:xintrea/local_rash.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again.  See the
'Note about fast-forwards' section of 'git push --help' for details.

Я сделал git pull, он завершился ошибкой:

Renaming apprash/config/config.php => misc/06_linuxthash_php/apprash/config/config.php
Auto-merging misc/06_linuxthash_php/apprash/config/config.php
CONFLICT (rename/modify): Merge conflict in misc/06_linuxthash_php/apprash/config/config.php
CONFLICT (rename/delete): Rename css/main.css->misc/06_linuxthash_php/css/main.css in 081262b214d515aba563ded9dc8eeb9ff0896cc6 and deleted in HEAD
Renaming index.php => misc/06_linuxthash_php/rash.php
Auto-merging misc/06_linuxthash_php/rash.php
Removing data/base/db_20120901172549.sqlite
Removing data/pic/team_avatar/0157a760f5710c2d92f44bb5e6f49817.png
Removing data/pic/team_avatar/c11f8869892fa7462b9970e2f29d87c6.png
Removing data/pic/team_avatar/team_avatar_avangard.png
Removing data/pic/team_avatar/team_avatar_dinamo.png
Removing data/pic/team_avatar/team_avatar_ksov.png
Removing data/pic/team_avatar/team_avatar_spartak.png
Removing data/pic/trainer_avatar/3f7ca692c08f26d11c339e84f5c1809e.png
Removing data/pic/trainer_avatar/eff8e5e721d198a4cc2addc965df5b8d.png
Removing main.html
Automatic merge failed; fix conflicts and then commit the result.

Тогда я снова сделал «три команды». Git что-то сказал про merge (причем, я прямой команды на merge не давал!):

[master c3e8a45] Merge branch 'master' of bitbucket.org:xintrea/local_rash
Counting objects: 50, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (30/30), done.
Writing objects: 100% (34/34), 4.09 KiB, done.
Total 34 (delta 19), reused 0 (delta 0)
To git@bitbucket.org:xintrea/local_rash.git
   081262b..c3e8a45  master -> master

И результат вы видите на картинке - коммит 27.11.13 0:09 со слиянием, которое мне никуда не впало.

Видно, что коммит 27.11.13 0:02 находится в середине истории (почему???), ПОСЛЕ более ранних коммитов.

Теперь задача: мне нужно к этому коммиту (27.11.13 0:02) вернуться.

И я не могу это сделать!

Пробую:

git checkout a906f16ce7147911e9f950d569f2aeeb51091970

где a9...70 - это ID коммита 27.11.13 0:02.

Не помогает, я не вижу файлов которые я делал при этом коммите.

Пробую:

git reset --soft a906f16ce7147911e9f950d569f2aeeb51091970

Такая же фигня, состояние не возвращается к состоянию, в котором находился проект при коммите 27.11.13 0:02.

Вопрос: Как вернуться к состоянию, которое было при коммите 27.11.13 0:02?

Вот история коммитов (git log) ветки master, если поможет. Сейчас в ней почему-то нет финального коммита 27.11.13 0:09, хотя на картинке он виден. Может быть это из-за git reset.

$ ./git_log.sh
commit a906f16ce7147911e9f950d569f2aeeb51091970
Author: xintrea <xintrea@gmail.com>
Date:   Wed Nov 27 00:02:45 2013 +0400

    - Добавлен начальный двухколоночный макет главной страницы

commit e99bd9cc6df72e57632064db849b31ac5cb3830a
Author: xintrea <xintrea@gmail.com>
Date:   Sun Nov 17 18:35:46 2013 +0400

    - Первый коммит после отката от Django

commit 0e7b5221595c44be888b27b980ab45c6ea2dde25
Author: xintrea <xintrea@gmail.com>
Date:   Tue Sep 4 23:12:02 2012 +0400

    Сделан перенос обнаруженных удаленных новостей в базу публикаций
    в раздел новостей

commit 577d9b379d12e51e87c25b24d2a53ceb72568f44
Author: xintrea <xintrea@gmail.com>
Date:   Sat Sep 1 10:53:36 2012 +0400

    Подготовлен скрипт lorpad, он начал наполнять базу 

commit 7a0757665ad7821342f1a221bae5d791e110cfa1
Author: xintrea <xintrea@gmail.com>
Date:   Mon Aug 27 01:25:30 2012 +0400

    Почти рабочий вариант с самодельным SQLite3 драйвером sqlite3pdo

commit 632f20b66ee8d9468732161ca1f210db1f79edce
Author: xintrea <xintrea@gmail.com>
Date:   Sun Aug 19 01:14:31 2012 +0400

    Сделан переход на CodeIgniter 2.1.2
    Исправлен PDO драйвер чтоб можно было работать с SQLite базой
    Заведена пробная база news
    Хранение сессий в базе данных временно отключено

commit 2b90796b230d31428c24d215a6ecbb28a7c2e0f0
Author: xintrea <xintrea@gmail.com>
Date:   Fri Aug 17 21:59:48 2012 +0400

    Заменен CodeIgniter Debug Tool bar на более свежую версию, взятую
    с github. Для установки пришлось изменить только конструктор в MY_Log.php
    Этот вариант показывает используемые файлы.

... 
★★★★★

прямой команды на merge не давал

git pull это git fetch и git merge, см. ман.

Я на картинке вижу что ты случайно создал ветку. Скорее всего, ты работал с репозиторием из разных мест и на каком-то этапе ты не сделал git pull и понесся править репозиторий.

Я рекомендую прочесть книгу по гит. Станет гораздо понятнее.

true_admin ★★★★★ ()

Диагноз предыдущего автора совершенно корректен. Для того, чтобы наглядно оценить сложившуюся после Ваших действий картинку, можете запустить gitk --all и посмотреть, как на данный момент устроен граф коммитов в Вашем репозитории. В принципе, можно обойтись командой git log --pretty=raw ... , но gitk очевидно нагляднее.

Добавлю, что существует возможность переписать историю, выправив весь набор коммитов в единую цепочку. Подчёркиваю: это действие НЕ является сколько-нибудь обязательным, и чаще всего проводится [неофитами git-а] для удовлетворения своего [ложного] чувства прекрасного. Однако, иногда такая возможность бывает полезна.

Итак, перед началом процедуры нужно выбрать последний перед нежелательным ветвлением коммит, допустим этот коммит имеет хэш abcdef. Выполняем команду git rebase -i abcdef. Эта команда пересоздаcт коммиты в текущей ветке поверх выбранного Вами коммита. Подчёркиваю: будут созданы новые коммиты, которые в принципе соответствуют Вашим старым, но являются всего лишь их копиями (а могут и не являться, если Вам это требуется, или если в процессе переналожения Вы будете вынуждены разрешать конфликты).

Итак, вначале эта команда предложит Вам выбрать порядок переналожения коммитов, запустив $EDITOR / core.editor со списком коммитов . Можете оставить предложенный git'ом список как есть, можете поменять порядок следования коммитов, можете вообще убрать какие-то коммиты. Я рекомендую выбившийся из цепочки коммит (см. gitk --all) накладывать первым (самым верхним) или последним (самым нижним) в списке.

После того, как список сформирован, нужно будет его сохранить и выйти из редактора. Далее git продолжит свою работу по возможности неинтерактивно.

Разрешение конфликтов. Будьте готовы к тому, что при наложении [выбившегося] коммита произойдёт конфликт, и автоматический процесс переналожения набора коммитов будет приостановлен до тех пор, пока Вы не разрешите все конфликтные места.

Для разрешения конфликтов удобно воспользоваться программой наподобие kdiff3 или meld, указав её git-у в качестве merge.tool:

apt-get install kdiff3
git config --global merge.tool kdiff3

Когда git оповестит Вас о том, что случился конфликт и Вам нужно его разрешить, то, чтобы продолжить процесс ребэйза, нужно будет выполнить

git mergetool

и последовательно разрешить все обнаруженные конфликты.

После того, как конфликты разрешены, нужно оценить [глазами или тестами] результат, и, если всё хорошо, выполнить

git rebase --continue

и git продолжит процесс переналожения коммитов до следующей конфликтной ситуации или до конца списка.

Если в какой-то момент до окончания Вы решите отменить весь процесс целиком, Вы можете прервать выполнение, а потом сказать

git rebase --abort

и всё вернётся к Вашему первоначальному состоянию, «как будто ничего не было».

После того, как процесс ребэйза завершён (запустите ещё раз gitk и убедитесь в том, что «всё красиво»), Вам нужно опубликовать новую цепочку [на bitbucket].

Помните, полученная цепочка коммитов - это совершенно другие коммиты, хоть и похожие на те, что уже залиты в репозиторий. Поэтому Вам придётся насильно переписывать историю.

Вообще, это ай-яй-яй, так делать низзя. [Гипотетические] пользователи Вашего репозитория уже могли склонировать его себе и начать какую-то свою работу поверх Ваших коммитов. Им придётся вытащить себе новую историю и перенакладывать свои коммиты поверх неё. Они могут разозлиться. Но если Вас это не пугает, то можете переписывать :)

У push для этого есть ключик -f . В крайнем случае, Вы можете удалить соотв. ветку на bitbucket.org и перезалить её снова.

Ну, вот как-то так :)

AlexM ★★★★★ ()
Последнее исправление: AlexM (всего исправлений: 2)

git commit -a

Это равносильно

git add .
git commit

andreyu ★★★★★ ()
Ответ на: комментарий от andreyu

git commit -a

Это равносильно

git add .
git commit

Почти. Это равносильно:

git add -u .
git commit
Dendy ★★★★★ ()

А что, git reset --hard HEAD~1 не помогает вернуться к предыдущему состоянию?

Или можно вообще сделать новый мастер, в котором не должно быть твоего тупикового коммита:

git branch -m old-master
git checkout -b master origin/master

alexeiz ()
Ответ на: комментарий от AlexM

Итак, перед началом процедуры нужно выбрать последний перед нежелательным ветвлением коммит

Проблема в том, что у меня нет нежелательного ветвления. У меня ветвление было вполне желательно, и я не ожидал что оно вот так вот выйдет боком.

Как я делал это ветвление, написано здесь:

Git: как откатиться назад, но оставить коммит для истории
Git: как откатиться назад, но оставить коммит для истории

После этого «отката» я перепроверил, что нахожусь в основной ветке, сделал тестовый коммит («Первый коммит после о...» на рисунке), убедился что коммит нормально коммитится.

А вот следующий за ним коммит привел к мерджу и к вот этому бардаку, хотя я его делал точно так же как и тестовый коммит.


Пока я смутно понимаю, что вы мне написали, но буду разбираться, надо же работу восстановить.


PS: Да, не любит меня Git. Решил повтыкать в ProGit, а git-scm.com недоступен. Он переехал что-ли куда-то, и я об этом не знаю?

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

Ну, с полной уверенностью можно предположить, что commit2 с Вашего рисунка оказался тем или иным способом на bitbucket, причём, в тамошней ветке master. Могу предположить, что перед тем, как сресетиться обратно на commit1, Вы выполнили git push <bitbucket> master.

Почему я в этом уверен? Потому что Ваш листинг №2 из нынешнего вопроса, про невозможность git push однозначно говорит, что состояния ветки master в Вашем локальном репозитории и ветки master на bitbucket разошлись, перестали вытягиваться в линию.

Вообще, на самом деле, git внутри устроен очень просто :)

Каждый коммит состоит из слепка некоторого дерева файлов и связанной с этим слепком метаинформации: кто сохранил состояние дерева, когда и зачем, с каким комментарием.

Кроме того, у каждого коммита есть 0, 1 или более «предков».

Когда Вы создаёте первый коммит в пустом репозитории, то у этого коммита оказывается 0 предков, то есть он становится корневым.

Когда Вы просто совершаете ряд последовательных коммитов, то у каждого из них оказывается ровно один предок - предыдущий коммит.

Когда Вы делаете мёрж, то получается коммит с 2 (или более, если мёрж более сложный) предками.

Таким образом, вся совокупность коммитов в репозитории представляет собой вершины однонаправленного графа, рёбрами которого является отношение «предок». Некоторые части этого графа могут быть совершенно не связаны друг с другом. То есть, абсолютно не связаны.

Следует помнить, что малейшее изменение в дереве файлов или мета-информации коммита приводит к созданию коммита с собственным, уникальным SHA1. Поэтому, когда выполняется git commit --amend, то даже Вы ничего не меняли явно, то меняется время сохранения коммита.

Заметьте, до сих пор я вообще ни разу не упомянул огров ветки. Собственно говоря, всему этому графу и git'овской машинерии по его созданию и модификации ветки не нужны, они превосходно существуют и без веток*. Можно создавать коммиты, не привязанные ни к каким веткам, перепрыгивать с коммита на коммит, используя только уникальный идентификатор коммита (sha1) итп

Какая-либо ветка в случае git'а - это просто метка, именованный указатель на какой-либо определённый коммит в графе. Нужны они, в основном для удобства человеков, а также для того, чтобы встроенный в git сборщик мусора, который запускается каждые N коммитов или N дней, мог определить, что некоторый коммит более не требуется, так как ни одна из существующих в репозитории веток-меток не указывает на цепочку коммитов, в которую включён данный коммит (здесь, натурально, прямые аналогии с тем, как работает GC в яве).

Ветки разделяются на локальные (те, которые предназначены для модификации в данном локальном репозитории) и ремоутные - это копии локальных веток в каком-либо удалённом репозитории. Вы можете зайти в каталог .git и глазами посмотреть на содержимое каталога .git/refs/ и файлика .git/packed-refs. Если хотите, можете создать новые или изменить существующие файлики в refs или записи в packed-refs, а потом выполнить команды git log, git branch или запустить графическую гляделку на дерево коммитов (gitk или qgit)

С учётом вышеизложенного коммит в git происходит так:

  1. Вы подготавливаете дерево файлов и добавляете его в индекс (как устроен индекс, пока не важно, важно, что в нём сохраняется точное состояние файлов). За формирование индекса ответственна команда git add (git add -i делает картинку более сложной, но непринципиально)
  2. далее Вы говорите git commit. git в ответ на это выполняет такую последовательность действий:
    1. Из .git/HEAD вычитывается ссылка на текущий верхний коммит. Если в .git/HEAD находится непосредственно SHA1 коммита (эта ситуация возможна, если Вы перед этим говорили git checkout abc123def), то используем его в качестве родительского. Если в .git/HEAD находится ссылка на какую-либо ветку (например, ref: refs/heads/master), то вычитываем, на какой sha1 указывает эта ветка, и используем результат как родителя.
    2. далее формируем из сохранённого в индексе состояния файлов и накопленной мета-информации навый коммит в репозитории (на git-scm.com есть хорошая статья, как сделать это при помощи колышков и скотчанизкоуровневых утилит). На выходе у нас SHA-1 только что сделанного коммита.
    3. после этого обновляем ветку-ссылку: если в HEAD было явно прописан SHA-1 предка, то пишем туда новый SHA-1. Если в HEAD была ссылка не ветку, то новый SHA-1 пишется в файлик этой ветки.
    4. очищаем индекс

Вот так, можно сказать, примитивно, всё и работает.

git fetch в этой схеме - это, в целом, получение из удалённого репозитория точных копий (с тем же sha-1) коммитов, отсутствующих в репозитории локальном и сохранение в .git/refs/remotes/<repository-id>/... тех веток (указателей), которые имеются в удалённом репозитории в .git/refs/heads.

git push - это отправка в удалённый репозиторий точных копий локальных коммитов, отсутствующих в нём, и опять же, обновление ссылок. Здесь ситуация немножко сложнее в том смысле, что git push (вернее низкоуровневый git-receive-pack, который запускается на удалённом для приёма данных git-push) по умолчанию модифицирует ссылки непосредственно в .git/refs/heads удалённого репозитория. Поэтому по умолчанию здесь допускаются только такие изменения веток-меток, которые меняют ссылку на коммит, являющийся потомков («сыном», «внуком» и так далее) текущего коммита. Ну, чтобы ничего случайно не потерялось.

git pull - это как Вам уже указали, git fetch с последующим git merge-м.

git reset <sha1> - это запись в текущую ветку-метку нового значения. Если при этом используется --hard, то вдобавок изменяется ещё и текущее дерево и состояние индекса.

Во-от, как-то так. Ложки не существует.

AlexM ★★★★★ ()

Ох, не любит меня Git.

дальше можно не читать

q11q11 ★★★★★ ()

git add .
git commit -a
git push

Кстати, почему он такой многословный? После привычных hg ci && hg push вводить это регулярно задалбывает…

KRoN73 ★★★★★ ()
Ответ на: комментарий от KRoN73

Настрой алиасы в .gitconfig. И дождёшься таки, чтобы руки оторвали тебе за коммиты всего и вся.

Deleted ()
Ответ на: комментарий от KRoN73

Кстати, почему он такой многословный? После привычных hg ci...

Охх...

git config alias.ci '!sh -c "set -e; if test $# -eq 0; then git add -u; else git add "$@"; fi; git commit"'

Соль и перец по вкусу.

Надеюсь, не облажался в кавычках. Если облажался, или если хочется навернуть сильно больше логики по разбору параметров, то можно реализовать честным скриптом в /usr/libexec/git-core. Но, как Вы понимаете, это остаётся в качестве упражнения читателю.

AlexM ★★★★★ ()
Последнее исправление: AlexM (всего исправлений: 2)
Ответ на: комментарий от Deleted

git add -u воспроизводит поведение SVN, более-менее. Ну, ради тех, кому хочется почувствовать себя вернувшимся на 10 лет назад :) Тогда, поди, и гемо радикулит не так часто мучал, и вообще девушки вокруг были моложе.

AlexM ★★★★★ ()
Последнее исправление: AlexM (всего исправлений: 2)
Ответ на: комментарий от Deleted

Настрой алиасы в .gitconfig. И дождёшься таки, чтобы руки оторвали тебе за коммиты всего и вся.

Ну только если я сам себе их оторву, ибо я единственный разработчик.

Xintrea ★★★★★ ()
Ответ на: комментарий от AlexM

Ну в общем, восстановил состояние на нужный мне коммит. Локально восстановил, естественно. Пришлось создать restore-ветку, в ней экспериментировать.

Теперь надо понять, как это состояние залить на сервер, чтобы не напороться по второму разу на те же грабли.

Насколько я понял, проблема в том, что локально и на сервере не совпадают ветки. Мне бы, честно говоря, хватило бы того, чтобы на сервере было тупо зеркало моего локального репозитария.

Поэтому сейчас стоит вопрос: по какому пути пойти? Разбираться, как заливать только ветку, или разбираться, как заливать данные для получения полного зеркала на сервере.

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

git branch -v master shit *recovery

git branch -D master git branch -m master git push -f origin master

anonymous ()
Ответ на: комментарий от Xintrea
git branch -v
 master
 shit
*recovery

git branch -D master
git branch -m master
git push -f origin master
anonymous ()
Ответ на: комментарий от Xintrea

ну, для начала просто попробовать

git push <remote-server> -f master

(<remote-server> - это обычно origin, но вообще при помощи git remote можно вывести весь список и управлять им, если надо)

В зависимости от настроек удалённого сервера эта команда (принудительное переписывание ветки master) может сработать, а может и нет. «Заборонено, см. рис. 1». Если forced push не удался, то можно попробовать удалить ветку master в удалённом репозитории вовсе:

git push <remote-server> :master

(в этом случае мы отправляем пустой референс в master на <remote-server>, что эффективно удаляет его). После этого можно заливать текущий локальный master обычным способом.

Если же и второй способ не сработал («здравствуй, Gerrit!» :) ), то можно попытаться удалить ветку master через web-интерфейс удалённого сервера. Я почему так уверенно говорю про web-интерфейс: «обычные» git-сервера с настолько жестокими настройками по манипулированию ветками если и существуют, то наверняка там есть внешняя, нестандартная система управления правами, которая обычно доступна через web.

AlexM ★★★★★ ()
Ответ на: комментарий от anonymous

Хочу обратить внимания на то, что данная команда

git branch -D master
удалит локальную ветку master. Ничего непоправимого [сразу] от этого не произойдёт, есть git reflog, но хочется напомнить, что такие операции следует проводить на трезвую и холодную голову :)

AlexM ★★★★★ ()
Ответ на: комментарий от AlexM

но вообще при помощи git remote можно вывести весь список и управлять им, если надо

Весь список чего?

Если forced push не удался

Блин, о форсед пуш речи выше по тексту не идет.

в этом случае мы отправляем пустой референс

Что есть референс?

После этого можно заливать текущий локальный master обычным способом

Обычным - это каким?

Xintrea ★★★★★ ()
Ответ на: комментарий от Xintrea

Весь список чего?

список сконфигурированных удалённых репозиториев. В каждом локальном репозитории может отслеживаться состояние нескольких удалённых. Для этого информацию о их местонахождении заносят в .git/config либо при промощи команды git remote, либо текстовым редактором. Команда git remote --help , как и все остальные команды git, показывает релевантную страничку man. Попробуйте её прочесть, чтобы мне не было нужды пересказывать её Вам.

Блин, о форсед пуш речи выше по тексту не идет.

push -f (== push --force) - это как раз forced push.

Что есть референс?

reference. ссылка. См. выше мой рассказ про то, что ветки в git - это метки. Теги, кстати, тоже метки. Просто они не двигаются туда-сюда, и у них есть режим с GPG-подписью и прочими атрибутами, более сложный, чем просто запись в .git/refs/tags.

Обычным - это каким?

git push <remote> master

без ключика --force

Вообще, повторюсь, у каждой git'овой команды есть соответствующая ей страничка man. Добывается при помощи ключика --help . Не стесняйтесь ей пользоваться.

AlexM ★★★★★ ()
Последнее исправление: AlexM (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.