git git_clone commit git_status git_add git_commit git_push git_config

Шпаргалка

Вся практика этого шага умещается в одиннадцать команд:

КомандаЧто делает
git initпревратить папку в репозиторий
git statusпоказать, что сейчас происходит
git add файлотобрать изменения для коммита
git commit -m "..."сохранить снимок с сообщением
git log --onelineистория коммитов в одну строку
git diffчто изменилось с последнего коммита
git branchсписок веток
git switch имяпереключиться на ветку
git switch -c имясоздать ветку и переключиться
git merge имявлить ветку в текущую
git branch -d имяудалить влитую ветку
git cloneсклонировать репозиторий откуда-то
git rm —cachedУбрать файл из отслеживания, не удаляя его с диска
git cloneСкопировать удаленный репозиторий к себе целиком
git pushОтправить свои коммиты на удаленный репозиторий
git pullЗабрать с удаленного репозитория чужие коммиты

Создание git

git_clone

  1. Открыть страницу вашего репозитория
  2. Скопируйте ссылку для клонирования репозитория
  3. Открыть консоль GIT (файл git-bash.exe) и выполните команду клонирования репозитория:
# Вместо моей ссылки укажите свою 
git clone http://10.0.1.22:3000/Chopov_I_A/Linux_Chopov.git
  • В первый раз обычно нужно
git config --global user.name "John Doe" 
git config --global user.email johndoe@example.com

Репозиторий за одну команду

mkdir myserver
cd myserver
git init
 
git status
	On branch main
	No commits yet
	nothing to commit

Когда непонятно, что происходит с репозиторием, первым делом смотрят git status. Это безопасная команда уровня ls, ее можно дергать сколько угодно.


Первый коммит

Создадим файл конфига:

echo "listen 80;" > nginx.conf
git status
	Untracked files:
	  nginx.conf

Git заметил новый файл, но пометил его как untracked: файл лежит в папке, но за его историей Git пока не следит. Чтобы файл попал в историю, нужен коммит, а делается он в два шага:

git add nginx.conf
git commit -m "Базовый конфиг nginx"

История уже существует, посмотрим на нее:

git log —oneline

a3f8c21 (HEAD -> main) Базовый конфиг nginx

Набор букв и цифр слева – идентификатор коммита, по нему к этому снимку можно будет вернуться когда угодно.


Цикл изменений

Теперь главный ритм работы с Git. Поменяем конфиг:

echo "listen 8080;" > nginx.conf
git status        # подскажет: modified, nginx.conf изменен
git diff          # покажет, что именно изменилось
	- listen 80;
	+ listen 8080;

Это тот самый дифф, что мы видели раньше, только теперь он ваш: строка с минусом ушла, строка с плюсом пришла. Привычка смотреть git diff перед коммитом спасает от случайных изменений, которые вы не собирались сохранять.

Дальше знакомые два шага:

git add nginx.conf
git commit -m "Перенес сайт на порт 8080"
git log --oneline
	9b2e110 (HEAD -> main) Перенес сайт на порт 8080
	a3f8c21 Базовый конфиг nginx

Ветка без риска для рабочей версии

Сейчас оба коммита живут в ветке main. Допустим, мы хотим попробовать перевести сайт на защищенное соединение, но не уверены, что получится, и трогать рабочий конфиг не хотим. Это ровно та ситуация, под которую придуманы ветки.

Важная деталь: ветка – это не копия директории и не отдельная версия проекта на диске, а легкая метка, указатель на коммит, поэтому ветки создаются мгновенно и ничего не весят. Создадим ветку и сразу в нее переключимся:

git switch -c ssl-experiment

Команда git switch переключает ветки, а флаг -c заодно создает новую. Проверить, где вы находитесь, можно через git branch: текущая ветка помечена звездочкой. Теперь экспериментируем:

echo "listen 443 ssl;" > nginx.conf
git add nginx.conf
git commit -m "Включил SSL"

Этот коммит ушел в ветку ssl-experiment, а main остался стоять на месте:

И самое наглядное в ветках. Вернитесь в main и загляните в файл:

git switch main
cat nginx.conf
	listen 8080;

Эксперимента в файле нет. Git физически подменяет содержимое рабочей папки под ту ветку, в которой вы находитесь: в main лежит версия main, а если переключиться обратно в ssl-experiment, SSL вернется. Одна папка, несколько параллельных состояний проекта.


Мерж вливает результат

Допустим, эксперимент удался, и SSL-конфиг должен стать основным. Слияние всегда делается стоя в той ветке, в которую вливают изменения, поэтому сначала переключаемся в main:

git switch main
git merge ssl-experiment

Дальше возможны два сценария. Если main с момента создания ветки не менялся (наш случай), слияние проходит незаметно: Git просто передвигает main вперед, на последний коммит ветки, и никаких новых коммитов не появляется. Если же за время эксперимента в main успели появиться свои коммиты, например от коллеги, Git создаст отдельный коммит слияния, который соединит обе линии истории в одну.

Влитая ветка больше не нужна, удалим ее:

git branch -d ssl-experiment


Конфликты, когда Git просит помощи

Один случай Git не может разрешить сам: обе ветки изменили одну и ту же строку файла по-разному. Скажем, в main кто-то поставил listen 8080;, а в вашей ветке та же строка стала listen 443 ssl;. Какой вариант правильный, знаете только вы, поэтому Git останавливает слияние и вставляет прямо в файл маркеры с обоими вариантами:

<<<<<<< HEAD
listen 8080;
=======
listen 443 ssl;
>>>>>>> ssl-experiment

Сверху версия текущей ветки, снизу версия вливаемой. Разрешается конфликт руками: открываете файл, оставляете правильный текст, убираете маркеры, затем git add и git commit, и слияние завершено. Если что-то пошло не так, git merge --abort отменит слияние целиком и вернет все как было.

Выглядит конфликт страшнее, чем есть на деле: Git ничего не сломал и ничего не потерял, он показал два варианта и ждет вашего решения.


.gitignore: что нельзя коммитить

Вернемся к git status . В ранее примере репозиторий был стерильным: одна папка, один файл nginx.conf. В настоящем проекте та же команда сразу после git init покажет совсем другую картину:

Untracked files:
  app.py
  .env
  node_modules/
  app.log
  .DS_Store

Git честно предлагает взять под контроль все, что нашел в папке. Но в историю проекта должно попасть далеко не все из этого списка. app.py – да, это код. А вот .env с паролями, гигабайтная папка node_modules и лог приложения в истории не нужны, а кое-что из этого там еще и опасно. Разберемся, что отсекать и как сказать об этом Git.


Что не должно попадать в Git

Лишнее в репозитории делится на несколько понятных групп.

  1. Первое и самое важное – это секреты. Пароли от баз данных, токены доступа, приватные ключи, файлы вроде .env с настройками подключения.

  2. Вторая группа – то, что генерируется из кода и восстанавливается командой. Папка зависимостей node_modules, каталоги сборки build и dist, кэши вроде __pycache__. Эти файлы не пишут руками, их создает инструмент по описанию проекта. Хранить их в Git бессмысленно: они весят много, постоянно меняются и в любой момент собираются заново.

  3. Третья группа – мусор и временные файлы. Логи (app.log, *.log), временные файлы, дампы. Они меняются каждую секунду работы программы, и их история не несет никакого смысла.

  4. И последнее – личные и системные файлы, к проекту отношения не имеющие. .DS_Store создает Finder на macOS, Thumbs.db – проводник Windows, а папки .idea и .vscode хранят настройки редактора конкретного разработчика. Коллеге они не нужны, а в истории только шумят.


Файл .gitignore

Чтобы Git перестал замечать ненужное, в репозиторий кладут файл с именем .gitignore. Это обычный текстовый файл со списком того, что Git должен игнорировать: такие файлы не показываются в git status, не попадают под git add и, значит, не уходят в коммиты.

Обратите внимание на последнюю строку: сам .gitignore в историю как раз попадает. Он часть проекта, и правила должны быть одинаковыми у всей команды, поэтому его коммитят наравне с кодом.


Как пишут правила

Внутри .gitignore идет по одному правилу на строку. Базового набора хватает почти всегда:

# строки с решеткой — комментарии
 
.env              # конкретный файл по имени
*.log             # любой файл с расширением .log
node_modules/     # целая директория (косая черта в конце)
secrets/          # и эта тоже

Имя файла означает именно этот файл. Звездочка * заменяет любую часть имени, поэтому *.log накрывает все логи разом. Косая черта в конце говорит, что речь о директории со всем содержимым. Этих приемов достаточно, чтобы описать почти любой проект, а готовые шаблоны .gitignore под конкретный язык легко найти и взять за основу.


Ловушка, которую .gitignore не закрывает

.gitignore действует только на файлы, которые Git еще не отслеживает. Если файл уже попал хотя бы в один коммит, добавление его в .gitignore не сделает ничего: Git продолжит следить за ним как ни в чем не бывало.

Чтобы перестать отслеживать уже закоммиченный файл, его убирают из-под контроля отдельно:

git rm —cached .env # убрать из отслеживания, сам файл на диске оставить

После этого его еще нужно занести в .gitignore, иначе Git снова предложит его добавить. Но есть нюанс важнее: из прошлых коммитов файл при этом никуда не девается.


Секреты в истории остаются навсегда

Git тем и ценен, что ничего не забывает, и в случае с секретами это оборачивается против вас. Если пароль или ключ хоть раз попал в коммит, он остается в истории навсегда. Удалить файл новым коммитом недостаточно: старый коммит с секретом по-прежнему лежит в репозитории, и из него все можно достать.

Для публичных репозиториев это не теория. Открытые репозитории постоянно сканируют боты, и выложенный по ошибке ключ, например от облака AWS, находят и пускают в дело буквально за минуты, а со счетом за чужие траты потом разбирается владелец. GitHub даже встроил защиту, которая умеет распознавать секреты и блокировать такой коммит на отправке, но полагаться стоит не на нее, а на привычку не коммитить лишнее.

Поэтому если секрет все-таки утек в репозиторий, чистки истории мало. Единственное надежное решение – отозвать сам секрет: сменить пароль, перевыпустить ключ, сделать утекшее значение недействительным. Тогда даже найденная в истории копия уже ничего не открывает.


Как держать секреты вне репозитория

Сложилась простая практика. Настоящие секреты держат в файле .env, который занесен в .gitignore и у каждого разработчика свой. А рядом коммитят его шаблон, обычно .env.example, тот же список настроек, но с пустыми значениями:

# .env  — в .gitignore, с настоящими значениями, в Git не идет
DB_PASSWORD=s3cr3t-real-password
 
# .env.example  — коммитится, это шаблон без секретов
DB_PASSWORD=

Так новый человек в проекте видит, какие настройки нужны, но не получает чужих паролей, а сами секреты в историю не попадают. В реальной доставке секреты вообще не лежат в файлах рядом с кодом, а подставляются отдельно, переменными окружения и хранилищами секретов.