Коммиты
Коммит в git репозитории хранит снимок всех файлов в репозитории. Почти как огромная копия, только эффективнее.
Git пытается быть лёгким и быстрым, так что он не просто слепо копирует весь проект каждый раз, а ужимает коммит в набор изменений или «дельту» между текущей версией и предыдущей. Это позволяет занимать меньше места.
Также Git хранит всю историю о том, когда какой коммит был сделан и кем
Это очень важно, можно посмотреть из-за кого упал прод и навалять ему. Файлы в репозитории могут находиться в 3 различных “областях”
Файлы в репозитории могут находиться в 3 различных “областях”.
- HEAD
- Индекс
- Рабочий каталог
Наш проект сейчас пуст. Давайте создадим первый файл:
На данном этапе только область “Рабочий каталог” содержит данные.
Рабочий проект это ваша папка с файлами, в данном случае это . Две другие области сохраняют свое содержимое внутри каталога в понятном и удобном для git формате, но не понятном для человека. Рабочий Каталог распаковывает их в файлы, что упрощает для нас их редактирование.
Считайте Рабочий Каталог песочницей, где вы можете опробовать изменения перед коммитом. На данном этапе вы можете в любой момент очистить все изменения и вернуться к последнему коммиту.
Сделаем снимок текущего состояния проекта, то есть сделаем коммит. Для этого необходимо сначала добавить содержимое “Рабочего Каталога” в Индекс. Индекс — это черновик коммита. Только файлы из индекса попадают в коммит.
Без добавления файла в Индекс у нас не получится создать коммит, проверьте это сами:
Для добавления в Индекс используется следующая команда:
Когда у вас много файлов, вы можете добавить их все разом .
Теперь сделаем наш первый коммит, выполнив команду . Тем самым мы сохраним содержимое Индекса как неизменяемый снимок в области . Хорошей практикой считается коротко описать, что было изменено, для этого используется флаг :
Все, коммит готов. И файл попал в область . будет родителем следующего созданного коммита. Как правило, самое простое считать снимком вашего последнего коммита. Возможно пока не совсем понятно что такое , но о нем мы еще поговорим.
Если сейчас выполнить , то мы не увидим никаких изменений, так как все три области одинаковые.
Теперь мы хотим внести изменения в файл и закоммитить его. Мы пройдём через всё ту же процедуру; сначала мы отредактируем файл в нашем рабочем каталоге. Давайте называть эту версию файла v2 и обозначать красным цветом.
Теперь посмотрим, какие изменения произошли в git:
Нам подсказывают, что изменения не попадут в коммит, пока мы не сделаем . Выполним эту комманду чуть позже, пока добавим новый файл:
Еще раз проверяем статус репозитория:
Новый файл появился в списке не отслеживаемых, то есть в Рабочем Каталоге. Давайте добавим в отслеживание этот файл, а так же измененный ранее.
Теперь у нас есть изменения, которые будут включены в коммит. Значит можно сделать второй коммит:
Я уже упоминал, что у коммитов есть родитель, который указывает на предыдущий коммит. Из таких цепочек складывается “ветка”. О ветках мы поговорим в следующей статье. Пока достаточно знать, что по умолчанию у нас уже есть ветка .
Для удобства восприятия визуализируем наши коммиты. Кружочки это коммиты, а стрелочки между ними указывают на родителей.
Прочие команды и необходимые возможности
Хэш — уникальная идентификация объектов
В git для идентификации любых объектов используется уникальный (то есть с
огромной вероятностью уникальный) хэш из 40 символов, который определяется
хэшируюшей функцией на основе содержимого объекта. Объекты — это все: коммиты,
файлы, тэги, деревья. Поскольку хэш уникален для содержимого, например, файла,
то и сравнивать такие файлы очень легко — достаточно просто сравнить две строки
в сорок символов.
Больше всего нас интересует тот факт, что хэши идентифицируют коммиты. В этом
смысле хэш — продвинутый аналог ревизий Subversion. Несколько примеров
использования хэшей в качестве способа адресации.
Ищет разницу текущего состояния проекта и коммита за номером… сами видите,
каким:
То же самое, но оставляем только шесть первых символов. Git поймет, о каком
коммите идет речь, если не существует другого коммита с таким началом хэша:
Иногда хватает и четырех символов:
Читает лог с коммита по коммит:
Разумеется, человеку пользоваться хэшами не так удобно, как машине, именно
поэтому были введены другие объекты — тэги.
git tag — тэги как способ пометить уникальный коммит
Тэг (tag) — это объект, связанный с коммитом; хранящий ссылку на сам коммит,
имя автора, собственное имя и некоторый комментарий. Кроме того, разработчик
может оставлять на таких тегах собственную цифровую подпись.
Кроме этого в git представленные так называемые «легковесные тэги» (lightweight
tags), состоящие только из имени и ссылки на коммит. Такие тэги, как правило,
используются для упрощения навигации по дереву истории; создать их очень легко.
Создаёт «легковесный» тэг, связанный с последним коммитом; если тэг уже есть,
то еще один создан не будет:
Помечает определенный коммит:
Удаляет тег:
Перечисляет тэги:
Создаёт тэг для последнего коммита, заменяет существующий, если таковой уже был:
После создания тэга его имя можно использовать вместо хэша в любых командах
вроде git diff, git log и так далее:
Обычные тэги имеет смысл использовать для приложения к коммиту какой-либо
информации, вроде номера версии и комментария к нему. Иными словами, если в
комментарии к коммиту пишешь «исправил такой-то баг», то в комментарии к тэгу по
имени «v1.0» будет что-то вроде «стабильная версия, готовая к использованию».
Создаёт обычный тэг для последнего коммита; будет вызван текстовый редактор для
составления комментария:
Создаёт обычный тэг, сразу указав в качестве аргумента комментарий:
Команды перечисления, удаления, перезаписи для обычных тэгов не отличаются от
команд для «легковесных» тэгов.
Относительная адресация
Вместо ревизий и тэгов в качестве имени коммита можно опираться на еще один
механизм — относительную адресацию. Например, можно обратиться прямо к предку
последнего коммита ветки master:
Если после «птички» поставить цифру, то можно адресоваться по нескольким предкам
коммитов слияния:
Ищет изменения по сравнению со вторым предком последнего коммита в master; HEAD
здесь — указатель на последний коммит активной ветки.
Аналогично, тильдой можно просто указывать, насколько глубоко в историю ветки
нужно погрузиться.
Что привнес «дедушка» нынешнего коммита:
То же самое:
Обозначения можно объединять, чтобы добраться до нужного коммита:
Файл .gitignore — объясняем git, какие файлы следует игнорировать
Иногда по директориям проекта встречаются файлы, которые не хочется постоянно
видеть в сводке git status. Например, вспомогательные файлы текстовых редакторов,
временные файлы и прочий мусор.
Заставить git status игнорировать определенные файлы можно, создав в корне или
глубже по дереву (если ограничения должны быть только в определенных директория)
файл .gitignore. В этих файлах можно описывать шаблоны игнорируемых файлов
определенного формата.
Пример содержимого такого файла:
Существуют и другие способы указания игнорируемых файлов, о которых можно узнать
из справки git help gitignore.
Серверные команды репозитория
Команда создания вспомогательных файлов для dumb-сервера в $GIT_DIR/info и
$GIT_OBJECT_DIRECTORY/info каталогах, чтобы помочь клиентам узнать, какие ссылки
и пакеты есть на сервере:
Проверяет сколько объектов будет потеряно и объём освобождаемого места при
перепаковке репозитория:
Переупаковывает локальный репозиторий:
Как избежать такого в дальнейшем?
В GitHub и GitLab есть функциональность под названием «защищённые ветки» (protected branches). Отметьте , , или какие ещё ветки важны для вас как защищённые и система не даст вам выстрелить себе в ногу. Если force push всё же понадобится, то защиту всегда можно на время снять. См
документацию: https://help.github.com/articles/defining-the-mergeability-of-pull-requests/
Создайте алиасы для команд, которые должны делать , чтобы обезопасить себя от ошибок по неосторожности:
Прекратите уже экспериментировать прямо в основной ветке! Команда — ваш друг.
Pro Tip: У команды также ещё очень хорошо сочетаются друг с другом ключи и , особенно, если вы отвлеклись от проекта на месяцок-другой. Попробуйте, если, конечно, вам всё ещё мало приключений…
Подмодули
Клонирование репозитория с подмодулями
При клонировании репозитория вам необходимо инициализировать и обновить подмодули:
Запуск данной команды эквивалентен запуску команды:
после обычного клонирования репозитория
Обновление подмодулей
Подмодуль ссылается на конкретную коммит в другом репозитории. Чтобы получить
точное состояние всех подмодулей необходимо запустить:
Для получения состояния последнего коммита всех подмодулей необходимо выполнить
следующую команду:
или использовать аргументы по умолчанию команды git pull:
Эта команда просто обновляет локальную рабочую копию. При запуске команды git status
каталоги подмодулей будут показаны изменёнными. Чтобы обновить репозиторий необходимо
зафиксировать изменения:
Для получения состояние последнего коммита конкретного подмодуля необходимо использовать команду:
Добавление подмодулей
В текущий проект можно включить другой репозиторий Git в качестве папки, отслеживаемый Git:
После этого необходимо добавить и зафиксировать новый файл .gitmodules. В нём описано
какие подмодули следует клонировать при запуске команды git submodule update
Обновления журнала
Если параметр конфигурации «core.logAllRefUpdates» имеет значение «истина» и ссылка находится под «refs / heads /», «refs / remotes /», «refs / notes /» или псевдореф, например HEAD или ORIG_HEAD; или существует файл «$ GIT_DIR / logs / <ref>», тогда добавит строку в файл журнала «$ GIT_DIR / logs / <ref>» (разыменование всех символических ссылок перед созданием имени журнала), описывающую изменение значения реф. Строки журнала имеют следующий формат:
oldsha1 SP newsha1 SP committer LF
Где oldsha1 — это 40-символьное шестнадцатеричное значение, ранее сохраненное в <ref>, «newsha1» — 40-символьное шестнадцатеричное значение <newvalue>, а «committer» — имя коммиттера, адрес электронной почты и дата в стандартном формате идентификатора коммиттера Git. ,
Опционально с -м:
oldsha1 SP newsha1 SP committer TAB message LF
Где все поля,как описано выше,и «сообщение»-это значение,передаваемое в опцию -m.
Обновление не будет выполнено (без изменения <ref>), если текущий пользователь не может создать новый файл журнала, добавить его в существующий файл журнала или не имеет доступной информации о коммиттере.
2012–2021 Скотт Чакон и другиеЛицензия MIT.https://git-scm.com/docs/git-update-ref
Отменить опубликованные коммиты с новыми коммитами
С другой стороны, если вы опубликовали работу, вы, вероятно, не захотите сбрасывать ветку, поскольку это фактически переписывает историю. В этом случае вы действительно можете отменить коммиты. В Git revert имеет очень конкретное значение: создать фиксацию с обратным патчем, чтобы отменить ее. Таким образом, вы не переписываете историю.
справочная страница фактически охватывает многое из этого в своем описании. Еще одна полезная ссылка — .
Если вы решите, что в конце концов не хотите возвращаться, вы можете отменить возврат (как описано здесь) или вернуться к предыдущему состоянию (см. Предыдущий раздел).
Вы также можете найти этот ответ полезным в этом случае: Как переместить HEAD обратно в предыдущее место ? (Отдельная голова) и отменить фиксацию
Подробнее о шаблонах игнорирования
Шаблон | Примеры соответствия | Пояснение* |
---|---|---|
**/logs | logs/debug.log logs/monday/foo.bar build/logs/debug.log | Добавьте в начало шаблона две звездочки, чтобы сопоставлять каталоги в любом месте репозитория. |
**/logs/debug.log | logs/debug.log build/logs/debug.log но не logs/build/debug.log | Две звездочки можно также использовать для сопоставления файлов на основе их имени и имени родительского каталога. |
*.log | debug.log foo.log .log logs/debug.log | Одна звездочка — это подстановочный знак, который может соответствовать как нескольким символам, так и ни одному. |
*.log !important.log | debug.log trace.log но не important.log logs/important.log | Добавление восклицательного знака в начало шаблона отменяет действие шаблона. Если файл соответствует некоему шаблону, но при этом также соответствует отменяющему шаблону, указанному после, такой файл не будет игнорироваться. |
.log !important/.log trace.* | debug.log important/trace.log но не important/debug.log | Шаблоны, указанные после отменяющего шаблона, снова будут помечать файлы как игнорируемые, даже если ранее игнорирование этих файлов было отменено. |
/debug.log | debug.log но не logs/debug.log | Косая черта перед именем файла соответствует файлу в корневом каталоге репозитория. |
debug.log | debug.log logs/debug.log | По умолчанию шаблоны соответствуют файлам, находящимся в любом каталоге |
debug?.log | debug0.log debugg.log но не debug10.log | Знак вопроса соответствует строго одному символу. |
debug.log | debug0.log debug1.log но не debug10.log | Квадратные скобки можно также использовать для указания соответствия одному символу из заданного диапазона. |
debug.log | debug0.log debug1.log но не debug2.log debug01.log | Квадратные скобки соответствуют одному символу из указанного набора. |
debug.log | debug2.log но не debug0.log debug1.log debug01.log | Восклицательный знак можно использовать для указания соответствия любому символу, кроме символов из указанного набора. |
debug.log | debuga.log debugb.log но не debug1.log | Диапазоны могут быть цифровыми или буквенными. |
logs | logs logs/debug.log logs/latest/foo.bar build/logs build/logs/debug.log | Без косой черты в конце этот шаблон будет соответствовать и файлам, и содержимому каталогов с таким именем. В примере соответствия слева игнорируются и каталоги, и файлы с именем logs |
logs/ | logs/debug.log logs/latest/foo.bar build/logs/foo.bar build/logs/latest/debug.log | Косая черта в конце шаблона означает каталог. Все содержимое любого каталога репозитория, соответствующего этому имени (включая все его файлы и подкаталоги), будет игнорироваться |
logs/ !logs/important.log | logs/debug.log logs/important.log | Минуточку! Разве файл logs/important.log из примера слева не должен быть исключен нз списка игнорируемых? Нет! Из-за странностей Git, связанных с производительностью, вы не можете отменить игнорирование файла, которое задано шаблоном соответствия каталогу |
logs/**/debug.log | logs/debug.log logs/monday/debug.log logs/monday/pm/debug.log | Две звездочки соответствуют множеству каталогов или ни одному. |
logs/*day/debug.log | logs/monday/debug.log logs/tuesday/debug.log but not logs/latest/debug.log | Подстановочные символы можно использовать и в именах каталогов. |
logs/debug.log | logs/debug.log но не debug.log build/logs/debug.log | Шаблоны, указывающие на файл в определенном каталоге, задаются относительно корневого каталога репозитория. (При желании можно добавить в начало косую черту, но она ни на что особо не повлияет.) |
Изменение самого последнего коммита
Команда git commit –amend позволяет вам изменить самое последнее сообщение о коммите.
Не изменять коммит
Чтобы изменить сообщение самого последнего коммита, который не был передан в удаленный репозиторий, передайте его снова, используя флаг –amend.
- Перейдите в каталог хранилища в вашем терминале.
- Выполните следующую команду, чтобы поправить (изменить) сообщение о последнем коммите:
git commit --amend -m "New commit message."
Команда выполняет перезапись самого последнего коммита новым.
Опция -m позволяет записать новое сообщение в командной строке без открытия редактора сессии.
Перед изменением сообщения о коммите вы также можете добавить другие ранее забытые изменения:
git add .git commit --amend -m "New commit message."
Измененный коммит
Измененный коммит – это новый объект с другим SHA-1. Предыдущий коммит больше не будет существовать в текущей ветке.
Как правило, вам следует избегать внесения изменений в коммит, который уже выдвинут, поскольку это может вызвать проблемы у людей, которые основывают свою работу на этом коммите. Хорошей идеей будет проконсультироваться со своими коллегами-разработчиками перед изменением принудительного коммита.
Если вы изменили сообщение о самом последнем введенном коммите, вам придется принудительно нажать его.
- Перейдите в хранилище.
- Исправьте сообщение о последнем введенном коммите:
git commit --amend -m "New commit message."
- Принудительно нажмите, чтобы обновить историю удаленного хранилища:
git push --force branch-name
Сложный вариант: починить за кем-то
Иногда бывает, что сделали либо не вы, либо кто-то принял пачку Pull Request’ов в то время, пока вы веселились со своими экспериментами.
Тяжесть ситуации в том, что вы не можете просто сделать , поскольку у вас локально нет коммитов, которые нужно восстановить (и у вас не получиться скачать их с помощью ).
Стоп! Без паники! Зайдите в чат, покайтесь, скажите, чтобы никто ничего не делал — вам понадобится время, чтобы всё вернуть.
Здесь нам поможет тот факт, что GitHub не удаляет коммиты, которые больше не принадлежат ни к какой ветке. Но, что усложняет задачу, не даёт их и стянуть с командной строки.
Если force-пуш сделали вы, то просто возьмите хэш коммита, который был в ветке до вас (как и в прошлом пункте). Если не вы, то можно зайти в ленту событий GitHub’а (на главной странице, в случае, если вы подписаны на репозиторий) и посмотреть, кто последний коммитил в эту ветку:
Теперь через консоль можно получить недостающие коммиты:
$ git fetch From github.com:org/repo * master-before-force-push -> origin/master-before-force-push
и теперь задача сводится к предыдущей:
Если наработки в вашем е ещё нужны, то лучше отребейзить свои коммиты поверх него:
Изменение самой последней фиксации
Команда позволяет вам изменить самое последнее сообщение фиксации.
Не нажата фиксация
Чтобы изменить сообщение о самой последней фиксации, которая не была в удаленный репозиторий, повторите ее с флага .
-
Перейдите в каталог репозитория в вашем терминале.
-
Выполните следующую команду, чтобы исправить (изменить) сообщение последней фиксации:
Что делает команда, так это перезапись самой последней фиксации новой.
Параметр позволяет вам написать новое сообщение в командной строке, не открывая сеанс редактора.
Перед изменением сообщения о фиксации вы также можете добавить другие изменения, которые вы ранее забыли:
Нажатая фиксация
Исправленный (измененный) коммит — это новая сущность с другим SHA-1. Предыдущая фиксация больше не будет существовать в текущей ветке.
Как правило, вам следует избегать изменения уже отправленной фиксации, поскольку это может вызвать проблемы у людей, которые основывают свою работу на этой фиксации. Перед изменением принудительной фиксации рекомендуется проконсультироваться с коллегами-разработчиками.
Если вы изменили сообщение последнего отправленного коммита, вам придется принудительно отправить его.
-
Перейдите в репозиторий.
-
Измените сообщение последней нажатой фиксации:
-
Принудительно нажмите, чтобы обновить историю удаленного репозитория:
Временно переключитесь на другую фиксацию
Если вы хотите временно вернуться к нему, подурачиться, а затем вернуться туда, где вы находитесь, все, что вам нужно сделать, это проверить желаемый коммит:
Или, если вы хотите совершать коммиты, пока вы там, продолжайте и создайте новую ветку, пока вы в ней:
Чтобы вернуться туда, где вы были, просто проверьте ветку, в которой вы были снова. (Если вы внесли изменения, как всегда при переключении ветвей, вам придется иметь с ними дело соответствующим образом. Вы можете выполнить сброс, чтобы выбросить их; вы можете спрятать, оформить заказ, припрятать поп, чтобы взять их с собой; вы можете зафиксировать их в ветку там, если вы хотите ветку там.)
Rogue Coder?
Работаете самостоятельно и просто хотите, чтобы все заработало? Следуйте этим инструкциям ниже, они надежно работают для меня и многих других в течение многих лет.
Работаете с другими? Git сложен. Прочтите комментарии под этим ответом, прежде чем делать что-то необдуманное.
Возврат рабочей копии к самой последней фиксации
Чтобы вернуться к предыдущей фиксации, игнорируя любые изменения:
Где HEAD — последняя фиксация в вашей текущей ветке
Возврат рабочей копии к более ранней фиксации
Чтобы вернуться к фиксации, более ранней, чем самая последняя фиксация:
Кредиты идут на аналогичный вопрос о переполнении стека, Вернуться к фиксации с помощью хэша SHA в Git? .
Просмотр истории коммитов
Не все коммиты будете делать вы, какие-то будут делать ваши коллеги по команде, поэтому вам может понадобиться изучить историю коммитов. Одним из основных и наиболее мощных инструментов для этого является команда .
Помимо автора и даты, у каждого комита есть идентификатор, который называется hash. Пример: 2934ee19f4d4ca37ff9bea9dc8208ef5362d789e. Необязательно использовать такую длинную запись, git поймет и по первым 5 символам, какой hash вам нужен.
Команда имеет очень большое количество опций для поиска коммитов по разным критериям.
Одним из самых полезных аргументов является или , который показывает разницу, внесенную в каждый коммит. Можно ограничить количество записей в выводе команды, используя параметр :
Общее
Git — система контроля версий (файлов). Что-то вроде возможности сохраняться в компьютерных играх (в Git эквивалент игрового сохранения — коммит)
Важно: добавление файлов к «сохранению» двухступенчатое: сначала добавляем файл в индекс (), потом «сохраняем» ()
Любой файл в директории существующего репозитория может находиться или не находиться под версионным контролем (отслеживаемые и неотслеживаемые).
Отслеживаемые файлы могут быть в 3-х состояниях: неизменённые, изменённые, проиндексированные (готовые к коммиту).
Ключ к пониманию
Ключ к пониманию концепции git — знание о «трех деревьях»:
- Рабочая директория — файловая система проекта (те файлы, с которыми вы работаете).
- Индекс — список отслеживаемых git-ом файлов и директорий, промежуточное хранилище изменений (редактирование, удаление отслеживаемых файлов).
- Директория — все данные контроля версий этого проекта (вся история разработки: коммиты, ветки, теги и пр.).
Коммит — «сохранение» (хранит набор изменений, сделанный в рабочей директории с момента предыдущего коммита). Коммит неизменен, его нельзя отредактировать.
У всех коммитов (кроме самого первого) есть один или более родительских коммитов, поскольку коммиты хранят изменения от предыдущих состояний.
Простейший цикл работ
- Редактирование, добавление, удаление файлов (собственно, работа).
- Индексация/добавление файлов в индекс (указание для git какие изменения нужно будет закоммитить).
- Коммит (фиксация изменений).
- Возврат к шагу 1 или отход ко сну.
Указатели
- — указатель на текущий коммит или на текущую ветку (то есть, в любом случае, на коммит). Указывает на родителя коммита, который будет создан следующим.
- — указатель на коммит, с которого вы только что переместили (командой , например).
- Ветка (, etc.) — указатель на коммит. При добавлении коммита, указатель ветки перемещается с родительского коммита на новый.
- Теги — простые указатели на коммиты. Не перемещаются.
Перед началом работы нужно выполнить некоторые настройки:
Если вы в Windows:
Длинный вывод в консоли: Vim
Если нужно что-то написать, нажмите i — это переход в режим вставки текста. Если нужно сохранить изменения, перейдите в командный режим и наберите :w.
# Нажатия кнопок ESC — переход в командный режим i — переход в режим редактирования текста ZQ (зажат Shift, поочередное нажатие) — выход без сохранения ZZ (зажат Shift, поочередное нажатие) — сохранить и выйти ```bash # Нажатия кнопок ESC — переход в командный режим i — переход в режим редактирования текста ZQ (зажат Shift, поочередное нажатие) — выход без сохранения ZZ (зажат Shift, поочередное нажатие) — сохранить и выйти # Ввод в командном режиме :q! — выйти без сохранения :wq — сохранить файл и выйти :w filename.txt — сохранить файл как filename.txt