Введение в Git. Учимся контролировать версии исходников
Рубрика: Журнал Хакер -> Статьи
Метки: git | svn | контроль версий | программирование
Просмотров: 17400
Почему архивы — это плохо?
Я всегда удивлялся, когда видел, как хорошие программисты перед внесением изменений в файл проекта тупо архивируют его, присваивая в качестве имени что-то вроде cart.php.150220131500.zip. Нетрудно догадаться, что сакральный смысл этой конструкции подразумевает имя сценария (cart.php) и дату создания архива. Я такое встречаю достаточно часто и скажу по большому секрету, что на заре своей карьеры поступал точно так же. Все изменилось, когда я умудрился потерять кучу рабочего кода, банально перепутав архив.
Досадная ошибка молодости заставила меня обратить внимание на специализированные системы управления версиями. Буду откровенным — переход дался непросто. Поначалу трудно заставить себя выполнять «лишние» действия… лишь спустя время (обычно период адаптации длится в районе месяца) начинаешь удивляться: «Как я раньше работал по-другому?»
Недолгая дружба с SVN
Не подумай, что мы ходим сказать про SVN что-то плохое. Просто лично мне не понравилось, что для работы над проектом я должен иметь постоянное соединение с SVN-сервером. Этот минус особенно проявляется при отсутствии постоянного рабочего места. В таких случаях сервер обязан быть доступен извне, а мобильное рабочее место подразумевает хорошее соединение с инетом. Нет качественного коннекта — здравствуйте, тормоза при очередном получении изменений.
Вторым существенным минусом для меня стала гора служебного мусора. Меня дико бесило, что SVN пихает в каждую директорию проекта скрытую папку с кучей служебных файлов. В последних версиях эту проблему вроде как решили, но этот путь слегка затянулся.
Знакомьтесь, это Git!
Нередко разрушения и бесконечные споры провоцируют рождение поистине удивительных вещей. Случилось это и с Git. Все начиналось довольно просто и, конечно же, не без участия законодателя моды королевства Open Source — Linux. С самого первого релиза (1991 год) разработка кода ядра выполнялась по старинке: путем приема патчей от «населения» и архивирования предыдущих версий. Объемы кода ядра, внушающее ужас число разработчиков и современные тенденции не могли не внести корректив в этот процесс. В 2002 году проект разработки ядра перекочевал на проприетарную распределенную систему управления версиями BitKeeper от BitMover Inc.
К счастью, в сотрудничестве с компанией BitMover Inc. произошел разлад, вынудивший компанию забрать право бесплатного использования их продукта. Этот неприятный инцидент подстегнул Линуса Торвальдса с компанией девелоперов на создание собственной системы управления версиями. Ребята хотели разработать надежное решение, обладающее высокой скоростью работы и упрощающее командную разработку. В 2005 году все эти хотелки вылились в первую версию Git.
Git с самого начала пришелся по душе разработчикам. Буквально сразу его взяли на вооружение крупные open source проекты (Drupal, Linux). Однако главный всплеск популярности произошел после запуска специализированного хостинга Git-проектов - GitHub. Сегодня львиная доля опенсорсных проектов размещаются именно там.
Как работает Git
В целом Git представляется простым продуктом, но первое знакомство с ним зачастую проходит не совсем гладко. Причина заключается в непонимании основополагающих принципов работы. Первым делом нужно усвоить главное правило: не нужно пытаться подгонять термины и знания, полученные при работе с другими подобными продуктами.
Идеология Git в корне отличается от SVN (как пример), и все попытки натянуть одну теорию на другую, скорее всего, завершатся путаницей. Для начала давай вспомним, как данные хранятся в системах типа SVN. Если отбросить умные слова из толстых книг, то можно сказать, что система просто хранит файлы и цепочку изменений к ним. Схематично это выглядит так:
ФАЙЛ -> Изменение № 1 -> Изменение № 2 -> Изменение № 3
В Git все происходит совершенно иначе. Он не пытается хранить лишь изменения отдельного файла. Вместо этого он делает слепки (копии) абсолютно всех входящих в проект файлов на текущий момент. Если файл не изменился, то вместо копии создастся ссылка на предыдущую копию (или оригинал). Таким образом, разработчик всегда работает с одним набором файлов, просто их содержимое меняется от условий. Очередной слепок создается на этапе фиксации изменений. На схеме это отображается так:
ВЕРСИЯ 1 Версия 2
Файл № 1 Файл № 1 (А) (файл изменился)
Файл № 2 Файл № 2 (просто ссылка, файл не менялся)
Файл № 3 Файл № 3 (A) (файл изменился)
Чем Git лучше других?
Одно из самых важных преимуществ Git — децентрализованность. Смотри сам, Git, в отличие от альтернативных систем управления версиями, не требуется постоянного соединения с сервером. Все файлы проекта хранятся локально (!), поэтому ты можешь работать над кодом в любом месте и в любое время.
Отсутствие необходимости коннекта с сервером избавляет от различных сетевых тормозов. У меня нередко возникали ситуации, когда требовалось подключиться к рабочему SVN-серверу из командировки посредством GPRS-соединения. Это была настоящая каторга!
Наличие полных локальных копий проекта сразу решает проблему с резервированием данных. Каждый разработчик обладает полной копией всех файлов, и при необходимости из нее можно поднять оригинал. Падение сервера с удаленным репозиторием превращается в не самую большую проблему.
Выделять сильные стороны Git можно бесконечно долго, но не упомянуть про киллер-фичу однозначно нельзя. Я говорю про ветки. Это самая сильная функция, и на первых порах от нее реально сносит голову. Никто не запрещает создавать в своем репозитории кучу веток и постоянно переключаться между ними или объединять несколько веток в одну!
Немного теории
Одна из главных сущностей мира Git — различные состояния файлов. Каждый файл, входящий в состав проекта, может находиться в одном из трех состояний:
Отдельного разговора заслуживает структура каждого проекта, работающего под управлением Git. Условно ее можно разделить на три составляющие: служебная директория (git directory), рабочая директория (working directory) и область подготовленных файлов (staging area). Служебная директория git представляет собой хранилище метаданных и базу данных всех объектов проекта. Ты можешь увидеть ее, если заглянешь в любой клонированный или созданный тобой репозиторий. Там ты обязательно увидишь скрытую папку с именем «.git».
Рабочая директория — это папка, в которой хранятся файлы твоего проекта. Все ее содержимое представляет собой копию определенной версии проекта. Под областью подготовленных файлов подразумевается обычный файл, расположенный в служебной директории и содержащий информацию об изменениях, которые войдут в следующий коммит.
От слов к делу
Втиснуть всю необходимую теорию в одну часть статьи просто нереально, поэтому предлагаю приступить к практике и рассматривать возникшие вопросы по ходу дела. Сразу хочу сказать, что мы не будем затрагивать поднятие локального Git-хостинга (это ты сможешь сделать сам, прочитав во врезке про дистрибутив GitLite). Лучше потренируемся на сервисе Bitbucket. Почему именно на нем, а не на великом GitHub?
Во-первых, Bitbucket ничуть не уступает ему по возможностям. А во-вторых, он позволяет совершенно бесплатно создавать приватные репозитории для небольших команд. За эту же возможность на GitHub придется платить 200 рублей в месяц.
Готовим инструменты
Мы договорились, что в качестве хостинга своих проектов будем использовать Bitbucket, поэтому потрудись сразу создать в нем учетную запись. Как только твой аккаунт будет готов, сразу беги на goo.gl и сливай последнюю версию дистрибутива Git. По указанной ссылке лежат дистрибутивы под Windows. Если твоя родная площадка — Linux, то установить актуальную версию дистрибутива ты сможешь при помощи apt-get.
Настройка Git
После установки Git необходимо произвести пару настроек — установить свое имя (именно оно будет отображаться в коммитах) и e-mail. Для ввода опций запусти консоль Git Bash и выполни следующие команды:
$ git config --global user.name “spider_net” # Задаем имя пользователя. Отображается в коммитах
$ git config --global user.email “antonov.igor.khv@gmail.com” # Задаем e-mail пользователя
Локальный репозиторий
Создать репозиторий мы можем одним из двух способов: клонировать существующий или создать новый. Первый способ гораздо проще, поэтому начнем с него.
Для начала создай где-нибудь у себя на винте директорию, в которую будем помещать все git-проекты. Я создал такую на диске E: и обозвал ее «test-git». Открой окно консоли (Git Bash), если успел его уже закрыть, и установи в качестве текущей директории папку, которую только что создал:
$ cd E:/test-git
С этим разобрались. Теперь зайди на GitHub (не из консоли, а с помощью браузера) и выбери абсолютно любой проект, на котором мы потренируемся в создании клонов. Я не стал мучить себя выбором и в качестве первого подопытного выбрал Drupal (https://github.com/drupal/drupal). На странице с каждым открытым репозиторием имеется строка ввода, в которой указан полный путь к репозиторию. В случае с Drupal это будет https://github.com/drupal/drupal.git. Скопируй его в буфер обмена, а затем возвращайся в консоль и вбей в нее:
$ git clone https://github.com/drupal/drupal.git drupal7
Этой командой мы сообщили о своем желании клонировать репозиторий Drupal в директорию drupal7. После выполнения операции в директории test-git появится новый каталог, содержащий исходные коды популярной системы управления контентом Drupal.
Клонирование существующих репозиториев выполняется просто, но и создать репозиторий для существующего проекта ничуть не сложней.
Возьми любой свой старый проект (язык программирования особой роли не играет) и помести его в нашу общую для всех проектов папку test-git. Для демонстрации я взял один из своих проектов на C#, над которым мы когда-то трудились с моим другом, и поместил в папку kaskoCalc.
Теперь нам требуется сообщить Git о своем желании сотрудничать. В этот момент будет создана служебная директория (помнишь о скрытой папке .git?), набитая разными файлами. Данная операция выполняется при помощи команды git init. Вводи ее в консоли (предварительно не забудь переместиться в папку с тестовым проектом), а после загляни в директорию проекта. В нем должна появиться скрытая папка .git.
Однако этого действия недостаточно для включения всех файлов проекта под версионный контроль. Наша задача добавить все файлы в индекс и выполнить фиксацию изменений. Набери в консоли несколько команд:
$ git add * # Подготавливаем все файлы
$ git commit -m “source of my project” # Выполняем фиксацию
Удаленный репозиторий
Создавать локальные репозитории для своих проектов мы научились. Мы также убедились, что в этом нет ничего сложного. Теперь давай посмотрим, как перенести наш локальный репозиторий на хостинг Git-проектов Bitbucket. Если ты еще не создал в нем учетную запись, самое время сделать это.
Заходи в личный кабинет и создай новый репозиторий (Create repository). Тебя попросят ввести имя репозитория (name), описание (description), уровень доступа (access level), тип репозитория (git/mercurial) и язык программирования, на котором выполнен проект. Для своего проекта в качестве имени я указал kaskocalc, установил уровень доступа private (с репозиторием сможем работать только мы) и в поле выбора языка программирования остановился на C#.
Выполнив эту нехитрую операцию, возвращайся к консоли и введи несколько команд:
# Добавляем алиас для удаленного репозитория
# Это позволит нам не писать длиннющие адреса, ведущие на Bitbucket
# Следующая команда создаст алиас kaskocalc для пустого репозитория kaskocalc.git
$ git remote add kaskocalc https://guavastudio@bitbucket.org/guavastudio/kaskocalc.git
# Выполняем отправку файлов из локального репозитория, в ветку master удаленного
$ git push kaskocalc master
После отправки последней команды ты увидишь приглашение ввести пароль. Вбивай сюда пароль от аккаунта в Bitbucket. Если не возникло никаких ошибок, то все файлы твоего проекта мигрируют в удаленный репозиторий.
Собственный GitHub
Поюзав сервисы вроде Bitbucket и GitHub, ты рано или поздно задумаешься: «А существует ли возможность развернуть собственный Git-сервер?» Оказывается, сделать это совсем не сложно. Достаточно установить на свой сервер какой-нибудь дистрибутив Linux и развернуть на нем приложение GitLab. Это бесплатная альтернатива проектам вроде GitHub. После установки ты также получишь функциональный веб-интерфейс (позволяет выполнять административные функции, управлять правами доступа, просматривать коммиты и так далее) и возможность получить доступ к репозиториям через SHH, HTTPS. Система устанавливается буквально за пару команд.
Разбираемся с ветками
Теперь посмотрим, как работать с одной из самых крутых фишек Git — ветвлением. Я не преувеличиваю важности и крутости этой функции. Она действительно шикарна и позволяет разработчикам комфортно работать над множеством задач, не прибегая к созданию нескольких копий исходников всего проекта. Благодаря развитой модели ветвления Git завоевал свою неслыханную популярность и продолжает покорять сердца разработчиков.
Что же в этой модели такого особенного и почему она так эффективна? Частично на этот вопрос я ответил выше: разработчику не требуется делать копию всех файлов проекта. Работа продолжается в такой же директории, с теми же исходниками, но Git будет знать, что это новая ветка, а значит, сможет корректно зафиксировать изменения и при желании вернуться на другую ветку. Вообще, при работе над проектами под управлением Git применяется негласное правило: каждую задачу нужно решать в отдельной ветке.
Такой подход мотивируется тем, что во время программирования могут часто меняться приоритеты и при командной разработке требуется максимальное разграничение задач между участниками. Представь ситуацию, что ты с самого утра бьешься над реализацией новой функции, а тут телефонный звонок от пользователя, который обнаружил критичный баг и требует срочного исправления. Следуя идеологии Git, твои действия сведутся к созданию новой ветки, внесению исправлений и выполнению слияния с рабочей веткой. При этом изменения, сделанные для реализации новой функции, не попадут в основную ветку (при условии, что они также выполнялись в отдельной ветке).
Так, а что же является веткой в Git? Как они формируются и почему создание ветки не требует отдельной копии проекта, как это принято в других системах управления версиями? Чтобы ответить на эти вопросы, нам потребуется вернуться к началу и вспомнить, как Git хранит свои данные. В теоретической части статьи я говорил, что Git хранит не цепочку изменений, а слепки.
Итак, когда мы говорим о ветках применительно к Git, то на самом деле мы подразумеваем указатель. Не определенную копию проекта, а просто указатель на один из коммитов. Раз указатель ссылается на коммит, то можно сделать вывод, что эта сущность подвижна и каждое новое движение будет сопровождаться очередным коммитом. Число веток в Git не ограничено, и если следовать всем заповедям (одна ветка на задачу), то веток может быть сколько угодно, но срок их жизни будет недолгим. Рано или поздно все ветки сольются с другими или сразу с главной веткой.
Так, а что такое главная ветка? Когда ты создаешь новый репозиторий, то в нем автоматически создается одна ветка — master. Если не создавать дополнительных веток, то вся работа будет происходить в master, что является плохой практикой. Ветка master по факту представляет собой рабочую копию решения, которая может быть в кратчайшие сроки развернута на рабочем сервере.
Увы, в рамках одной статьи я не могу разжевать все нюансы и рассказать подробности работы веток, поэтому давай сразу перейдем к практике и попробуем создать отдельную ветку. Каждая новая ветка создается при помощи команды «git branch <Имя ветки>». Попробуем создать новую ветку под именем test-branch в нашем проекте. Выполняем в консоли:
$ git branch test # Создаем новую ветку «test»
Безошибочное выполнение команды будет означать, что новая ветка была создана. Правда, создание ветки не означает, что мы сразу переходим на нее и все последующие изменения будут учитываться в рамках новой ветки. Для перехода на ветку test требуется выполнить еще одну команду:
$ git checkout test-branch
Git checkout переведет нас на ветку test-branch, и мы сразу можем приступать к внесению изменений. Открой любой файл своего тестового проекта и попробуй что-нибудь в нем изменить. В своем проекте я открыл файл BaseCalculator.cs, описывающий абстрактный класс, и добавил в нем описание нового метода GetHelloWorldString(). После этого я сохранил изменения и сделал коммит:
$ git add “Abstract Classes/BaseCalculator.cs” # Подготавливаем файл
$ git commit -m “add GetHelloWorldString()” # Выполняем коммит
Изменения в test-branch зафиксированы, теперь попробуем посмотреть, что произошло в ветке master. Для переключения на другую ветку выполни уже знакомую команду checkout:
$ git checkout master
Попробуй открыть модифицированный файл и убедись, что внесенных несколькими минутами ранее изменений там нет. Они остались в ветке test-branch, и, чтобы к ним вернуться, мы должны опять выполнить переход.
Разбираемся с ветвлениями в Git
Чтобы легче освоить такую непростую вещь, как ветвление, я настоятельно рекомендую попробовать пройти интерактивное обучение при помощи веб-сервиса learnGitBranchingLearnGitBranching. Выглядит это так. Ты получаешь доступ к виртуальной консоли и ряду практических задач. В твоем распоряжении несколько команд (commit, branch, checkout, cherry-pick, reset, revert, rebase и merge) и небольшой кусок справки. С их помощью тебе и предстоит биться с предложенными квестами. Надо отметить, что сложность задач хорошо варьируется. Если сначала предлагают совсем уж пустяковые, над решением которых думать особо не требуется, то потом начинается настоящий рок-н-ролл. На таких задачах и в хелп не стыдно заглянуть.
Сливаемся в едином порыве
У нас имеется две ветки, в одной из которых (test-branch) были изменения (напомню, я добавил описание нового метода). Будем считать, что эти изменения полностью готовы для отправки в основную ветку, то есть в master. Для слияния веток применяется команда merge. Попробуем слить ветку test-branch с master:
$ git checkout master # Переходим на ветку master
$ git merge test-branch # Выполняем слияние с веткой test-branch
Выполнение этих команд будет обязательно сопровождаться сообщением «Fast forward» (на сленге говорят «переметка»). Оно подразумевает, что удача на нашей стороне и Git удалось объединить все изменения без лишнего геморроя. Успеху мы обязаны нашему коммиту, а точнее, его наследственности. Ветка test-branch сразу указывала на коммит, являющийся прямым родителем коммита, с которым мы работаем в настоящее время.
Хорошо, а что, если удача повернулась к нам задом и повторить такой финт не удается? Как Git поступит в этом случае? Такие ситуации заставляют Git попотеть: ему приходится выполнить трехходовое слияние на основе двух снимков состояния репозитория, на которые ссылаются вершины веток и общий слепок-прародитель для этих двух веток.
По окончании этих манипуляций Git создаст новый коммит, который принято называть коммит-слияния. Свое название он получил из-за того, что имеет больше одного предка. Обрати внимание, что Git и в этом случае берет всю работу по организации слияния на себя. Он проанализирует возможные варианты и остановится на лучшем предке. Если слияние выполнилось успешно (нет никаких ошибок), то можно не стесняться и удалять ненужную ветку:
$ git branch -d test-branch
На этом слияние можно считать завершенным, все изменения, сделанные в ветке test-branch, мигрировали в master.
Разбор конфликтов
Рано или поздно у тебя обязательно возникнет ситуация, когда изменения, сделанные в одной ветке, наглым образом конфликтуют с изменениями в другой. Подобные вещи появляются, если ты изменишь одну и ту же часть файла в разных ветках.
Git подобные конфликты самостоятельно разрулить не может, так что эта работа ложится на твои плечи. Если конфликт возникнет, то ответом на команду merge будет сообщение: «CONFLICT (content): Merge conflict in BaseCalculator.cs». Получить список всех проблемных файлов ты всегда можешь при помощи команды git status. В приведенном примере видно, что конфликт возник в файле BaseCalculator.cs. Если открыть этот файл сейчас, то в районе конфликтного участка будет что-то вроде:
<<<<<<< HEAD:BaseCalculator.cs
return 3;
=======
int i = 4;
return = I +2;
>>>>>>> hotfix31337:BaseCalculator.cs
В верхней части блока приведен код из ветки master, а в нижней — из hotfix31337. Реализация методов в двух ветках сильно отличается. Конфликт разрешается путем самостоятельного редактирования файла. Например, либо ты просто удаляешь вариант из ветки master и оставляешь лишь новую реализацию (int i = 4; return = I + 2;), либо собираешь из двух кусков один. Такую операцию необходимо произвести для каждого конфликта и по завершении выполнить для них git add.
Большая шпаргалка
Первое знакомство с Git оставляет неоднозначное впечатление: куча команд, ветки, консоль и другие непонятные вещи. Чтобы тебе сразу не запутаться во всем этом хозяйстве, я подготовил для тебя большую шпаргалку. В ней я собрал команды, которые чаще всего использую. Надеюсь, эта шпора сможет помочь тебе в трудную минуту.
Плагины для популярных IDE/редакторов
https://github.com/kemayo/sublime-text–2-git — для Sublime Text 2;
http://forum.lowyat.net/topic/1358320/all — для Notepad++;
http://www.eclipse.org/egit/download — для Eclipse;
http://code.google.com/p/nbgit — для NetBeans.
Вместо заключения
Переходить или нет на Git — дело личное. Да, он может показаться сложным и даже неуклюжим, но, поверь, это лишь первое впечатление. Достаточно перевести на него пару своих рабочих проектов и попробовать реально поработать с системой несколько дней. Уверен, уже через неделю консольные команды не будут так страшны, а ты перестанешь лишний раз переживать при работе с исходным кодом в команде. В общем, идею ты понял, и мне остается лишь пожелать тебе хороших коммитов и денежных проектов. Удачи!
Статья опубликована журнале "Хакер" (http://xakep.ru). Май 2013 г. Издательство GameLand.
Ссылка на опубликованную статью сайта издания: http://www.xakep.ru/post/61200/
Ссылка на журнал: http://www.xakep.ru/magazine/xa/172/default.asp
2014-02-13 в 09:07:16
Может я чего-то недопонимаю? "Просто лично мне не понравилось, что для работы над проектом я должен иметь постоянное соединение с SVN-сервером". Вот про это я не знал. У меня на домашнем сервере стоит subversion. На рабочем компе и на домашнем компе находятся локальные копии исходников. На работе я один раз подключаюсь к SVN, скачиваю свежие исходники и уже работаю локально с ними. В конце рабочего дня заливаю исходники обратно на SVN - делаю commit с комментариями. Прихожу домой и уже там могу слить свежую ревизию в локальную папку и продолжить работать из дома. Зачем постоянное соединение с SVN? Честно не понимаю. Может я что-то делаю не так?
2014-02-13 в 22:00:55
Ты не совсем правильно понял или я не совсем красиво написал :-) Скорей всего второе, т.к. когда я прочитал твой коммент, то сам впал в ступор. Я имел в виду не прям постоянное соединение, а саму централизованность SVN. Она крутится всегда на сервере и к нему всегда нужен доступ. Git напротив, децентрализован.