GIT для продвинутых – проще некуда

Мы продолжаем двигаться от простого к сложному и сегодня будет более продвинутый урок и скорей всего последний в моем цикле. Но это не точно. 

Отменяем изменения

Допустим, вы внесли изменения в какой-то файл и хотите отменить их. В программах с графическим интерфейсом скорей всего будет возможность правой кнопкой кликнуть по имени файла и выбрать Undo Changes, а в git это можно сделать, выполнив команду:

git checkout имя_файла

Мы забираем из репозитория не ветку, а конкретный файл и все локальные изменения теряются.

Измените файл index.html, который мы создали в прошлый раз и на котором мы тестировали коммиты. После выполнения команды git status файл должен быть помечен, как измененный. Теперь отмените изменения, выполнив:

git checkout index.html

Все локальные изменения будут утеряны.

Работаем с историей

Для просмотра истории изменений можно использовать команду gitk, с которой мы уже знакомы, а можно использовать git log. Если этой команде указать имя файла, то вы увидите историю изменений только его.

git log index.html

Сейчас в index.html у меня несколько изменений. А что, если к вам приходит тестер и говорит, что какие-то изменения сломали код? Вполне резонным было бы взять файл до изменений, откомпилировать проект и посмотреть, как он работал до.

Выполним git log index.html

После слова commit мы видим уже знакомые нам уникальные SHA коды коммитов. Мы можем просмотреть конкретный из них выполнив команду gitk и указав нужный SHA. Например, посмотрим на второй снизу, когда мы изменили файл и указали update index:

gitk 646d34e6cb8f639cb933a38c18f5ec4ad51b3793

Мы можем взять и откатиться на состояние файла index.html этого коммита:

git checkout 646d34e6cb8f639cb933a38c18f5ec4ad51b3793 index.html

Мы остались на текущей ветке мастера, в текущем состоянии, но просто поверх текущего состояния наложили файл index.html, который был во время коммита 646d34e6cb8... .

Если посмотреть git status, то можно увидеть, что файл index.html изменен. Git как бы наложил изменения из старой ветки поверх текущей, но не закомитил их. Чтобы отказаться от этой операции достаточно просто выполнить:

git checkout index.html

В небольших проектах или в случае с CSS/HTML получение одного единственного файла из истории может быть достаточным. В случае с большим проектом, и файлом кода, этого может не хватить. Выхватив всего один CS или JAVA файл может сломать код, и вы его не скомпилируете. Но вы может создать новую ветку от определенной точки в истории и как-бы перейти в это состояние для всех файлов.

До сих пор при создании новой ветки я вам говорил, что всегда нужно указывать origin/master, чтобы всегда ветка создавалась от мастера. Если нужна другая ветка, то нужно указывать именно ее. Но смысл в том, что всегда должна быть ветка, от которой вы хотите создать новую.

Но базой для новой ветки может быть не только другая ветка в текущем своем состоянии, но и любая точка в истории. То есть когда QA сказали, что вы что-то сломали, вы можете создать новую ветку от определенного коммита, откомпилировать проект и убедиться, прав тестер или нет. Просто при создании коммита указываем SHA в качестве основы:

git checkout -b wip/updatecommit 646d34e6cb8f639cb933a38c18f5ec4ad51b3793 

Схематично этот процесс можно нарисовать так. Мы находимся сейчас в текущем состоянии master, но при создании новой ветки наше состояние было взято с точки в истории и будет соответствовать этой точке.

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

У меня на работе были случаи, когда я работаю над каким-то изменением, коммичу его, отправляю на сервер и тут понимаю, что я закоммитил не в ту ветку. Давайте сэмулируем это:

git checkout -b wip/testreset origin/master
vi index.html
#здесь вносим изменение в index.html
git add index.html
git commit -m 'сохраняем изменение не туда'
git push

Возможно в этот момент я создаю pull request я понимаю, что изменения не нужно было делать в этой ветке wip/testreset, а где-то в другом месте. Как исправить ситуацию?

Через gitk или через git log смотрим на SHA коммита, который был сделан прямо перед последним. У меня log показал:

Последний sha это наш некорректный коммит, а нам нужно оказаться на коммите, которые был до этого и это a168c47ac96dcfd9d96c6e69b2dc7e28404479b8. Чтобы откатиться на это состояние выполняем команду git reset --soft и указываем этот SHA:

git reset --soft a168c47ac96dcfd9d96c6e69b2dc7e28404479b8

В результате текущая точка откатиться в истории на состояние до коммита. А если выполнить git status, то некорректный коммит никуда не денется, точнее изменения никуда не денутся. Все изменения останутся локально готовые к коммиту.

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

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

! [rejected]        wip/testreset -> wip/testreset (non-fast-forward)
error: failed to push some refs to 'https://github.com/mflenov/demo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Чтобы перезаписать содержимое ветки на сервере, можно отправить текущую ветку с ключом -f

git push -f origin wip/testreset

Вот теперь и на сервере мы откатили изменения.

--soft сохраняет изменения локально. Если вам эти изменения не нужны, то можно указать --hard.

git reset --hard a168c47ac96dcfd9d96c6e69b2dc7e28404479b8

В этот раз мы откатились совсем, и они потерялись локально. Все, что было после коммита a168c47ac96dcfd9d96c6e69b2dc7e28404479b8 ушло безвозвратно.

А что если вы закоммитили все верно и все туда, но в коммит попали какие-то данные, которые не должны были? Если я просто закоммитил не то, то я просто беру и создаю новые коммит с коррекцией. А если нужно именно подправить последний коммит? Можно использовать мягкий reset, и закоммитить потом новые изменения, а можно подправить последний коммит без сброса.

git checkout -b wip/testamend origin/master
vi index.html
#здесь вносим изменение в index.html
git add index.html
git commit -m 'сохраняем не те изменения'
git push

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

Убираем слово personal, добавляем файл в коммит

git add index.html

и теперь выполняем уже знакомый нам git commit, но указываем дополнительный ключ --amend:

git commit --amend -m 'корректируем изменения'

Теперь если посмотреть на историю изменений gitk, то вы не увидите строка предыдущего коммита исчезла и вместо нее появилась новая с новым SHA:

В принципе, это то же самое, что если бы мы выполнили reset и создали новый коммит с нашим изменением, результат в истории был бы абсолютно такой же.

Отправка текущей ветки

Во время своих видео я несколько раз опечатывался и для отправки изменений на сервер выполнял команду git push и она завершалась ошибкой, потому что полная команда

git push origin имя_ветки

Так почему я ошибался несколько раз на такой простой и популярной команде? Дело в том, что перед записью видео я удалил глобальный файл настроек где была вот такая магическая строка в разделе [push]:

[push]
   default = current

Посмотрим на полное содержимое моего файла gitconfig: cat ~/.gitconfig

[user]
	email = mikhail@flenov
	name = mflenov

[merge]
	tool = vscode
[mergetool "vscode"]
	cmd = code --wait $MERGED
[diff]
	tool = vscode
[difftool "vscode"]
	cmd = code --wait --diff $LOCAL $REMOTE

[push]
	default = current

Последние две строки можно добавить вручную или можно выполнить следующую команду в терминале, что в принципе то же самое:

git config --global push.default current

Теперь для отправки текущей ветки на сервер достаточно просто выполнять команду git push, без указания origin и ветки и в этом случае на сервер будет загружена текущая ветка. В 99% случаев мы отправляем на сервер именно текущую ветку, поэтому такое действие по умолчанию очень даже удобно.



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

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

Добавить Комментарий

О блоге

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

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне