Сорри за <pre>, но переформатировать мне лень.
Более строго следует говорить не о "редактировании" или "изменении" истории, а о cоздании "альтернативной" истории. Если специально ничего не предпринимать, в репозитории git остаются все объекты "старой" истории, соответствующие предыдущим коммитам и версиям файлов. На эти объекты не будут "ссылаться" ветки, но если Вы вспомните их SHA1-ключи, либо как-то специально позаботитесь их "пометить" (тэгом, или другой веткой), то старая история будет c точки зрения git "ничем не хуже" новой. Почти во всех командах git можно ссылаться на коммиты любым способом: - с помощью SHA1-ключа, - с помощью имени ветки (если это последний коммит на ветке), - с помощью тэга (если вы его предусмотрительно поставили git tag), - c помощью специальных имён, например HEAD - последний коммит на данной ветке, HEAD^ - предпоследний (точнее, первый предок последнего коммита) и т.п. Подробности см. git-rev-parse --help Ниже в командах, которые допускают любую идентификацию коммита, я буду указывать в качестве аргумента <id>, или <id-...>. Если допускается только имя ветки, указывается <ветка>. Для начинающих я рекомендую приступая к редактированию истории пометить все ключевые точки тэгами. Их хорошо видно в gitk. Только не забудьте их потом удалить git tag -d В понятие истории git я буду включать не только совокупность коммитов git-а, но и содержание рабочего каталога (да простят меня потомки). Типовые задачи редактирования истории: 1. Отказаться от всех изменений в рабочем каталоге (аналог revert в svn). Кошерный способ: git checkout -f Отказаться от части изменений можно с помощью: git checkout <path> НО: git checkout . не удалит, например, вновь добавленных файлов. Более жёсткий способ удалить _все_ изменения: git reset --hard HEAD 2. "Сохранить" изменения (состояние) рабочего каталога. git stash При этом рабочий каталог "очищается" до HEAD, а сохранённые изменения можно в последствии "применить" к текущему, либо к любому другому состоянию рабочего каталога с помощью git stash apply В частности, это позволяет "переносить" изменения между ветками (хотя, лучше их оформлять как коммиты, и оперировать потом уже с ними). 3. Отредактировать/дополнить последний коммит: git commit --amend Можно применять даже если Вам просто понадобилось переписать commit-log (например, Вы его "недописали" или он оказался не в той кодировке). Фактически при выполнении этой операции будет создан _другой_ commit object, и HEAD ветки будет связан с ним. (Старый объект в репозитории git тоже сохранится). 4. "Отказаться" от нескольких последних коммитов в истории (в частности, от последнего) Создать новую ветку new в нужной нам точке истории и переставить на неё существующую: git checkout <id> -b new git branch -M <нужная нам ветка> Например, отказаться от последнего коммита на ветке master (если мы на нём находимся), можно так: git checkout HEAD^ -b new_master git branch -M master После первой команды мы находимся "на один коммит назад" и создали там новую ветку с именем new_master (текущей веткой является new_master). После второй команды мы "переименовали" new_master в master, -M позволяет проигнорировать, что master уже есть. Тоже самое можно сделать одной командой: git reset --hard <id> Но это менее безопасно (см. ниже). 5. "Переставить" метки веток. git reset [--ключ] <id> Позволяет "передвинуть" текущий HEAD (и метку ветки) на заданный коммит. Есть три варианта, задаваемых ключами: --hard - "выкидывает" всё текущее состояние рабочий копии, вы оказываетесь на коммите <id>, как будто после него ничего не было; Т.е. это просто "перестановка ветки". --soft - "сохраняет" изменения в рабочей копии (и в "индексе" git) и добавляет к ним изменения из "истории" от <id> до точки, из которой мы переходим. Более подробно см. п. "Слияние нескольких коммитов в один". --mixed - (по умолчанию) - ведёт себя как --soft, но не изменяет состояние "индекса" git (оно будет соответствовать коммиту <id>, на который мы перешли) - новые и изменённые файлы не считаются "добавленными" в индекс, т.е. в отличии от --soft для них требуется явно делать git add, git rm, .etc Поскольку git reset (особенно --hard), позволяет "потерять" последнее положение ветки (т.е. оставить HEAD "непомеченным"), следует использовать эту команду с осторожностью. 6. Слияние нескольких коммитов в один. Если это "последние" коммиты в истории этой ветки: git reset --soft <id> git commit -a -s [--amend] Первая команда позволяет "отскочить" HEAD на несколько коммитов назад, при этом сохранив все "изменения" этих коммитов в рабочем каталоге. Например, git reset --soft HEAD^^ позволит "объединить" изменения последнего и предпоследнего коммитов. Если мы хотим "добавить" к этим изменениям, изменения из коммитов с другой ветки, нам поможет git cherry-pick --no-commit <id> Эта команда "добавляет" изменения коммита в рабочий каталог и в индекс, но не выполняет операцию commit. 7. Удаление нескольких коммитов "внутри истории". git-rebase magic Например, у Вас есть история ветки: ...-(N-5)-(N-4)-(N-3)-(N-2)-(N-1)-(N) - ветка и вам захотелось удалить коммиты (N-4)-(N-2) включительно. Это можно сделать с помощью команды git-rebase: git-rebase --onto <ветка>~5 <ветка>~2 <ветка> Например, git-rebase --onto master~5 master~2 master Нотация <id>~<n> означает n-ый коммит назад, т.е. в данном случае: - master - (N) - master~2 - (N-2) - master~5 - (N-5) Смысл операции git-rebase --onto <id-newbase> <id-upstream> <id-head>: 1) Переключиться на коммит <id-head> (== git checkout <ветка>, если <id-head> - это HEAD ветки) 2) Начать новую ветку от точки <id-newbase> 3) "Поместить" на новую ветку коммиты от <id-upstream> до <id-head>, не включая <id-upstream> 4) Если <id-head> - это HEAD ветки, переставить <ветку> на то, что получилось В данном случае: От коммита (N-5) мы начинаем "применять" коммиты (N-1) и (N), и переставляем метку ветки, в результате чего получается "новая история": (N-1)'-(N)' - ветка / ...-(N-5)-(N-4)-(N-3)-(N-2)-(N-1)-(N) 8. Объединение коммита с "внутренним" коммитом в истории. Например, в коммите <id-src> Вы исправили ошибку в "старом исправлении" <id-dst>, которое было несколько коммитов назад. Последовательность действий: 1) Создать новую ветку new_branch от коммита <id-dst>, который надо поменять (дополнить). git checkout <id-dst> -b new_branch 2) Сделать cherry-pick коммита <id-src>, который вы хотите "приплюсовать" к внутреннему. git cherry-pick --no-commit <id-src> 3) "Дополнить" последний коммит изменениями из рабочего каталога. git commit --amend 4) Добавить в новую историю последовательность "правильных" коммитов: git rebase --onto HEAD <id-первый коммит="">^ <id-последний коммит=""> 5) Переставить ветку на новый HEAD git branch -f <имя ветки> Пояснения требуют два последних действия: git rebase в данном случае добавляет нужную последовательность коммитов "в голову" новой ветки, но если <id-последний коммит=""> - это не HEAD старой ветки, то после git rebase новый HEAD не будет соответствовать ни какой ветке ! (так уж работает git rebase) Для этого требуется последняя операция, она явно переставляет ветку на HEAD. Если наше исправление было бы не закоммичено, можно было воспользоваться git stash и git stash apply вместо git cherry-pick. 9. Редактирование "внутреннего" коммита. Действия аналогичны п.8, но проще. Пусть мы находимся на ветке <имя ветки>. 1) Извлечь коммит <id-dst>, подлежащий редактированию; ветку new_branch создавать при этом не обязательно, но желательно: git checkout <id-dst> [-b new_branch] 2) Исправить код, "дополнить" последний коммит изменениями из рабочего каталога. git commit -a --amend 3) Добавить в новую историю последовательность "правильных" коммитов: git rebase --onto HEAD <id-dst> <имя ветки> 4) Удалить ветку new_branch, если она была создана на шаге 1) git branch -D new_branch Специально переставлять ветку <имя ветки> в данном случае не требуется, т.к. в команде git rebase в п. 3) в качестве последнего аргумента было имя ветки, а не просто SHA1-id. В такой ситуации эта команда "автоматически" переставит ref ветки. 10. rebase ветки с помощью git rebase. git rebase <upstream-branch> Эта операция подробно рассмотрена в разъяснениях Никиты по идеологии и сценариям использования git. Не следует относится к git rebase "формально": например, если Вы считаете, что некоторые коммиты с ветки разумнее было бы переместить на master, можно "продублировать" их на master с помощью git cherry-pick, после чего сделать git rebase. После этого, с веки эти коммиты волшебным образом исчезнут. 11. "Откат" отдельного коммита. Строго говоря, это не редактирование истории: просто автоматически добавляется коммит (либо, изменение в рабочей копии), "отменяющее" заданный коммит. git revert [--no-commit] <id> Эту возможность следует использовать если Вы не хотите "честно" редактировать историю. Например, коммит надо откатить только на одной из ветвей, либо этот коммит был "очень давно", и не хочется перестраивать из-за него всю историю целиком.
No comments:
Post a Comment