Кратко про Git
Git – система контроля версий. Что-то вроде возможности сохраняться в компьютерных играх. Только более функционально. Помогает фиксировать и работать с изменениями в проекте при одиночной и командной разработке. Добавление файлов двухэтапное: сначала добавляем файл в индекс git add
, потом сохраняем git commit
.
Любой файл в директории существующего репозитория может быть зафиксирован или нет. То есть отслеживаться или не отслеживаться.
Отслеживаемые файлы могут быть в 3-х состояниях: неизменённые, изменённые и готовые к коммиту.
Как это работает?
Чтобы понять Git, рассмотрим концепцию трёх деревьев:
- Рабочая директория — все файлы проекта;
- Индекс — отслеживаемые git-ом файлы и директории, промежуточное хранилище изменений. Это редактирование и удаление отслеживаемых файлов;
- Директория
.git
— всё для работы системы контроля версий. Коммиты, ветки и прочее.
Коммит неизменен, его нельзя отредактировать. В крайнем случае удалить, но это нежелательно.
У всех коммитов, кроме самого первого, есть один или более родительских коммитов, потому что коммиты хранят изменения от предыдущих состояний. В программировании это называют односвязным списком.
Как с ним работать?
- Редактировать, добавлять и удалять файлы;
- Добавлять файлы в индекс;
- Делать коммит;
- Возврат к первому шагу.
Некоторые понятия
- Ветка. Например,
main
,master
,dev
и т.д. — набор изменений, по сути один из набора односвязных списков; HEAD
— указатель на последний коммит в ветке.
Команды и флаги Git
Настройки
Перед началом работы нужно выполнить некоторые настройки:
git config --global user.name "<имя>" # указать имя, которым будут подписаны коммиты
git config --global user.email "<email>" # указать электронную почту
Если работаете в Windows:
git config --global core.autocrlf true # включить преобразование окончаний строк из CRLF в LF
Указание не отслеживаемых файлов
Файлы и директории, которые не нужно включать в репозиторий, указываются в файле .gitignore
. Обычно это устанавливаемые зависимости node_modules/
, venv/
, готовая сборка build/
или dist/
. И подобные, создаваемые при установке или запуске. Каждый файл или директория указываются с новой строки. Можно использовать генераторы.
Создать новый репозиторий
git init # создать новый репозиторий в текущей директории
git init folder-name # создать новый репозиторий в указанной директории
Клонирование репозитория
# клонировать удалённый репозиторий в одноименную директорию
git clone <ссылка на репозиторий в ssh>
# клонировать удаленный репозиторий в директорию «FolderName»
git clone <ссылка на репозиторий в ssh> имяпапки
# клонировать репозиторий в текущую директорию
git clone <ссылка на репозиторий в ssh> .
Добавление изменений в индекс
git add . # добавить в индекс все новые, изменённые, удалённые файлы из текущей директории и её поддиректорий
git add text.txt # добавить в индекс указанный файл (был изменён, был удалён или это новый файл)
git add -i # запустить интерактивную оболочку для добавления в индекс только выбранных файлов
git add -p # показать новые/изменённые файлы по очереди с указанием их изменений и вопросом об отслеживании/индексировании
Коммиты
git commit -m "<название коммита>" # зафиксировать в коммите проиндексированные изменения, без сообщения
git commit -am "<название коммита>" # проиндексировать отслеживаемые файлы и закоммитить, добавить сообщение
Просмотр изменений
git status # показать состояние репозитория
git diff # сравнить рабочую директорию и индекс. неотслеживаемые файлы игнорируются
git diff --color-words # сравнить рабочую директорию и индекс, показать отличия в словах. неотслеживаемые файлы игнорируются
git diff index.html # сравнить файл из рабочей директории и индекс
git diff HEAD # сравнить рабочую директорию и коммит, на который указывает HEAD. неотслеживаемые файлы игнорируются
git diff --staged # сравнить индекс и коммит с HEAD
git diff main feature # посмотреть что сделано в ветке feature по сравнению с веткой main
git diff --name-only master feature # посмотреть что сделано в ветке feature по сравнению с веткой master, показать только имена файлов
git diff master...feature # посмотреть что сделано в ветке feature с момента расхождения с main
Удаление изменений из индекса
git reset # убрать из индекса все добавленные в него изменения. в рабочей директории все изменения сохранятся. антипод git add
git reset README.md # убрать из индекса изменения указанного файла. в рабочей директории изменения сохранятся
Отмена изменений
git reset --hard # опасно: отменить изменения; вернуть то, что в коммите, на который указывает HEAD (
git clean -df # удалить неотслеживаемые изменения и директории
Отмена коммитов и перемещение по истории
Все коммиты, которые уже были отправлены в удалённый репозиторий, должны отменяться новыми коммитами git revert
, дабы избежать проблем с историей разработки у других участников проекта.
git revert HEAD --no-edit # создать новый коммит, отменяющий изменения последнего коммита без запуска редактора сообщения
git revert b9533bb --no-edit # то же, но отменяются изменения, внесённые коммитом с указанным хэшем b9533bb
Переключиться на другой коммит / коммиты
git checkout b9533bb # переключиться на коммит с указанным хешем
git checkout dev # переключиться на другую ветку
git checkout -b new-branch # создание новой ветки и переключение на неё
Удаление файла
git rm text.txt # удалить отслеживаемый неизменённый файл и проиндексировать это изменение
git rm -f text.txt # удалить отслеживаемый изменённый файл и проиндексировать это изменение
git rm -r log/ # удалить всё содержимое отслеживаемой директории log/ и проиндексировать это изменение
git rm ind* # удалить все отслеживаемые файлы с именем, начинающимся на in» в текущей директории и проиндексировать это изменение
git rm --cached README.md # удалить из отслеживаемых индексированный файл. файл останется на месте. часто используется для нечаянно добавленных в индексирование файлов
История коммитов
Выход из длинного лога вывода: q
, перемещения b / d / u.
git log # показать все изменения
git log main # показать коммиты в указанной ветке
git log -2 # показать последние 2 коммита в активной ветке
git log -2 --stat # показать последние 2 коммита и статистику внесённых изменений
git log -p -22 # показать последние 22 коммита и внесённую разницу на уровне строк
git log --graph -10 # показать последние 10 коммитов с красивым ветвлением
git log --since=2.weeks # показать коммиты за последние 2 недели
git log --after '2024-02-19' # показать коммиты, сделанные после указанной даты
git log index.html # показать историю изменений файла index.html
git log -5 index.html # показать историю изменений файла index.html, последние 5 коммитов
Кто написал строку
git blame README.md --date=short -L 5,8 # показать строки 5-8 указанного файла и коммиты, в которых строки были добавлены
Ветки
git branch # показать список веток
git branch -v # показать список веток и последний коммит в каждой
git branch new-branch # создать новую ветку с указанным именем на текущем коммите
git checkout new-branch # перейти в указанную ветку
git checkout -b new_-ranch # создать новую ветку с указанным именем и перейти в неё
git merge hotfix # влить изменения в ветку, в которой находимся, данные из ветки hotfix
git merge hotfix --log # влить изменения в ветку, в которой находимся, данные из ветки hotfix, показать редактор описания коммита, добавить в него сообщения вливаемых коммитов
Временное скрытие изменений без коммита
git stash # временно сохранить незакоммиченные изменения и убрать их из рабочей директории
git stash pop # вернуть сохраненные командой git stash изменения в рабочую директорию
Удалённые репозитории
git remote -v # показать список удалённых репозиториев, связанных с локальным
git branch -r # показать удалённые ветки
git remote remove origin # убрать привязку удалённого репозитория с веткой origin
git remote add origin <ссылка на репозиторий> # добавить удалённый репозиторийс указанной ссылкой на SSH
git remote rm origin # удалить привязку удалённого репозитория
git fetch origin # скачать все ветки с удалённого репозитория, но не сливать со своими ветками
git fetch origin main # то же, но скачивается только указанная ветка
git push origin main # отправить в удалённый репозиторий своей ветки main
git pull origin # влить изменения с удалённого репозитория
Конфликт слияния
Предполагается ситуация: есть ветка main
и есть ветка feature
. В обеих ветках есть коммиты, сделанные после расхождения веток. В ветку main
пытаемся влить ветку feature
git merge feature
, получаем конфликт. Потому что в обеих ветках есть изменения одной и той же строки в файле index.html
.
При возникновении конфликта, репозиторий находится в состоянии прерванного слияния. Нужно оставить в конфликтующих местах файлов только нужный код, проиндексировать изменения и закоммитить.
git merge feature # влить в активную ветку изменения из ветки feature
git merge-base main feature # показать хеш последнего общего коммита для двух указанных веток
git checkout --ours index.html # оставить в конфликтном файле index.html состояние ветки, в которые вливаем
git checkout --theirs index.html # оставить в конфликтном файле index.html состояние ветки, из которой мы вливаем
git checkout --merge index.html # показать в конфликтном файле index.html сравнение содержимого сливаемых веток для ручного редактирования
git checkout --conflict=diff3 --merge index.html # показать в конфликтном файле index.html сравнение содержимого сливаемых веток
git reset --hard # прекратить это прерванное слияние, вернуть рабочую директорию и индекс как было в момент коммита, на который указывает HEAD. потом плакать
git reset --merge # прекратить это прерванное слияние, но оставить изменения, не закоммиченные до слияния
git reset --abort # то же, что и строкой выше
Переносим ветки
Можно переместить ответвление какой-либо ветки от основной на произвольный коммит. Это нужно для того, чтобы в «ереносимой ветке появились какие-либо изменения, внесённые в основной ветке.
Нельзя переносить ветку, если она уже отправлена на удалённый репозиторий.
git rebase main # перенести все коммиты активной ветки так, будто активная ветка ответвилась от main. часто вызывает конфликты
git rebase --onto master feature # перенести коммиты активной ветки на main, начиная с того места, в котором активная ветка отделилась от ветки feature
git rebase --abort # прервать конфликтный rebase, вернуть рабочую директорию и индекс к состоянию до начала rebase
git rebase --continue # продолжить конфликтный rebase
Архивирование
git archive -o ./project.zip HEAD # создать архив с файловой структурой проекта по указанному пути состояние репозитория, соответствующее указателю HEAD
Примеры
Простые и сложные примеры использования
Начало работы
Создание нового репозитория, первый коммит, привязка удалённого репозитория с gthub.com, отправка изменений в удалённый репозиторий.
# указана последовательность действий:
# директория проекта уже оздана, мы в ней
git init # создаём репозиторий в этой директории
touch README.md # создаём файл README.md
git add README.md # добавляем файл в индекс
git commit -m "first commit" # создаём коммит
git remote add origin <ссылка на удалённый репозиторий> # добавляем предварительно созданный пустой удаленный репозиторий
git push -u origin main # отправляем данные из локального репозитория в удалённый
Внесение изменений»в коммит
Только если коммит ещё не был отправлен в удалённые репозиторий.
# указана последовательность действий:
git add index.html # индексируем измененный файл
git commit -m "add orange button" # делаем коммит
# Коммит пока не был отправлен в удалённый репозиторий
# Понимаем, что нужно было ещё что-то сделать в этом коммите.
# вносим изменения
git add index.html # индексируем измененный файл
git commit --amend -m "add orange button and button handler" # заново делаем коммит
Работа с ветками
Есть ветка main, выполняем масштабную задачу, но возникает необходимость подправить критичный баг.
# указана последовательность действий:
git checkout -b new-feature # создадим новую ветку для изменений
# вносим изменения
git commit -a -m "add new line" # делаем коммит
# тут выясняется, что есть баг
git checkout main # возвращаемся к ветке main
# устраняем баг
git commit -a -m "fix bug bzzzz" # делаем коммит
git push # отправляем коммит в удалённый репозиторий
git checkout new-feature # переключаемся обратно в ветку new-page-header для продолжения работ над «шапкой»
# редактируем
git commit -a -m "add new line" # делаем коммит
git checkout master # переключаемся в ветку main
git merge new-feature # вливаем в master изменения из ветки new-feature
git branch -d new-feature # удаляем ветку new-feature
Работа с ветками, конфликт слияния
Есть ветка main
, в двух параллельных ветках new-feature-1
и new-feature-2
было отредактировано одно и то же место одного и того же файла, первую ветку new-feature-1
влили в main
, попытка влить вторую вызывает конфликт.
# указана последовательность действий:
git checkout main # переключаемся на ветку main
git checkout -b new-feature-1 # создаём ветку new-feature-1, основанную на ветке main
# редактируем файлы
git commit -a -m "fix №1" # коммитим
git checkout main # возвращаемся к ветке main
git checkout -b new-feature-2 # создаём ветку new-feature-2, основанную на ветке main
# редактируем файлы
git commit -a -m "fix №2" # коммитим
git checkout main # возвращаемся к ветке main
git merge new-feature-1 # вливаем изменения из ветки new-feature-1 в текущую ветку main. работает
git merge new-feature-2 # вливаем изменения из ветки new-feature-2 в текущую ветку main. конфликт
# Automatic merge failed; fix conflicts and then commit the result.
# выбираем в конфликтных файлах те участки, которые нужно оставить, сохраняем
git commit -a -m "fix conflict" # коммитим результат устранения конфликта
Синхронизация репозитория-форка с мастер-репозиторием
Есть некий репозиторий на GitHub, там мы сделали форк проекта. В форк добавлены какие-то изменения. Задача: стянуть с основного репозитория изменения, которые были внесены уже после нашего форка.
# указана последовательность действий:
git remote add upstream <ссылка на репозиторий> # добавляем удалённый репозиторий. upstream приставка для удалённых веток
git fetch upstream # стягиваем все ветки мастер-репозитория, но пока не сливаем со своими
git checkout main # переключаемся на ветку master своего репозитория
git merge upstream/main # вливаем стянутую ветку master удалённого репозитория upstream в свою ветку main
Ошибка в работе: закоммитили в мастер, но поняли, что нужно было коммитить в новую ветку
Сработает только если коммит еще не отправлен в удалённый репозиторий.
# указана последовательность действий:
git checkout -b new-branch # создаём новую ветку из main
git checkout main # переключаемся на main
git reset HEAD~ --hard # сдвигаем указатель main на 1 коммит назад
git checkout new-branch # переключаемся обратно на новую и продолжаем работу
Ответить