Разработка проекта с git. Если все идет по плану (Часть 1)

Предисловие

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

Цель — описать простейший процесс работы с git при разработке реального проекта (от создания репозитория до добавления в проект нового функционала), основные юзкейсы и проблемы, с которыми сталкивается начинающий/продолжающий разработчик (например, я) в своей повседневной работе.

Статья поделена на две части:

  • процесс разработки, в котором все идет по плану,
  • процесс разработки, в котором что-то пошло не так.

В первой части поговорим о разработке “по плану”.

Создание репозитория

Существует два основных подхода к созданию репозитория git: создание нового и клонирование (копирование) существующего.

Создание нового локального репозитория для нового проекта

Для начало работы над новым проектом необходимо создать для него папку, внутри которой будет находиться репозиторий git. И перейти в эту папку.

git init project
cd project

После выполнения первой из этих команд была создана папка проекта project, а внутри нее появилась скрытая подпапка .git — это и есть наш репозиторий, в котором храниться база данных репозитория (информация о всех файлах проекта, их версиях, истории изменений и т.д.) и основные настройки.

Совет: В самом начале разработки имеет смысл создать в корневой папке проекта файл .gitignore и указать в нем файлы/папки, которые git не должен отслеживать. В дальнейшем скорректировать этот файл будет тоже возможно, но могут потребоваться дополнительные действия в случае совместной работы с репозиторием нескольких разработчиков.

Создание нового локального репозитория для существующего проекта

Если у нас уже имеется проект с рядом файлов и мы хотим отдать его под контроль git — достаточно перейти в папку с этим проектом и выполнить команду:

git init

Если мы теперь воспользуемся командой git status то увидим, что все файлы проекта, находившиеся там на момент создания репозитория, выделены красным, т.е. они не добавлены в индекс. Надо их проиндексировать (добавить в отслеживаемые) и закоммитить (сохранить в базу данных репозитория). Подробнее об этом ниже в разделе Работа в ветке.

Создание нового удаленного репозитория

Git выполняет 2 основных функции:

  • осуществляет контроль версий проекта
  • организует совместную работу над проектом нескольких разработчиков

Если для выполнения первого пункта достаточно иметь локальный репозиторий, то для второго необходимо создание удаленного репозитория, который будет синхронизироваться с локальным. Создать его можно на одной из специальных площадок: GitHub, GitLab, BitBucket и др. Создаются они там обычно с помощью графического интерфейса. Например, для Github последовательность действий примерно такая:

  1. Кликаем на плюсик справа вверху экрана
  2. Выбираем New repository
  3. Задаем имя репозитория (оно может совпадать или не совпадать с именем локального репозитория, это значения не имеет)
  4. Кликаем Create repository.

Когда удаленный репозиторий создан, необходимо привязать его к локальному. Для этого в папке с локальным репозиторием выполняем команду:

git remote add origin https://github.com/my_user_name/remote_repo.git

origin — это короткое имя для обращений к удаленному репозиторию (чтобы не писать каждый раз его url-адрес).

url-адрес репозитория можно посмотреть на площадке, где он был создан (для github это кнопка Clone or download).

Убедиться, что текущий локальный репозиторий привязан к удаленному, можно посмотрев список всех удаленных репозиториев:

git remote

В простейшем случае к одному локальному репозиторию привязан один удаленный.
Чуть больше информации: полные url-адреса и их короткие имена, — можно увидеть, добавив флаг -v.

git remote -v

Создание нового локального репозитория путем клонирования существующего удаленного репозитория

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

Скопировать удаленный репозиторий можно так:

git clone https://github.com/my_user_name/remote_repo.git

Обратим внимание, что нет нужды отдельно создавать папку с именем проекта, т.к. при клонировании автоматически будет создана папка с именем удаленного репозитория (в примере выше — remote_repo). Если мы хотим, чтобы папка (репозиторий) на локальном компьютере имела другое имя, надо указать его при клонировании явно.

git clone https://github.com/my_user_name/remote_repo.git my_name_of_directory

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

Создание ветки

Создание новой ветки

По умолчанию удаленный репозиторий имеет имя origin, а главная ветка проекта называется master (это та ветка, в которой мы оказываемся сразу после создание/клонирования репозитория). Обычно ветка master оставляют “чистой”, в ней лежит всегда работающая версия проекта, а разработка нового функционала ведется в отдельных ветках.

Допустим, нам требуется разработать новую фичу. Создадим для нее новую локальную ветку.

git branch branch_name

И перейдем в эту новую ветку.

git checkout branch_name

Две вышеописанные команды для краткости можно объединить в одну.

git checkout -b branch_name

Копирование существующей ветки

Часто возникает ситуация, когда нужно не создавать новую ветку, а посмотреть/продолжить работу в уже существующей ветке. Например, когда над фичей работает несколько разработчиков и один из них уже создал ветку локально и отправил ее в удаленный репозиторий. Или когда нужно выполнить код-ревью или просто что-то посмотреть/вспомнить в разрабатываемой когда-то давно (и не нами) фиче. В общем, ветка уже существует и хранится в удаленном репозитории.
Перед тем как ее скопировать, надо получить все актуальные изменения из удаленного репозитория в локальный.

git fetch origin

Помним, что origin — это просто короткое имя для нашего удаленного репозитория.
Теперь наш локальный репозиторий точно знает о всех удаленных ветках, можно создать локальную ветку, скопировав ее из уже существующей удаленной.

git checkout -b his_branch origin/his_branch

Как видим — эта команда очень похожа на команду для создания обычной локальной ветки, но в ней появляется дополнительный аргумент origin/his_branch, указывающий, откуда надо копировать ветку.

Работа в ветке

При работе над новой фичей для проекта мы создаем/изменяем/удаляем файлы. Все эти изменения отображаются в git. Когда мы закончили очередной этап работы, нам нужно зафиксировать новое состояние файлов. Для этого сначала надо проиндексировать изменения в конкретном файле (добавить их в отслеживаемые).

git add name_of_file

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

git add .

Либо проиндексировать вообще все изменения во всем репозитории.

git add -A или git add ——all

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

Далее необходимо зафиксировать изменения (сделать коммит).

git commit

При выполнении этой команды запустится текстовый редактор (установленный по умолчанию), в котором можно задать подробное описание сделанных в коммите изменений. При сохранении изменений коммит будет совершен.

Если описание коммита не очень длинное, то можно задать его в одну строчку. По факту, именно так обычно и делается.

git commit -m “Description of what was made in this commit”

Две вышеописанные команды (индексация и коммит) можно выполнить в одну строчку — поможет добавление флага -a у команды commit.

git commit -am “Text”

Но она сработает только если редактировались/удалялись файлы, которые уже отслеживались в git. Если мы создаем новые файлы — их придется добавлять отдельно с помощью git add + git commit).

Обратите внимание: В данной команде важна последовательность флагов. Так сработает: -am или -a -m. А так нет: -ma или -m -a.

Синхронизация локального и удаленного репозиториев

С одной стороны, синхронизировать каждое изменение (особенно, неоднозначное, непроверенное) не стоит, т.к. пока изменения хранятся лишь у нас локально — мы можем делать с ними что угодно — изменять/отменять и т.д., это не повлияет на работу других разработчиков. С другой стороны, частая синхронизация позволит другим разработчикам, работающим в той же ветки, что и мы, получать всегда актуальное состоянии файлов и своевременно решать конфликты изменений, неизменно возникающие при работе нескольких людей над одними и теми же файлами (лучше решать много мелких конфликтов, чем затянуть с синхронизацией и разбираться в одном большом конфликте).

В любом случае, синхронизация — процесс регулярный и обыденный.

Итак, мы закончили один из этапов разработки новой фичи, добавили изменения в индекс и закоммитили их и хотим отправить их в удаленный репозиторий. Перед этим необходимо получить последние изменения с удаленного репозитория (ведь пока мы разрабатывали свою фичу кто-то другой на своем локальном компьютере мог тоже изменить какие-то файлы и отправить их в общий удаленный репозиторий).

На самом деле, если в удаленном репозитории имеются какие-то изменения, которых нет у нас — у нас и не получится просто так (без специальных команд) отправить в него свои изменения.
Тем не менее, попробуем получить изменения.

git pull origin branch_name

origin — репозиторий из которого берем изменения
branch_name — ветка из которой берем изменения

На этом этапе возможно появление конфликтов (если новые изменения в удаленном репозитории противоречат нашим локальным изменениям). О разрешении конфликтов погорим позже. Пока предположим, что конфликтов нет. В этом случае можно смело отправлять свои локальные изменения в удаленный репозиторий.

git push origin branch-name

Если нам нужно отправить изменения текущей ветки, то вместо ее полного имени можно указать HEAD.

git push origin HEAD

Это команда делает то же самое, но может помочь избежать проблем, если мы случайно опечатаемся в имени ветки.

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

pit push -u origin branch_name

либо

pit push -u origin HEAD

Тогда в дальнейшем находясь в конкретной ветке можно использовать просто git pull и git push (без указания репозитория и имени ветки).
Привязать ветку можно и отдельной командой (ничего явно не отправляя и не получая с удаленного репозитория).

git branch --track origin/branch_name

Эту команду можно использовать только для вновь созданной локальной ветки (когда удаленной ветки еще не существует).
Поэтому более универсальный вариант связывания веток, работающий всегда, выглядит так.

git branch -u origin/branch_name branch_name

Разница этих двух команд в том, что первую можно использовать только для вновь созданной локальной ветки (когда удаленной ветки еще не существует), а вторую — в любой момент.

Если ветка создавалась путем копирования с удаленного репозитория, то отдельно привязывать ее не нужно. Она привязана автоматически и, соответственно, короткий вариант команд git pull и git push доступен сразу.

Слияние веток

После того, как разработка фичи полностью завершена и протестирована, нужно добавить (влить) новые изменения в проект, т.е. слить ветку, в которой велась разработка (branch_name) в ветку с основным проектом (master).

Не забудем убедиться, что мы работаем именно с актуальными состояниями веток master и branch_name. Перейдем в каждую из них и получим последни е изменения.

git checkout branch_name && git pull
git checkout master && git pull

После этого переходим в ветку, В КОТОРУЮ будут вливаться изменения. В нашем случае — это ветка master и мы уже в ней. Bливаем в нее ветку с нашей фичей.

git merge branch_name

Здесь также возможно появление конфликтов как и при “стягивании” изменений из удаленного репозитория командой git pull. И также как и в прошлый раз мы пока предположим, что конфликтов нет и фича успешно добавлена в основную ветку проекта.

На практике, процесс объединения основной ветки и ветки разработки выглядит несколько сложнее.
Например, предварительно сливают ветку master в ветку разработки новой фичи (branch_name) и только после решения всех конфликтов, сливают назад.

Либо выделяют отдельную ветку для релиза, в которую сливают основную ветку с проектом (master) и ветку с новыми фичами (branch_name), разрешают в ней конфликты и сливают обратно в master.

Все это для того, чтобы держать ветку master в рабочем состоянии и не засорять ее «мусорными» коммитами, решающими конфликты.

Но что бы и куда бы мы не сливали (мерджили), последовательность действия остается одна и та же:

  1. Переходим в ветку, в которую будем добавлять изменения.
  2. Выполняем команду  git merge, указываю ветку, откуда берем изменения
  3. Решаем конфликты, создаем коммит, описывающий решение конфликта.

Итак, мы рассмотрели процесс разработки по плану. В второй части поговорим о том, что делать, если все пошло не так.

Метки:


«