Основы Git — от первого коммита до GitHub и исправления ошибок
Основные понятия Git
Представим, что мы правим конфиг сервера и сохраняем копии «на всякий случай»:
nginx.conf
nginx.conf.bak
nginx.conf.old
nginx.conf.new_final
nginx.conf.new_final2
Через неделю уже непонятно, какая версия рабочая, чем они отличаются и когда что менялось. А если над файлом работают двое, всё становится ещё хуже.
Git — это программа, которая решает эту проблему. Она:
- помнит все версии файлов и может вернуться к любой;
- показывает, что, когда и кем было изменено;
- позволяет нескольким людям работать над одним проектом и потом объединять результат.
Такие программы называются системами контроля версий, а Git — самая популярная из них.
Все примеры в статье будут выполнены на Linux Mint 22.3, но всё должно работать аналогично и для Ubuntu 24.04/26.04, и для Debian 12/13.
Если git у вас ещё не установлен, установим:
apt install git
Для Windows 10/11 также есть возможность скачать и установить Git.
Глобальные настройки
Перед началом работы Git нужно настроить. Обязательными настройками являются указание вашего имени и email.
git config --global user.name "<user>"
git config --global user.email "<email>"
Это глобальные настройки, которые влияют на все репозитории на хосте.
Настройки хранятся в:
- Linux —
~/.gitconfig; - Windows —
C:\Users\<user>\.gitconfig.
Посмотрим используемые настройки:
git config --list
Репозиторий
Репозиторий (repository) — это папка с нашим проектом, за которой следит Git. Внутри неё находятся наши файлы и скрытая папка .git, где Git хранит всю историю.
Создадим каталог, перейдём в него и превратим обычный каталог в репозиторий:
mkdir my-project
cd my-project/
git init
В выводе увидим, что был инициализирован пустой репозиторий. А также был создан подкаталог
.git, который вручную трогать не нужно.
Коммит
Коммит (commit) — это что-то вроде точки восстановления, вы можете создать сколько угодно таких точек в истории, и на любую из них сможете вернуться в будущем. У каждого коммита есть:
- идентификатор;
- автор и дата;
- комментарий, что изменилось (например, «Добавил конфиг nginx»).
Каждый новый коммит помнит, какой коммит был перед ним. Так получается цепочка:
A ← B ← C ← D
Это и есть история проекта. По ней можно посмотреть, как проект менялся, и вернуться на любую точку.
Создадим простой конфиг для nginx (это просто пример):
nano nginx-demo.conf
server {
listen 8080;
server_name _;
location / {
return 200 "nginx git demo\n";
}
}
Добавим файл в индекс (про это ниже):
git add nginx-demo.conf
Сделаем первый коммит:
git commit -m "Первый коммит"
Посмотрим историю коммитов:
git log
commit 4ae3d68319d8b1c06763b582460ad2d05f874442 (HEAD -> master)
Author: user <email>
Date: Thu Oct 8 09:39:41 2026 +0300
Первый коммит
Коммиты в Git хранятся надёжно. Поэтому, пока мы коммитим, потерять работу сложно. Потерять можно как раз то, что не закоммичено.
Для краткого вывода удобно использовать опцию --oneline:
git log --oneline
4ae3d68 (HEAD -> master) Первый коммит
Очень важно понять: в Git изменения проходят три «зоны»:
┌──────────────┐ git add ┌─────────────┐ git commit ┌───────────┐
│Рабочая папка │ ────────▶ │Индекс │ ──────────▶ │Репозиторий│
│(то, что вы │ │(подготовлено│ │(сохранено │
│ редактируете)│ │ к коммиту) │ │ в истории)│
└──────────────┘ └─────────────┘ └───────────┘
- Рабочая папка: в ней обычные файлы, которые можно редактировать. Git знает, что файл изменился, но ничего не сохраняет.
- Индекс (область подготовки): список изменений, которые вы отобрали для следующего коммита. Команда
git addкладёт их сюда. - Репозиторий (сохранённая история): команда
git commitберёт всё, что лежит в индексе, и записывает как новый коммит.
Зачем нужен промежуточный шаг? Допустим, вы поменяли три файла: два по одной задаче, один по другой. Через индекс можно сделать два отдельных коммита с понятными комментариями.
Посмотреть, что где лежит, можно командой git status — но выполним её чуть позже.
Поправим конфиг:
nano nginx-demo.conf # изменим порт на 9090
Создадим ещё один файл:
nano README.md
Мой тестовый проект.
Файл
README.mdне обязательно писать большими буквами — это просто общепринятая традиция.
Пока не будем добавлять README.md в индекс, а посмотрим status:
git status
Текущая ветка: master
Изменения, которые не в индексе для коммита:
(используйте «git add <файл>...», чтобы добавить файл в индекс)
(используйте «git restore <файл>...», чтобы отменить изменения в рабочем каталоге)
изменено: nginx-demo.conf
Неотслеживаемые файлы:
(используйте «git add <файл>...», чтобы добавить в то, что будет включено в коммит)
README.md
индекс пуст (используйте «git add» и/или «git commit -a»)
Мы видим:
nginx-demo.confизменён, но не в индексе, то есть вcommitон не попадёт;README.md— неотслеживаемый (лежит в каталоге, но не добавлен в индекс и Git не отслеживает изменения в нём).
Добавим README.md в индекс, сделаем коммит. И то же самое сделаем с nginx-demo.conf:
git add README.md
git commit -m "Добавили README"
git add nginx-demo.conf
git commit -m "порт 9090"
После этого git status скажет: нечего коммитить. А история изменений (git log --oneline) выглядит так:
4428f03 (HEAD -> master) порт 9090
4ccfa36 Добавили README
f867913 Первый коммит
История идёт снизу вверх, самый последний коммит — сверху. А служебное имя
HEAD— это то, где вы сейчас находитесь.
Ветки
Допустим, мы хотим попробовать что-то рискованное, например переписать конфиг, но не хотим ломать рабочую версию. Для этого есть ветки. Ветка (branch) — это отдельная линия истории. Мы «ответвляемся» от основной линии, работаем в стороне, и основная остаётся нетронутой:
main: A ← B ← C ← D
↖
feature: E ← F
Если эксперимент удался, ветку можно слить (merge) обратно в основную. Если нет, ветку просто удаляют, и основная история не пострадала.
Основная ветка обычно называется main или master. В локальном репозитории (зависит от версии) по умолчанию master, но в хранилище GitHub по умолчанию main. Остальные создают под конкретные задачи: fix/nginx-port, feature/healthcheck.
Технически ветка — это просто имя, привязанное к коммиту, своего рода закладка. Когда вы делаете новый коммит в текущей ветке, закладка сдвигается на него. Ветки создаются мгновенно и их можно делать сколько угодно.
Создадим новую ветку и посмотрим на ветки (текущая будет помечена звёздочкой):
git branch fix/readme
git branch
fix/readme
* master
Переключимся на новую ветку:
git switch fix/readme
Кстати, можно создать новую ветку и сразу переключиться на неё:
git switch -c fix/readme # вместо двух команд
Сделаем изменения в файле README.md:
nano README.md
Мой тестовый проект.
Хранит файл настроек Nginx.
Добавим в индекс и сделаем коммит:
git add README.md
git commit -m "Поправили README"
В результате:
git log --oneline
a0099a1 (HEAD -> fix/readme) Поправили README
4428f03 (master) порт 9090
4ccfa36 Добавили README
f867913 Первый коммит
Дальше обычно проверяют новую функциональность, и если всё хорошо, то объединяют ветку с основной (в нашем случае master). Но давайте сначала переключимся на master, проверим, что там файл не изменён, а затем произведём слияние:
git switch master
cat README.md
Мой тестовый проект.
Слияние выполняется на целевой ветке. Мы на master, поэтому просто:
git merge fix/readme
В выводе мы увидим, что слияние изменило файл
README.md.
Посмотрим историю проекта:
git log --oneline
a0099a1 (HEAD -> master, fix/readme) Поправили README
4428f03 порт 9090
4ccfa36 Добавили README
f867913 Первый коммит
Теперь обе ветки
master(текущая) иfix/readmeуказывают на один и тот же коммит (a0099a1). Произошло перемещение веткиmasterна коммит изfix/readme. Это называется Fast-forward.
Можем посмотреть ветки с опцией -v, чтобы увидеть хеши коммитов на ветках:
git branch -v
fix/readme a0099a1 Поправили README
* master a0099a1 Поправили README
Если ветка уже не нужна, можем её удалить:
git branch -d fix/readme
Стоит отметить, что Git откажется удалять неслитую ветку, тогда нужно использовать
-D, это удалит неслитую ветку без проверки. Главное — быть внимательными и не удалить нужную ветку.
Теги
В отличие от ветки, тег (tag) — это закладка, которая не двигается: она навсегда указывает на конкретный коммит. Обычно теги ставят на релизы, например v1.0.0.
git tag -a v1.0.0 -m "Первый релиз" # создать тег
git tag # показать список тегов
Удалённый репозиторий
До сих пор всё происходило на вашем компьютере. Но обычно проект лежит ещё и на сервере: GitHub, GitLab, Gitea. Так им могут пользоваться коллеги, а сервер служит резервной копией.
- Локальный репозиторий: тот, что у вас на компьютере.
- Удалённый репозиторий: копия на сервере.
Основные действия:
| Что нужно | Команда | Что происходит |
|---|---|---|
| Получить проект в первый раз | git clone <адрес> | скачивается весь проект со всей историей |
| Отправить свои коммиты на сервер | git push | ваши коммиты копируются на сервер |
| Скачать новое с сервера | git fetch | скачивает новые коммиты, но ваши файлы не меняет |
| Скачать и сразу применить | git pull | то же, что fetch, и затем объединяет с вашей веткой |
Ваш компьютер Сервер
┌───────────┐ git push ──▶ ┌───────────┐
│локальный │ │удалённый │
│репозиторий│ ◀── git pull │репозиторий│
└───────────┘ └───────────┘
Полезная привычка: сначала делать
git fetch, смотреть, что появилось нового (git status,git log), и только потом решать, объединять или нет.
Важная особенность Git: у каждого, кто сделал clone, на компьютере лежит полная копия истории. Поэтому почти всё (log, commit, branch) работает без интернета, а связь с сервером нужна только для push, pull и fetch. Сервер — это не «главная программа», а общее место, о котором команда договорилась.
На GitHub по умолчанию имя главной ветки — main. Чтобы избежать путаницы, желательно локальный master переименовать в main.
git status # убедимся, что нет незакоммиченных изменений
git switch master # если вы не на master, то нужно переключиться на эту ветку
git branch -m main # переименуем ветку
git branch # проверим
Нужно создать удалённый репозиторий перед подключением к нему локального. В этой статье будем использовать GitHub
. Заходим в веб-интерфейс и создаём там новый репозиторий. ⚠️ Важно: при создании не добавляем README, .gitignore и лицензию, так как мы хотим отправить свой локальный репозиторий. Иначе на GitHub уже будет коммит, и истории не совпадут.

Для доступа к репозиторию на GitHub нужно использовать ssh-ключ или токен, обычный пароль от GitHub не подойдёт. Я буду использовать ssh-ключ.
Проверяем, есть ли уже ssh-ключи:
ls -la ~/.ssh
Ищем файлы
id_ed25519иid_ed25519.pubилиid_rsaиid_rsa.pub.
Если ключей нет, то создаём:
ssh-keygen -t ed25519
Копируем публичный ключ:
cat ~/.ssh/id_ed25519.pub
Добавим ключ в настройки GitHub:
- На https://github.com/settings/keys нажмём New SSH key.
- В Title напишем комментарий, например название компьютера.
- В Key вставим скопированный публичный ключ (начинается с
ssh-ed25519илиssh-rsa). - И нажмём Add SSH key.
После настройки доступа подключим наш локальный репозиторий к удалённому:
git remote add origin git@github.com:<user>/<repository>.git
Удалённый репозиторий по традиции называют
origin, но можно выбрать и другое имя.
Если ошиблись в ссылке, есть возможность исправить:
git remote set-url origin <новая ссылка>
Чтобы посмотреть, к чему подключен локальный репозиторий:
git remote -v
После подключения можем отправить файлы на сервер:
git push -u origin main
При первом пуше удобно использовать
-u, чтобы Git запомнил, что локальная веткаmainотслеживает удалённую веткуorigin/main. После этого можно просто делатьgit pushиgit pull.
В интерфейсе GitHub убедимся, что репозиторий наполнился:

Кстати, теги не отправляются на сервер по умолчанию, их нужно отправлять явно:
git push --tags origin
Теперь мы можем на другом компьютере клонировать этот репозиторий. Но предварительно на том компьютере тоже нужно создать и добавить ssh-ключ на GitHub (делается аналогично, как было показано выше).
Итак, на другом компьютере, после настройки доступа по ssh-ключу, клонируем репозиторий:
git clone git@github.com:<user>/my-project.git
cd my-project/
Изменим файл на этом компьютере:
nano nginx-demo.conf # поменяем порт на 8088
И запушим изменения:
git add nginx-demo.conf
git commit -m "порт 8088"
git push
В веб-интерфейсе видим, что последний коммит сделал petya (такое имя я дал пользователю на втором компьютере):

Вернёмся на первый компьютер и выполним:
git fetch # скачали коммиты без применения
git status # смотрим, есть ли изменения
Текущая ветка: main
Ветка отстает от «origin/main» на 1 коммит и может быть быстро перемотана.
(используйте «git pull», чтобы обновить вашу локальную ветку)
нечего коммитить, нет изменений в рабочем каталоге
Посмотрим историю (опция --all покажет историю удалённого репозитория тоже, если перед этим мы делали fetch):
git log --oneline --all
16e996e (origin/main) порт 8088
a0099a1 (HEAD -> main, tag: v1.0.0) Поправили README
4428f03 порт 9090
4ccfa36 Добавили README
f867913 Первый коммит
То есть локально мы на a0099a1, а на сервере 16e996e.
Чтобы увидеть изменения:
git diff a0099a1 16e996e
# или
git diff HEAD origin/main # мне этот вариант больше нравится
diff --git a/nginx-demo.conf b/nginx-demo.conf
index d42dba6..d57abe1 100644
--- a/nginx-demo.conf
+++ b/nginx-demo.conf
@@ -1,5 +1,5 @@
server {
- listen 9090;
+ listen 8088;
server_name _;
Видим, что строка
listen 9090;заменена наlisten 8088;. Хотя это и было понятно по комментарию, важно уметь смотреть различия с помощьюdiff.
Кроме diff, можем использовать show, чтобы посмотреть, что изменилось в коммите:
git show 16e996e
commit 16e996e6799cfb999906c2681731325e2046a2d8 (origin/main)
Author: petya <petya@yandex.ru>
Date: Wed Oct 14 11:56:02 2026 +0300
порт 8088
diff --git a/nginx-demo.conf b/nginx-demo.conf
index d42dba6..d57abe1 100644
--- a/nginx-demo.conf
+++ b/nginx-demo.conf
@@ -1,5 +1,5 @@
server {
- listen 9090;
+ listen 8088;
server_name _;
location / {
После проверок объединим локальную ветку с удалённой:
git pull
Обновление a0099a1..16e996e
Fast-forward
nginx-demo.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Опять происходит Fast-forward — это обычная перемотка вперёд, без создания нового коммита. Это возможно, если ветки не разошлись.
Исправление ошибок
Изменили файл, нужно вернуть как было
Меняем файл:
nano nginx-demo.conf # меняем порт на 8888
Смотрим разницу с индексом и восстанавливаем:
git diff nginx-demo.conf
git restore nginx-demo.conf
⚠️ Внимание! Файл вернётся к последней сохранённой версии, а все ваши незакоммиченные правки в нём пропадут безвозвратно.
Добавили файл в индекс, но передумали
Меняем файл и помещаем его в индекс:
nano nginx-demo.conf # меняем порт на 8888
git add nginx-demo.conf
Здесь почти всё то же самое, но с опцией --staged.
git diff --staged nginx-demo.conf # разница файла в индексе и в коммите
git restore --staged nginx-demo.conf # убирает файл из индекса, правки остаются в папке
git restore nginx-demo.conf # ⚠️восстанавливаем файл из индекса
Ошиблись в последнем коммите (push не выполняли)
Тот же случай, но уже и в индекс добавили, и коммит сделали:
nano nginx-demo.conf # меняем порт на 8888, а хотели на 9999
git add nginx-demo.conf
git commit -m "порт 9999" # комментарий верный, а внутри 8888
Заметили ошибку, правим файл:
nano nginx-demo.conf # исправили на 9999
git add nginx-demo.conf # важно добавить в индекс
Если ещё не выполняли push, можем заменить предыдущий коммит следующим:
git commit --amend -m "порт 9999" # этот коммит заменит предыдущий
⚠️ Не используйте
--amendдля коммитов, которые уже отправлены на сервер (push) и которыми могли воспользоваться другие: вы переписываете историю. Это исправить уже будет сложно.
git log --oneline
f52c822 (HEAD -> main) порт 9999
d129d3a (origin/main) порт 8088
d4fbd10 (tag: v1.0.0) Поправили README
Видим, что у нас остался только один коммит
порт 9999. Предыдущий (порт 8888) был переписан.
Нужно отменить коммит, который уже на сервере
Ещё раз исправим файл, добавим его в индекс, сделаем коммит и запушим в GitHub.
nano nginx-demo.conf # порт 1111
git add nginx-demo.conf
git commit -m "порт 1111"
git push
И нужно откатиться:
git log --oneline # смотрим хеши
021969f (HEAD -> main, origin/main) порт 1111
f52c822 порт 9999
d129d3a порт 8088
d4fbd10 (tag: v1.0.0) Поправили README
Отменяем последний коммит:
git revert 021969f
# или
git revert HEAD
Git откроет текстовый редактор, где можно изменить стандартное сообщение Revert на любое другое.
revertне удаляет коммит, а создаёт новый коммит, который отменяет изменения старого. История остаётся целой, поэтому это безопасный способ откатывать то, что уже отправлено на сервер.
После этого запушим:
git push origin
В интерфейсе GitHub:

Слово Revert означает, что данный коммит откатили. В файле сейчас порт 9999.
Срочно нужно переключиться, а работа не закончена
Делаем новую ветку и сразу переключаемся на неё:
git switch -c fix/login
Меняем конфиг и делаем коммит:
nano nginx-demo.conf # меняем порт на 80
git add nginx-demo.conf
git commit -m "login: port 80"
Возвращаемся в main, меняем тот же файл и коммитим:
git switch main
nano nginx-demo.conf # меняем имя сервера на example.com
git add nginx-demo.conf
git commit -m "main: server_name example.org"
Теперь nginx-demo.conf в коммитах main и fix/login различается.
Переключаемся снова на fix/login, правим файл, но не коммитим (работа ещё не закончена).
git switch fix/login
nano nginx-demo.conf # добавим комментарий: # WIP: location /login
И срочно нужно переключиться на main:
git switch main
error: Ваши локальные изменения в указанных файлах будут перезаписаны при переключении на состояние:
nginx-demo.conf
Сделайте коммит или спрячьте ваши изменения перед переключением веток.
Прерываю
Git отказывает, только если файл различается между ветками. Если файл одинаковый, правки молча переедут на новую ветку.
Чтобы решить вопрос, нужно спрятать изменения с помощью stash, и это позволит переключиться:
git stash -m "fix/login: WIP location /login"
После этого файл nginx-demo.conf вернётся к закоммиченным изменениям.
Посмотрим спрятанное:
git stash list
stash@{0}: On fix/login: fix/login: WIP location /login
Переключимся на main и сделаем исправления:
git switch main
nano README.md # добавим любую строчку
git add README.md
git commit -m "Изменили README"
Возвращаемся на fix/login:
git switch fix/login
git stash pop # достаём спрятанное
git add nginx-demo.conf
git commit -m "Добавили Login"
stashпо умолчанию не прячет новые (untracked) файлы. Для них добавляйте ключ-u.
При слиянии происходит конфликт
После проделанного выше можем попытаться слить ветки, но получим конфликт:
git switch main
git merge fix/login
Автослияние nginx-demo.conf
КОНФЛИКТ (содержимое): Конфликт слияния в nginx-demo.conf
Сбой автоматического слияния; исправьте конфликты, затем зафиксируйте результат.
Смотрим историю всех веток:
git log --oneline --graph --all
* 111df62 (fix/login) Добавили Login
* 32ab733 login: port 80
| * 58424af (HEAD -> main) Изменили README
| * 20e761f main: server_name example.org
|/
* 4af1ab5 (origin/main) Revert "порт 1111"
Ветки разошлись от общего предка 4af1ab5, дальше у каждой своя история:
4af1ab5 ← общий предок (merge base)
├── main: 20e761f → 58424af (HEAD)
└── fix/login: 32ab733 → 111df62
Конфликт произошёл, так как мы в разных ветках правили один и тот же файл (nginx-demo.conf) и затронули соседние строки. Git при слиянии сравнивает три версии: общего предка, текущую ветку и сливаемую (трёхстороннее слияние). Именно поэтому важно знать, где ветки разошлись.
Чтобы найти, в каком файле есть расхождение:
git diff --name-only main...fix/login # что изменила fix/login (nginx-demo.conf)
git diff --name-only fix/login...main # что изменил main (README.md и nginx-demo.conf)
Из вывода понятно, что виновник
nginx-demo.conf— именно его правили в обеих ветках.
Смотрим, что конкретно изменено в ветке fix/login:
git diff main...fix/login -- nginx-demo.conf
diff --git a/nginx-demo.conf b/nginx-demo.conf
index c4ec436..a656ddc 100644
--- a/nginx-demo.conf
+++ b/nginx-demo.conf
@@ -1,8 +1,9 @@
server {
- listen 9999;
+ listen 80;
server_name _;
location / {
return 200 "nginx git demo\n";
}
}
+# WIP: location /login
Поменяли порт на 80, добавили комментарий
WIP: location /login.
Смотрим, что конкретно изменено в ветке main:
git diff fix/login...main -- nginx-demo.conf
diff --git a/nginx-demo.conf b/nginx-demo.conf
index c4ec436..6a9bf42 100644
--- a/nginx-demo.conf
+++ b/nginx-demo.conf
@@ -1,6 +1,6 @@
server {
listen 9999;
- server_name _;
+ server_name example.com;
location / {
return 200 "nginx git demo\n";
Поменяли имя сервера на example.com.
Стоит отметить! В обеих командах выше Git берёт общего предка и сравнивает его с правой веткой. Левая ветка нужна только для поиска предка, её содержимое в сравнении не участвует. Результат читается так: «что изменила правая ветка с момента расхождения».
Git сливает построчно: изменения можно объединить автоматически, только если между ними есть хотя бы одна неизменённая строка. Здесь строки 2 и 3 идут подряд, такой «прослойки» нет. Git не может уверенно решить, что правки независимы, и отдаёт выбор человеку. Логически они независимы (порт и имя сервера не связаны), но Git этого не знает.
Команда, которую мы выполнили в самом начале (git merge fix/login), внесла изменения в файл nginx-demo.conf:
server {
<<<<<<< HEAD
listen 9999;
server_name example.com;
=======
listen 80;
server_name _;
>>>>>>> fix/login
location / {
return 200 "nginx git demo\n";
}
}
# WIP: location /login
Git вставляет маркеры конфликта в файл, чтобы показать, где у двух веток разные версии одного и того же участка:
<<<<<<< HEAD— начало конфликтного блока, ниже версия из текущей ветки.>>>>>>> fix/login— конец блока, выше версия из сливаемой ветки.
Нужно убрать три строки с маркерами (<<<<<<<, =======, >>>>>>>) и оставить итоговый вариант, например:
server {
listen 80;
server_name example.com;
location / {
return 200 "nginx git demo\n";
}
}
# WIP: location /login
После этого сразу:
git diff --check # проверим, что маркеров не осталось
git add nginx-demo.conf
git commit -m "port 80, server_name example.com"
Посмотрим на историю после слияния:
git log --oneline --graph --all
* 1030650 (HEAD -> main) port 80, server_name example.com
|\
| * 111df62 (fix/login) Добавили Login
| * 32ab733 login: port 80
* | 58424af Изменили README
* | 20e761f main: server_name example.org
|/
* 4af1ab5 (origin/main) Revert "порт 1111"
Коммит слияния —
1030650, у него два родителя:58424af(вершинаmain) и111df62(вершинаfix/login).HEAD -> mainстоит на коммите слияния, значит,mainтеперь содержит работу обеих веток.
После слияния можем удалить ветку:
git branch -d fix/login
Разошлись ветки main и origin/main
Как это обычно происходит:
- Мы клонировали репозиторий или были на последнем коммите.
- Локально сделали коммит (
git commit). - А на сервере тем временем появился другой коммит. Например:
- отредактировали файл прямо в веб-интерфейсе GitHub;
- или сделали коммит с другого компьютера;
- или кто-то другой запушил изменения.
Чтобы воспроизвести ситуацию, сделаем push с первого компьютера:
git push
Переключимся на другой компьютер и не будем делать git pull или git fetch, поэтому он ещё не знает, что на сервере уже есть другие коммиты. Исправим там файл и сделаем коммит.
nano nginx-demo.conf # поменяем порт на 90
git add nginx-demo.conf
git commit -m "порт 90"
Пробуем сделать push:
git push origin
! [rejected] main -> main (fetch first)
error: не удалось отправить некоторые ссылки ...
hint: 'git pull' before pushing again.
Смотрим, какие именно коммиты есть локально и на сервере:
git fetch
git log --oneline --graph --all
* b691e25 (HEAD -> main) порт 90
| * 1030650 (origin/main, origin/HEAD) port 80, server_name example.com
| |\
| | * 111df62 Добавили Login
| | * 32ab733 login: port 80
| * | 58424af Изменили README
| * | 20e761f main: server_name example.org
| |/
| * 4af1ab5 Revert "port 1111"
| * 021969f порт 1111
| * f52c822 порт 9999
|/
* d129d3a порт 8088
- Только локально:
b691e25 порт 90, он выходит изd129d3a. Аorigin/mainдо него не доходит. - Только на сервере:
1030650,58424af, и т.д. Они доходят доorigin/main, но локальныйmainих не содержит. - Общее для обеих сторон:
d129d3aи всё ниже.
Смотрим, чем локальные изменения отличаются от серверных:
git diff HEAD origin/main
diff --git a/README.md b/README.md
index ead0ef3..7551ee1 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
Мой тестовый проект.
Хранит файл настроек Nginx.
+Ведём доработку.
diff --git a/nginx-demo.conf b/nginx-demo.conf
index e8e8f70..772d028 100644
--- a/nginx-demo.conf
+++ b/nginx-demo.conf
@@ -1,8 +1,9 @@
server {
- listen 90;
- server_name _;
+ listen 80;
+ server_name example.com;
location / {
return 200 "nginx git demo\n";
}
}
+# WIP: location /login
На сервере в файл
README.mdдобавили строчкуВедём доработку., а в файлеnginx-demo.confпорт 80 и имя сервера example.com. Локально — порт 90, имя сервера не меняли.
Дальше уже решаем, где правки вернее. В обоих случаях сначала создадим резервную ветку:
git branch backup-main
- Если на сервере история верная, а локально вы совершили ошибочный коммит, то полностью переписываем локальную историю. ⚠️ Это разрушающая операция (все локальные коммиты, которых нет на сервере, будут удалены):
git reset --hard origin/main
git status
- Если хотим оставить локальное изменение поверх серверных:
git rebase origin/main # получаем конфликт
nano nginx-demo.conf # исправляем файл, удаляем маркеры конфликта
git add nginx-demo.conf
git rebase --continue # продолжаем rebase
git push
Кстати, если вы сделали
rebaseи запутались, можно вернуть всё как было, выполнивgit rebase --abort(пока не выполнили--continue).
Разберём команды подробнее:
git reset --hard origin/main— опасная команда. Локальный репозиторий и вся локальная история перезапишутся историей с сервера (origin/main).- Переводит указатель текущей ветки, например
main, на коммитorigin/main. - Приводит индекс к состоянию этого коммита.
- Приводит рабочий каталог к состоянию этого коммита.
- Переводит указатель текущей ветки, например
git rebase origin/main— не удаляет работу, но переписывает локальные коммиты. Используйте только для коммитов, которые ещё не отправлены на сервер. Локальные коммиты применяются поверхorigin/main.- Подтягивает базу до
origin/main. - Сверху применяет локальный коммит.
- Создаёт новый коммит с новым хешем.
- Подтягивает базу до
Я выберу первый вариант, то есть git reset. Но если мы пошли по второму пути, то на другом компьютере сделаем pull, чтобы синхронизировать репозитории на обоих компьютерах.
В конце концов у нас получилась такая история коммитов:
* bbd0211 (HEAD -> main, origin/main) Выполнили слияние с fix/login
|\
| * e711541 Добавили Login
| * 9252ca4 login: port 80
* | f0c3907 Изменили README
* | 2eb5256 main: server_name example.org
|/
* ed71d50 Revert "port 8888"
* 3dff828 port 8888
* 0d15e05 отменили изменения
* 16e996e changed port 8088
* a0099a1 (tag: v1.0.0) Поправили README
* 4428f03 Поменяли порт nginx на 9090
* 4ccfa36 Добавили README
* f867913 Первый коммит
Как начать заново
⚠️ Разрушающие команды: они стирают историю локально и на сервере. Выполняйте только в учебном репозитории.
Если захотите проиграть всё заново, можем вернуться к первому коммиту:
git branch -D backup-main # удаляем резервную ветку
git reset --hard <хеш первого коммита>
git tag -d v1.0.0 # удаляем тег локально
git push --force-with-lease # переписываем историю на сервере
git push origin --delete v1.0.0 # удаляем тег на сервере
# на втором компьютере
git fetch
git reset --hard origin/main
git tag -d v1.0.0
Итог
Мы разобрали основы Git:
- три зоны: рабочая папка, индекс, репозиторий;
add,commit,log,status,diff,show;- ветки,
merge, теги; - удалённый репозиторий:
clone,push,fetch,pull; - исправление ошибок:
restore,--amend,revert,stash; - конфликты слияния и разошедшаяся история.
Главное правило: то, что закоммичено, потерять сложно, поэтому перед разрушающими командами (reset --hard, push --force) делайте резервную ветку.
В следующей статье разберём командную работу и более сложные ошибки.