Содержание
githowto.com
Первоначальная настройка
# Кто автор коммитов в этом экземпляре гита - имя и адрес git config --global user.name "Your Name" git config --global user.email "your_email@whatever.com" # Задать main веткой по умолчанию git config --global init.defaultBranch main # Корректно обрабатывать окончания строк (для Windows) git config --global core.autocrlf true git config --global core.safecrlf warn
Создание проекта
# Создать новый репозиторий (можно уже после создания файлов проекта в каталоге) git init # создастся подкаталог .git # Добавить файл и закоммитить с комментарием git add hello.html git commit -m "Initial Commit" # Проверить состояние репозитория git status On branch main # Сейчас на ветке main nothing to commit, working tree clean # Нет файлов, ожидающих записи, репозиторий равен текущему состоянию каталога # Переименовать master в main, если гит старый и команда, делающая ветку main основной, не сработала git branch -m master main
Внесение изменений
# Если что-то добавить в файл и посмотреть состояние репозитория: git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: hello.html no changes added to commit (use "git add" and/or "git commit -a") # Можно откатить файл обратно git restore hello.html # А можно добавить его в коммит (проиндексировать) git add hello.html # После добавления статус показывает, что файл проиндексирован git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: hello.html # Можно откатить индексацию (файл вернётся к добавленному, но не индексированному состоянию) git restore --staged hello.html # Отдельный шаг индексации позволяет разделять добавленные файлы на разные коммиты, # чтобы не связанные между собой изменения не попадали в один коммит. # К примеру, изменены 3 файла - a.html, b.html и c.html, и файл c.html не связан с другими. git add a.html git add b.html git commit -m "Changes for a and b" git add c.html git commit -m "Unrelated change to c"
Коммит
Если не задать параметр -m для git commit, то откроется редактор. Он задаётся в одной из настроек
- переменная среды GIT_EDITOR
- параметр конфигурации core.editor
- переменная среды VISUAL
- переменная среды EDITOR
git config --global core.editor emacs # emacs git config --global core.editor "code --wait" # VSCode
Гит фокусируется на изменениях в файле, а не на самом файле. К примеру, если одно изменение в файле было проиндексировано, а второе - нет, то при коммите добавится только проиндексированное изменение. Второе нужно будет сначала проиндексировать и потом коммитить.
История проекта
git log git log commit 204912e8c6cf87c0743ffa5646f4c7438d6ebeaa (HEAD -> main) Author: Your Name <you@example.com> Date: Tue Jan 21 16:26:12 2025 +0300 Added HTML header commit 88622c4f43f5caa7a891542f09564c68fc362682 Author: Your Name <you@example.com> Date: Tue Jan 21 16:15:07 2025 +0300 Added H1 tag commit 1a3a27e686a3baeec77a9f44a65471b88495a3e1 Author: unknown <user@DESKTOP.workgroup> Date: Tue Jan 21 15:17:07 2025 +0300 Initial Commit # Вид настраивается, например, однострочный вид git log --pretty=oneline 204912e8c6cf87c0743ffa5646f4c7438d6ebeaa (HEAD -> main) Added HTML header 88622c4f43f5caa7a891542f09564c68fc362682 Added H1 tag 1a3a27e686a3baeec77a9f44a65471b88495a3e1 Initial Commit # Ещё варианты git log --oneline --max-count=2 # 2 последних коммита git log --oneline --since="5 minutes ago" # за последние 5 минут git log --oneline --until="5 minutes ago" # кроме последних 5 минут git log --oneline --author="Your Name" # фильтр по автору git log --all --pretty=format:"%h %cd %s (%an)" --since="7 days ago" # Укороченный хэш, время, коммент, автор 204912e Tue Jan 21 16:26:12 2025 +0300 Added HTML header (Your Name) 88622c4 Tue Jan 21 16:15:07 2025 +0300 Added H1 tag (Your Name) 1a3a27e Tue Jan 21 15:17:07 2025 +0300 Initial Commit (unknown) git log --pretty=format:"%h %ad | %s%d [%an]" --date=short # Ещё вариант 204912e 2025-01-21 | Added HTML header (HEAD -> main) [Your Name] 88622c4 2025-01-21 | Added H1 tag [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown] # Сделать последний вариант pretty format по умолчанию git config --global format.pretty '%h %ad | %s%d [%an]' git config --global log.date short
Откат на старые версии
# Откатиться на самый первый коммит (достаточно первых 7 символов хэша как указатель) git checkout 1a3a27e Note: switching to '1a3a27e'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 1a3a27e Initial Commit # hello.html выглядит как в самом начале cat .\hello.html Hello, World # Вернуться на последнюю версию в ветке main git switch main # hello.html теперь опять со всеми изменениями cat .\hello.html <html> <body> <h1>Hello, World!</h1> </body> </html>
Создание тэгов версий
Работать с хэшами коммитов неудобно. Можно задать человеческий тэг, тогда можно будет сослаться на него.
git tag v1 # задать тэг текущей версии git log -n 1 204912e 2025-01-21 | Added HTML header (HEAD -> main, tag: v1) [Your Name] ### Задать тэг предыдущей версии # Переключиться на предыдущую версию git checkout v1^ # или git checkout v1~1 git tag v1-beta git log -n 1 88622c4 2025-01-21 | Added H1 tag (HEAD, tag: v1-beta) [Your Name] # Теперь можно переключаться по тэгам git checkout v1 Previous HEAD position was 88622c4 Added H1 tag HEAD is now at 204912e Added HTML header git checkout v1-beta Previous HEAD position was 204912e Added HTML header HEAD is now at 88622c4 Added H1 tag # Список тэгов git tag v1 v1-beta # Тэги в логе git log main --all 204912e 2025-01-21 | Added HTML header (tag: v1, main) [Your Name] 88622c4 2025-01-21 | Added H1 tag (HEAD, tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown]
Отмена локальных изменений
До индексации
Чтобы откатить изменения в файле, ещё не добавленном в коммит:
# Вот есть изменения, которые надо отменить git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: hello.html no changes added to commit (use "git add" and/or "git commit -a") # Откатить изменения git restore hello.html # Теперь чисто, изменений нет git status On branch main nothing to commit, working tree clean
После индексации
Отмена изменений в файле, добавленном в коммит
git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: hello.html # Убрать файл из индекса (коммита). Файл при этом остался изменённым! git restore --staged hello.html # Дальше действовать как до индексации git restore hello.html
Отмена коммитов
Есть несколько способов откатить кривой коммит.
Новый коммит, отменяющий предыдущий
revert
- история коммитов сохраняется: есть и кривой коммит, и отменяющий его.
git revert HEAD [main 20dea26] Revert "Wrong commit" 1 file changed, 1 deletion(-) PS C:\temp\githowto\repositories\work> git log 20dea26 2025-01-21 | Revert "Wrong commit" (HEAD -> main) [Your Name] c90b848 2025-01-21 | Wrong commit [Your Name] 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown]
Удаление коммита из истории
reset
- Изменит текущую ветку, чтобы она указывала на выбранный коммит.
- Опционально сбросит область подготовки до соответствия с указанным коммитом.
- Опционально сбросит рабочую директорию до соответствия с указанным коммитом.
# Сбросит ветку до коммита v1 (здесь: бесследно удалится 2 последних коммита, откатятся изменения в файлах) git reset --hard v1 # Лог чист git log 204912e 2025-01-21 | Added HTML header (HEAD -> main, tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown] # Тем не менее, коммиты ещё есть в списке всех, просто их нет в ветке main git log --all 20dea26 2025-01-21 | Revert "Wrong commit" (tag: wrong) [Your Name] c90b848 2025-01-21 | Wrong commit [Your Name] 204912e 2025-01-21 | Added HTML header (HEAD -> main, tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown]
На удалённые коммиты можно сослаться либо по хэшу, либо по тэгу, если он есть. Удалённые коммиты, на которые нет ссылок или тэгов, существуют до запуска сборщика мусора.
Здесь у одного из удалённых коммитов есть тэг, его надо удалить, чтобы сборщик мусора смог на нём отработать.
git tag -d wrong Deleted tag 'wrong' (was 20dea26) # Теперь в списке всех коммитов также нет ошибочных git log --all 204912e 2025-01-21 | Added HTML header (HEAD -> main, tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown]
Сброс в локальных ветках, как правило, безопасен. Последствия любой ошибки, как правило, можно восстановить простым сбросом с помощью нужного коммита.
Однако, если ветка уже стала общедоступной на удаленных репозиториях, сброс может сбить с толку других пользователей ветки.
Внесение изменений в коммиты
Применяется, когда нужно заменить коммит другим, например, когда что-то просто забыли добавить/удалить/изменить в сделанном коммите.
git commit -m "Add copyright" [main 3d91d05] Add copyright 1 file changed, 1 insertion(+) git log 3d91d05 2025-01-22 | Add copyright (HEAD -> main) [Your Name] 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown] git add .\hello.html git commit --amend -m "Add copyright with email" [main b0d54f0] Add copyright with email Date: Wed Jan 22 13:41:15 2025 +0300 1 file changed, 1 insertion(+) # Последний коммит заменён на новый git log b0d54f0 2025-01-22 | Add copyright with email (HEAD -> main) [Your Name] 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] 1a3a27e 2025-01-21 | Initial Commit [unknown]
Этого же результата можно достичь путем сброса последнего коммита в ветке и повторного коммита новых изменений.
Создание веток и переключение между ними
# Создать ветку style git switch -c style Switched to a new branch 'style' # Старый способ (не рекомендуется) git checkout -b style git status On branch style nothing to commit, working tree clean # Переключение между ветками (hello.html будет разным после коммитов в ветку style) git switch main cat hello.html git switch style cat hello.html
Перемещение файлов
Если тупо переименовать или перенести файлы в репозитории, то они будут помечены как удалённые и созданные заново. Связи между этими файлами нет и в истории гита нельзя будет отследить, что откуда взялось. Гит иногда догадывается, что файл был переименован, но рассчитывать на это нельзя.
# После обычного переименования/перемещения файлов руками git status On branch style Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) deleted: hello.html deleted: style.css Untracked files: (use "git add <file>..." to include in what will be committed) css/ index.html no changes added to commit (use "git add" and/or "git commit -a") # Перенести файлы корректно mkdir css git mv style.css css/style.css git mv hello.html index.html git status # Теперь статус в порядке git status On branch style Changes to be committed: (use "git restore --staged <file>..." to unstage) renamed: style.css -> css/style.css renamed: hello.html -> index.html # Чтобы посмотреть изменения файла до переименования, добавить параметр --follow git log css/style.css 4a6c610 2025-01-22 | Renamed hello.html; moved style.css (HEAD -> style) [Your Name] git log --follow css/style.css 4a6c610 2025-01-22 | Renamed hello.html; moved style.css (HEAD -> style) [Your Name] aa9e5d8 2025-01-22 | Add css [Your Name]
Просмотр отличающихся веток
# --all - показать все ветки (по умолчанию показывается текущая ветка) # --graph - дерево в виде простых текстовых линий git log --all --graph * b022e6a 2025-01-22 | Add README (HEAD -> main) [Your Name] | * 4a6c610 2025-01-22 | Renamed hello.html; moved style.css (style) [Your Name] | * 632e4e7 2025-01-22 | Include css into hello.html [Your Name] | * aa9e5d8 2025-01-22 | Add css [Your Name] |/ * b0d54f0 2025-01-22 | Add copyright with email [Your Name] * 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] * 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] * 1a3a27e 2025-01-21 | Initial Commit [unknown]
Слияние веток
Слить ветку main в style
# Перейти в ветку style git switch style Switched to branch 'style' # Слить ветки git merge main Merge made by the 'ort' strategy. README | 1 + 1 file changed, 1 insertion(+) create mode 100644 README # График коммитов git log --all --graph * 98277e2 2025-01-22 | Merge branch 'main' into style (HEAD -> style) [Your Name] |\ | * b022e6a 2025-01-22 | Add README (main) [Your Name] * | 4a6c610 2025-01-22 | Renamed hello.html; moved style.css [Your Name] * | 632e4e7 2025-01-22 | Include css into hello.html [Your Name] * | aa9e5d8 2025-01-22 | Add css [Your Name] |/ * b0d54f0 2025-01-22 | Add copyright with email [Your Name] * 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] * 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] * 1a3a27e 2025-01-21 | Initial Commit [unknown]
Путем периодического слияния ветки main с веткой style происходит перенос изменений из main и поддерживается совместимость изменений style с изменениями в основной ветке. Однако, это делает графики коммитов уродливыми. Альтернатива слиянию - перебазирование (rebase).
Конфликтующие изменения в ветках и разрешение конфликта
Если в обеих ветках была изменена одна и та же часть файла, Git может не справиться с автоматическим слиянием изменений. В этом случае Git сообщит о конфликте и попросит разрешить его вручную.
После попытки слива веток
git switch style Switched to branch 'style' git merge main Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.
Файл index.html выглядит так:
<!-- Author: Alexander Shvets (alex@githowto.com) --> <html> <head> <<<<<<< HEAD:index.html <link type="text/css" rel="stylesheet" media="all" href="style.css" /> ======= <title>Hello World Page</title> >>>>>>> main:hello.html </head> <body> <h1>Hello, World!</h1> <p>Let's learn Git together.</p> </body> </html>
Часть между <<<<<<<
и >>>>>>>
является конфликтом. Верхняя часть соответствует ветке style, которая является текущей веткой (или HEAD) репозитория. Нижняя часть соответствует изменениям из ветки main. Видно, что часть с <p>Let's learn Git together.</p>
git сделал сам.
# Можно отменить слияние веток git merge --abort # А можно отредактировать index.html и закоммитить его git add index.html git commit -m "Resolved merge conflict" git status On branch style nothing to commit, working tree clean git log --all --graph * e71d981 2025-01-22 | Resolved merge conflict (HEAD -> style) [Your Name] |\ | * 9e1309b 2025-01-22 | Added meta title (main) [Your Name] * | 98277e2 2025-01-22 | Merge branch 'main' into style [Your Name] |\| | * b022e6a 2025-01-22 | Add README [Your Name] * | 4a6c610 2025-01-22 | Renamed hello.html; moved style.css [Your Name] * | 632e4e7 2025-01-22 | Include css into hello.html [Your Name] * | aa9e5d8 2025-01-22 | Add css [Your Name] |/ * b0d54f0 2025-01-22 | Add copyright with email [Your Name] * 204912e 2025-01-21 | Added HTML header (tag: v1) [Your Name] * 88622c4 2025-01-21 | Added H1 tag (tag: v1-beta) [Your Name] * 1a3a27e 2025-01-21 | Initial Commit [unknown]
У Git нет графических инструментов слияния, но можно настроить сторонние.