Программирование и IT

git checkout

Исторически `git checkout` делает сразу несколько несвязанных вещей: переводит рабочую копию на другую ветку, создаёт ветку, достаёт отдельный файл из коммита. Из-за этой перегруженности в git 2.23 появились две отдельные команды — `switch` и `restore`. Разберём обе стороны: старый способ и современную замену.

Обновлено
В этой статье

Три работы одной команды#

Команда меняет то, на что смотрит рабочая копия. Дальше всё зависит от того,
что ей передали.

Передали имя ветки — указатель HEAD переезжает на эту ветку, а файлы в папке
проекта перерисовываются под её последний коммит. Передали хеш коммита —
файлы перерисовываются под этот коммит, но указателя ветки там нет, и ты
оказываешься в отсоединённом состоянии. Передали путь к файлу после двух тире —
ветка не меняется вообще, зато этот файл затирается версией из указанного
коммита, и твои несохранённые правки в нём пропадают без предупреждения.

Последний случай — единственное по-настоящему опасное применение. Изменения,
которых нет ни в коммите, ни в индексе, git восстановить не может: их нигде
нет. Поэтому перед откатом файла стоит либо закоммитить, либо спрятать правки
командой из статьи про git stash.

Синтаксис#

git checkout main              # перейти на существующую ветку
git checkout -b feature-auth   # создать ветку от текущего места и перейти
git checkout -b fix origin/main    # создать ветку от чужой вершины
git checkout 9c1f8ab           # посмотреть состояние коммита (HEAD отсоединён)
git checkout -- config.yml     # вернуть файл к версии из HEAD, правки стереть
git checkout 9c1f8ab -- app/db.py  # достать один файл из старого коммита
git checkout -                 # вернуться на предыдущую ветку

С git 2.23 те же задачи решают две узкие команды:

git switch main                # перейти на ветку
git switch -c feature-auth     # создать и перейти
git switch -                   # на предыдущую ветку
git restore config.yml         # вернуть файл к версии из HEAD
git restore --source=9c1f8ab -- app/db.py   # файл из конкретного коммита
git restore --staged report.md # убрать файл из индекса, правки оставить

Разделение полезно именно тем, что switch физически не умеет трогать файлы,
а restore — переключать ветки. Промахнуться и снести работу одной опечаткой
становится труднее.

Пример: переключение и незакоммиченные правки#

$ git switch -c feature-search
Switched to a new branch 'feature-search'
$ git status -sb
## feature-search
 M app/views.py

Правка в app/views.py осталась при тебе: переключение переносит
незакоммиченные изменения на новую ветку, если они не мешают. Мешают — когда
целевая ветка меняет тот же файл. Тогда git откажется:

error: Your local changes to the following files would be overwritten by checkout:
        app/views.py
Please commit your changes or stash them before you switch branches.
Aborting.

Это не ошибка, а защита: git не стал молча выбрасывать твою работу. Варианта
три — закоммитить, спрятать через git stash push, либо переключиться вместе
с правками: git switch -m other-branch попробует их перенести слиянием.

Пример: достать файл из прошлого#

Нужен старый вариант конфигурации, но история при этом остаётся нетронутой:

$ git log --oneline -3 -- deploy/nginx.conf
5d0aa71 новый порт
c17b3e2 отключили gzip
8a94f0c первая рабочая конфигурация
$ git checkout 8a94f0c -- deploy/nginx.conf
$ git status -s
M  deploy/nginx.conf

Файл из коммита 8a94f0c лежит теперь и в рабочей копии, и в индексе (буква
M в первой колонке). Ветка не переключалась, остальные файлы не тронуты.
Дальше это обычная правка: закоммитить или отменить.

Отсоединённый HEAD: что это и как выйти#

Переход на хеш коммита даёт предупреждение на пятнадцать строк, суть которого
в первой:

Note: switching to '9c1f8ab'.
You are in 'detached HEAD' state.

Ты смотришь на конкретный коммит, и никакая ветка сюда не указывает. Смотреть
и запускать код так можно свободно. Опасность одна: коммиты, сделанные в этом
состоянии, ни к чему не привязаны. Стоит переключиться на ветку — и добраться
до них можно будет только через git reflog.

Выходов два. Если ничего не коммитил — просто вернись: git switch -.
Если коммитил и результат нужен — закрепи его веткой прямо на месте:

$ git switch -c experiment
Switched to a new branch 'experiment'

Теперь коммиты принадлежат ветке experiment и никуда не денутся.

Частые ошибки#

Забыли два тире. git checkout config при наличии ветки config
переключит ветку, а не вернёт файл. Два тире говорят git: дальше пути, не
ссылки. Команда git restore от этой двусмысленности избавлена.

Ждали, что откат файла можно отменить. Нельзя. git checkout -- файл и
git restore файл уничтожают несохранённые правки безвозвратно — в отличие от
операций над коммитами, которые почти всегда возвращает журнал ссылок. Разница
между «потерять коммит» и «потерять несохранённое» подробно разобрана в статье
про git reset.

Ветка не находится. error: pathspec 'feature-x' did not match any file(s) known to git обычно значит, что ветка есть только на сервере. Сделай
git fetch origin, после чего git switch feature-x создаст локальную ветку,
следящую за серверной, автоматически.

План по этапам

  1. Посмотреть, где ты сейчасgit status -sb: текущая ветка и незакоммиченные файлы.
  2. Решить судьбу правокЗакоммитить, спрятать в stash или убедиться, что они не мешают переключению.
  3. Переключитьсяgit switch <ветка> или git checkout <ветка>; новой ветке — флаг -c или -b.
  4. Проверить, что переключилисьgit branch --show-current должен вывести ожидаемое имя.
  5. Восстановить файл, если нужноgit restore <файл> или git checkout <коммит> -- <файл>; помнить, что несохранённое в этом файле теряется.

Начать изучать эту тему у себя

План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.

Начать план

Проверь себя

1.Какая команда создаст ветку hotfix и сразу перейдёт на неё?

2.Ты правил файл notes.md и выполнили git restore notes.md. Что стало с правками?

3.Ты на коммите 9c1f8ab в состоянии detached HEAD и сделал два коммита. Как их сохранить?

Источники

Было полезно?

Ещё темы

Программирование и IT git branch Ветка в git — это подвижный указатель на коммит, а не копия файлов. Поэтому завести её ничего не стоит, а команда git branch отвечает за список веток, их создание, переименование и удаление. Программирование и IT git rebase Команда переносит коммиты твоей ветки так, будто ты начал работу не от старого состояния, а от текущего. История становится линейной, но коммиты при этом создаются заново — с новыми хешами. Отсюда главное ограничение: переносить можно только то, чем не пользуется никто другой. Программирование и IT Как создать репозиторий с нуля Репозиторий — это папка проекта плюс скрытая папка `.git`, в которой хранится вся история. Создаётся одной командой, но между ней и первым осмысленным коммитом есть несколько шагов, которые потом трудно переделать: имя ветки, список игнорируемых файлов, привязка к серверу. Программирование и IT git push Пока ты не выполнил git push, твои коммиты существуют только на твоём диске. Команда отправляет их в удалённый репозиторий и сдвигает там указатель ветки — поэтому она же чаще всего и отказывает, если кто-то успел отправить своё раньше. Программирование и IT git merge Слияние переносит работу из одной ветки в другую. Команда git merge берёт указанную ветку и соединяет её историю с текущей — иногда просто сдвигая указатель, иногда создавая отдельный коммит, а иногда останавливаясь и прося помощи. Программирование и IT git clone Команда git clone забирает репозиторий с сервера целиком — со всей историей, ветками и тегами — и кладёт копию в новую папку на твоём компьютере. С неё начинается работа с любым уже существующим проектом, своим или чужим.

Ещё сценарии