Что такое обджект хед
Я не буду описывать назначение каждого файла, а остановлюсь на основных моментах.
Самыми важными элементами являются objects, refs, HEAD, index. Для того чтобы понять, что это и с чем их едят, мы создадим несколько файлов, каталог, и добавим их в наш репозиторий.
Объекты
Первоначально каталог оbjects содержит пустые подкаталоги pack и info и никаких файлов.
Создадим в рабочей директории файл test.txt с содержимым «test file version 1».
Добавим этот файл в индекс.
Теперь посмотрим что изменилось у нас в репозитории.
В директорию objects добавился файл. Следует сказать, что все файлы в данной директории являются git объектами определенного типа.
Давайте посмотрим содержимое и тип данного объекта с помощью команды cat-file.
Данный объект имеет тип blob. Это и есть начальное представление данных в Git — один файл на единицу хранения с именем, которое вычисляется как SHA1 хеш содержимого и заголовка объекта. Первые два символа SHA определяют подкаталог файла, остальные 38 — имя. Данный объект просто хранит снимок содержимого файла test.txt.
Далее видим, что появился файл index. Это бинарный файл, содержащий список индексированных файлов и связанных с ними blob объектов.
То есть индекс содержит всю необходимую информацию для создания tree объекта при последующем коммите. Tree объект — это еще один тип объекта в git. Чуть позже мы с ним познакомимся.
А теперь добавим каталог new и файл new/new.txt
Давайте узнаем тип нового объекта и его содержимое.
И заглянем снова в index.
А теперь все это закоммитим.
Теперь наш репозиторий содержит 5 объектов.
Добавилось еще три файла. Посмотрим что это за файлы.
Это другой тип объектов git — tree объект. Объект данного типа содержит одну или более записей, указывающей на другое дерево или на blob объект.
И наконец, последний тип объекта — объект-коммит.
Мы видим дерево верхнего уровня, о котором я уже упоминал ранее, имя автора и коммитера и сообщение коммита.
Сделаем новое изменение(в test.txt изменим текст на «test file version 2»)и закоммитим. Кроме ссылки на дерево появилась еще ссылка на предшествующий коммит.
Чтобы все стало на свои места, нарисуем граф объектов
Ссылки
Так как у нас только одна ветка master, то и ссылка тоже только одна, которая указывает на последний коммит.
Давайте сделаем бранч release, указывающий на первый коммит.
Вот по сути что такое ветка в git — простой указатель на определенный коммит.
Посмотрим более наглядно
Данный файл содержит ссылку не на хеш, а на текущую ветку.
Если перейти на другую ветку, то и содержание данного файла изменится.
Немного подробностей про Class Based View, ч.2
Доброго времени суток, уважаемые читатели! Не так давно мной была опубликована первая часть статьи на данную тему. Я хочу еще раз поблагодарить всех пользователей, высказавших конструктивную критику, благодаря которой примеры в статье удалось привести к почти идеальному виду. В то же время я понял, что данный формат подачи материала является неэффективным: мы рассмотрели всего пару методов, реализованных в Class Based View (далее по тексту CBV). Во второй части я решил переработать подачу и далее постараюсь описать максимально возможное количество методов, представленных в API. Постараюсь, также, охватить те методы, которые были упущены в первой части. Очень надеюсь на конструктивную поддержку читателей и надеюсь, что и в дальнейшем у нас получится продуктивный диалог, в результате которого статья станет еще более информативной.
Ссылки для быстрого поиска методов
Ссылки для быстрого поиска атрибутов
Общая информация
Для начала определимся, что такое вообще эти CBV, нужны ли они и где их стоит использовать. Наверное стоит подчеркнуть, что особого выбора нам разработчики Django не предоставляют. Скорее всего уже с 1.4 версии (судя по транку) generic views будут объявлены как deprecated, а в одной из следующих версий могут вообще отказаться от их использования. Поэтому я решил, как это часто бывает, не откладывать изучение в долгий ящик, а начать работу с CBV прямо сейчас. Собственно, за неимением подробной информации, данная статья и будет написана на личных наблюдениях.
Итак, CBV позволяет нам использовать особенности объектно-ориентированного программирования при разработке наших отображений. Теперь мы можем реализовывать базовые классы, несущие определенную функциональность и использовать их как примеси (mixins) для наших отображений.
Все отображения имеют точку входа, который можно считать и конструктором, это метод dispatch. Данный метод принимает аргумент request, являющийся экземпляром объекта HttpRequest, в качестве первого аргумента. В этом его основная схожесть с function based views. Также данный метод принимает все переданные отображению переменные в качестве именованных и неименованных позиционных аргументов (*args/**kwargs). Рассмотрим пару примеров работы с данным методом.
Метод dispatch является наилучшим местом, для использования декораторов. Для их указания необходимо использовать реализованный в django декоратор method_decorator. Допустим нам необходимо предоставить доступ к определенному списку объектов лишь для авторизованных пользователей:
Имейте ввиду, что метод dispatch для корректной работы должен возвращать ссылку на родительский диспетчер. В противном случае не произойдет корректной передачи управления.
Очень часто возникает необходимость передать нашему отображени дополнительный контекст. Допустим у нас есть статья, просмотр которой мы реализуем с помощью класса DetailView. Нам необходимо также получить список комментариев к данной статье. На мой взгляд метод get_context_data
будет лучшей точкой для реализации требуемого функционала.
Несколько забегая вперед уточню, что получить доступ к текущему объекту при реализации DetailView можно через атрибут object.
Если мы хотим узнать (или задать) имя для шаблона, который будет использовать наше отображение, то нам нужно обратить внимание на метод get_template_names. Данный метод возвращает список имен шаблонов, которые Django будет пытаться использовать в порядке их указания. По умолчанию наивысшим приоритетом будет обладать шаблон, указанный в атрибуте template_name нашего объекта. Если данный атрибут не указан, то Django будет пытаться использовать шаблон «название_приложения/имя_объекта_префикс». Например для приложения content и модели post данный путь будет иметь вид «content/post_префикс.html». Префикс задается с помощью атрибута template_name_suffix, либо используется префикс по умолчанию для данного типа отображения. Например для ListView префикс носит имя «_list», для DetailView «_detail». Если мы просто хотим, чтобы наше отображение использовало шаблон, который мы хотим, то нам достаточно лишь указать его с помощью атрибута template_name. Переопределение метода get_template_names может потребоваться, если нам необходимо собирать имя шаблона по своим правилам.
Часто бывает необходимо изменить имя переменной, под которой наш объект (или список объектов) доступен в шаблоне. Метод, отвечающий за данную функциональность носит имя get_context_object_name. Данный метод принимает в качестве аргумента объект (или список объектов). В случае с отдельным объектом имя переменной в шаблоне по умолчанию будет именем самого объекта. В случае со списком объектов это будет имя объекта с суффиксом _list. Например для объекта Post иимена переменных будут post и post_list для отдельного объекта и списка объектов соответственно. Мы можем явно указать имя переменной, если присвоим ее значение атрибуту context_object_name.
Представим, что перед нами стоит задача по реализации RESTful приложения, CBV предоставляет исчерпывающий набор методов для их реализации. Чтобы реализовать работу приложения через определенный метод, нам нужно лишь переопределить одноименный метод в нашем отображении. Список доступных методов: get, post, put, delete, head, options, trace. Работа с формами упрощается в разы, ведь мы можем использовать метод get для отображения нашей формы, а ее обработку перенести в метод post. Требуется добавить какой-либо дополнительный специфический http метод? Нет проблем, достаточно лишь дополнить атрибут http_method_names, содержащий список имен доступных методов, а затем определить одноименный метод у себя в отображении. Все http методы должны возвращать объект HttpResponse. Если нам необходимо отрендерить шаблон нашего отображения, то мы можем для этого вызвать в вышеуказанных http методах метод render_to_response. Данный метод имеет не только такое же имя, как и его функциональный собрат, но и схожую функциональность.
Метод принимает в качестве аргумента требуемый контекст.
Прежде чем вывести информацию в шаблон нам необходимо ее получить. В этом нам поможет метод get_queryset, задача которого вернуть объект QuerySet. По умолчанию данный метод возвращает атрибут queryset если он определен, либо список всех объектов модели, которая указана в атрибуте model. Мы можем переопределить данный метод, чтобы он удовлетворял нашим задачам.
В данной части я постарался охватить по возможности все базовые (имеющиеся во всех типах CBV) методы. Если я что-то пропустил, то прошу сообщить и я дополню статью. Следующие части будут включать в себя описание особенностей отдельных типов CBV. Начну, пожалуй, с описания методов ListView и DetailView. Буду очень рад, если мои статьи помогут людям в поиске информации. Спасибо, что прочитали данную статью 🙂
Ежедневная работа с Git
Я совсем не долго изучаю и использую git практически везде, где только можно. Однако, за это время я успел многому научиться и хочу поделиться своим опытом с сообществом.
Конечно, я попытаюсь рассказать обо всём по-порядку, начиная с основ. Поэтому, эта статья будет крайне полезна тем, кто только начинает или хочет разобраться с git. Более опытные читатели, возможно, найдут для себя что-то новое, укажут на ошибки или поделятся советом.
Вместо плана
Очень часто, для того чтобы с чем-то начать я изучаю целую кучу материалов, а это — разные люди, разные компании, разные подходы. Всё это требует много времени на анализ и на понимание того, подойдёт ли что-нибудь мне? Позже, когда приходит понимание, что универсальное решение отсутствует, появляются совершенно другие требования к системе контроля версий и к разработке.
Окружение
Если вы открываете консоль, пишите git и получаете вот это:
Перестаём бояться экспериментировать
Строго говоря, даже неудачный git push можно исправить.
Поэтому, спокойно можете клонировать любой репозиторий и начать изучение.
Строим репозитории
В первую очередь нужно понять что такое git-репозиторий? Ответ очень прост: это набор файлов. Папка `.git`. Важно понимать, что это только набор файлов и ничего больше. Раз 20 наблюдал проблему у коллег с авторизацией в github/gitlab. Думая, что это часть git-системы, они пытались искать проблему в конфигруации git, вызывать какие-то git-команды.
В частности, при клонировании вот так:
урл «превращается» в
Т.е. используется SSH и проблемы нужно искать в нём. Как правило, это неправильно настроенный или не найденный ssh-ключ. Гуглить надо в сторону «SSH Auth Key git» или, если совсем по взрослому, проверить, что же происходит:
Какие протоколы поддерживаются поможет справка (раздел GIT URLS):
Сделаем себе один (будет нашим главным тестовым репозиторием):
Итог: у нас есть 3 репозитория. Там ничего нет, зато они готовы к работе.
Начало GIT
Скандалы! Интриги! Расследования!
Как это всё работает?
Как это всё можно понять и запомнить?
Для этого нужно заглянуть под капот. Рассмотрим всё в общих чертах.
Git. Почти под капотом
Git сохраняет в commit содержимое всех файлов (делает слепки содержимого каждого файла и сохраняет в objects). Если файл не менялся, то будет использован старый object. Таким образом, в commit в виде новых объектов попадут только изменённые файлы, что позволит хорошо экономить место на диске и даст возможность быстро переключиться на любой commit.
Это позволяет понять, почему работают вот такие вот забавные штуки:
Да, не стоит хранить «тяжёлые» файлы, бинарники и прочее без явной необходимости. Они там останутся навсегда и будут в каждом клоне репозитория.
Каждый коммит может иметь несколько коммитов-предков и несколько дочерних-коммитов:
Мы можем переходить (восстанавливать любое состояние) в любую точку этого дерева, а точнее, графа. Для этого используется git checkout:
Каждое слияние двух и более коммитов в один — это merge (объединение двух и более наборов изменений).
Каждое разветвление — это появление нескольких вариантов изменений.
Кстати, тут хочется отметить, что нельзя сделать тэг на файл/папку, на часть проекта и т.д. Состояние восстанавливается только целиком. Поэтому, рекомендуется держать проекты в отдельном репозитории, а не складывать Project1, Project2 и т.д. просто в корень.
Теперь к веткам. Выше я написал:
В Git нет веток* (с небольшой оговоркой)
Получается, что так и есть: у нас есть много коммитов, которые образуют граф. Выбираем любой путь от parent-commit к любому child-commit и получаем состояние проекта на этот коммит. Чтобы коммит «запомнить» можно создать на него именованный указатель.
Такой именованный указатель и есть ветка (branch). Так же и с тэгом (tag). `HEAD` работает по такому же принципу — показывает, где мы есть сейчас. Новые коммиты являются продолжением текущей ветки (туда же куда и смотрит HEAD).
Указатели можно свободно перемещать на любой коммит, если это не tag. Tag для того и сделан, чтобы раз и навсегда запомнить коммит и никуда не двигаться. Но его можно удалить.
Вот, пожалуй, и всё, что нужно знать из теории на первое время при работе с git. Остальные вещи должны показаться теперь более понятными.
Терминология
index — область зафиксированных изменений, т.е. всё то, что вы подготовили к сохранению в репозиторий.
commit — изменения, отправленные в репозиторий.
HEAD — указатель на commit, в котором мы находимся.
master — имя ветки по-умолчанию, это тоже указатель на определённый коммит
origin — имя удалённого репозитория по умолчанию (можно дать другое)
checkout — взять из репозитория какое-либо его состояние.
Простые правки
Если вы сделали что-то не так, запутались, не знаете, что происходит — эти две команды вам помогут.
Вернёмся к нашим репозиториям, которые создали раньше. Далее обозначу, что один разработчик работает в dev1$, а второй в dev2$.
Поделимся со всеми. Но поскольку мы клонировали пустой репозиторий, то git по умолчанию не знает в какое место добавить коммит.
Он нам это подскажет:
Второй разработчик может получить эти изменения, сделав pull:
Добавим ещё пару изменений:
Посмотрим, что же мы сделали (запускаем gitk):
Выделил первый коммит. Переходя по-порядку, снизу вверх, мы можем посмотреть как изменялся репозиторий:
До сих пор мы добавляли коммиты в конец (там где master). Но мы можем добавить ещё один вариант README.md. Причём делать мы это можем из любой точки. Вот, например, последний коммит нам не нравится и мы пробуем другой вариант. Создадим в предыдущей точке указатель-ветку. Для этого через git log или gitk узнаем commit id. Затем, создадим ветку и переключимся на неё:
Выглядит это так:
Теперь нам понятно, как создаются ветки из любой точки и как изменяется их история.
Быстрая перемотка
Наверняка, вы уже встречали слова fast-forward, rebase, merge вместе. Настало время разобраться с этими понятиями. Я использую rebase, кто-то только merge. Тем «rebase vs merge» очень много. Авторы часто пытаются убедить, что их метод лучше и удобнее. Мы пойдём другим путём: поймём, что же это такое и как оно работает. Тогда сразу станет ясно, какой вариант использовать в каком случае.
Пока, в пределах одного репозитория, сделаем ветвление: создадим файл, положим в репозиторий, из новой точки создадим два варианта файла и попробуем объединить всё в master:
Создадим файл collider.init.sh с таким содержанием:
Добавим, закоммитим и начнём разработку в новой ветке:
Обратите внимание, что в имени ветки не запрещено использовать символ ‘/’, однако, надо быть осторожным, т.к. в файловой системе создаётся папка с именем до ‘/’. Если ветка с таким названием как и папка существует — будет конфликт на уровне файловой системы. Если уже есть ветка dev, то нельзя создать dev/test.
A если есть dev/test, то можно создавать dev/whatever, но нельзя просто dev.
Теперь, в каждой ветке напишем код, который, соответственно, будет запускать и уничтожать наш коллайдер. Последовательность действий приблизительно такая:
Разработка закончена и теперь надо отдать все изменения в master (там ведь старый коллайдер, который ничего не может). Объединение двух коммитов, как и говорилось выше — это merge. Но давайте подумаем, чем отличается ветка master от collider/start и как получить их объединение (сумму)? Например, можно взять общие коммиты этих веток, затем прибавить коммиты, относящиеся только к master, а затем прибавить коммиты, относящиеся только к collider/start. А что у нас? Общие коммиты — есть, только коммиты master — нет, только collider/start — есть. Т.е. объединение этих веток — это master + коммиты от collider/start. Но collider/start — это master + коммиты ветки collider/start! Тоже самое! Т.е. делать ничего не надо! Объединение веток — это и есть collider/start!
Ещё раз, только на буквах, надеюсь, что будет проще для восприятия:
master = C1 + C2 +C3
collider/start = master + C4 = C1 + C2 +C3 + C4
master + collider/start = Общие_коммиты(master, collider/start) + Только_у(master) + Только_у(collider/start) = (C1 + C2 +C3) + (NULL) + (C4) = C1 + C2 +C3 + C4
Как быстро узнать, что fast-forward возможен? Для этого достаточно посмотреть в gitk на две ветки, которые нужно объединить и ответить на один вопрос: существует ли прямой путь от ветки А к B, если двигаться только вверх (от нижней к верхней). Если да — то будет fast-forward.
В теории понятно, пробуем на практике, забираем изменения в master:
Результат (указатель просто передвинулся вперёд):
Объединение
Теперь забираем изменения из collider/terminate. Но, тот, кто дочитал до сюда (дочитал ведь, да?!) заметит, что прямого пути нет и так красиво мы уже не отделаемся. Попробуем git попросить fast-forward:
Что и следовало ожидать. Делаем просто merge:
Я даже рад, что у нас возник конфликт. Обычно, в этом момент некоторые теряются, лезут гуглить и спрашивают, что делать.
Для начала:
Конфликт возникает при попытке объединить два и более коммита, в которых в одной и той же строчке были сделаны изменения. И теперь git не знает что делать: то ли взять первый вариант, то ли второй, то ли старый оставить, то ли всё убрать.
Как всегда, две самые нужные команды нам спешат помочь:
Мы находимся в master, туда же указывает HEAD, туда же и добавляются наши коммиты.
Файл выглядит так:
Из-за того что изменения были в одних и тех же местах конфликтов получилось много. Поэтому, я брал везде первый вариант, а второй копировал сразу после первого, но с gui это сделать довольно просто. Вот результат — вверху варианты файла, внизу — объединение (простите за качество):
Сохраняем результат, закрываем окно, коммитим, смотрим результат:
Мы создали новый коммит, который является объединением двух других. Fast-forward не произошёл, потому, что не было прямого пути для этого, история стала выглядеть чуть-чуть запутаннее. Иногда, merge действительно нужен, но излишне запутанная история тоже ни к чему.
Вот пример реального проекта:
Конечно, такое никуда не годится! «Но разработка идёт параллельно и никакого fast-forward не будет» скажете вы? Выход есть!
Перестройка
Что же делать, чтобы история оставалась красивой и прямой? Можно взять нашу ветку и перестроить её на другую ветку! Т.е. указать ветке новое начало и воспроизвести все коммиты один за одним. Этот процесс и называется rebase. Наши коммиты станут продолжением той ветки, на которую мы их перестроим. Тогда история будет простой и линейной. И можно будет сделать fast-forward.
Другими словами: мы повторяем историю изменений с одной ветки на другой, как будто бы мы действительно брали другую ветку и заново проделывали эти же самые изменения.
Для начала отменим последние изменения. Проще всего вернуть указатель master назад, на предыдущее состояние. Создавая merge-commit, мы передвинули именно master, поэтому именно его нужно вернуть назад, желательно (а в отдельных случаях важно) на тот же самый commit, где он был.
Как результат, наш merge-commit останется без какого-либо указателя и не будет принадлежать ни к одной ветке.
Используя gitk или консоль перемещаем наш указатель. Поскольку, ветка collider/start уже указывает на наш коммит, нам не нужно искать его id, а мы можем использовать имя ветки (это будет одно и тоже):
Когда с коммита или с нескольких коммитов пропадает указатель (ветка), то коммит остаётся сам по себе. Git про него забывает, не показывает его в логах, в ветках и т.д. Но физически, коммит никуда не пропал. Он живёт себе в репозитории как невостребованная ячейка памяти без указателя и ждёт своего часа, когда git garbage collector её почистит.
Иногда бывает нужно вернуть коммит, который по ошибке был удалён. На помощь придёт git reflog. Он покажет всю историю, по каким коммитам вы ходили (как передвигался указатель HEAD). Используя вывод, можно найти id пропавшего коммита, сделать его checkout или создать на коммит указатель (ветку или тэг).
Выглядит это примерно так (история короткая, поместилась вся):
Посмотрим, что получилось:
Для того, чтобы перестроить одну ветку на другую, нужно найти их общее начало, потом взять коммиты перестраиваемой ветки и, в таком же порядке, применить их на основную (base) ветку. Очень наглядная картинка (feature перестраивается на master):
Важное замечание: после «перестройки» это уже будут новые коммиты. А старые никуда не пропали и не сдвинулись.
В теории разобрались, пробуем на практике. Переключаемся в collider/terminate и перестраиваем на тот коммит, куда указывает master (или collider/start, кому как удобнее). Команда дословно «взять текущую ветку и перестроить её на указанный коммит или ветку»:
Откроется редактор в котором будет приблизительно следующее:
Если коротко: то в процессе перестройки мы можем изменять комментарии к коммитам, редактировать сами коммиты, объединять их или вовсе пропускать. Т.е. можно переписать историю ветки до неузнаваемости. На данном этапе нам это не нужно, просто закрываем редактор и продолжаем. Как и в прошлый раз, конфликтов нам не избежать:
На данном этапе мы удаляли, редактировали, объединяли правки, а на выходе получили красивую линейную историю изменений:
Перерыв
Дальше примеры пойдут по сложнее. Я буду использовать простой скрипт, который будет в файл дописывать случайные строки.
На данном этапе нам не важно, какое содержимое, но было бы очень неплохо иметь много различных коммитов, а не один. Для наглядности.
Скрипт добавляет случайную строчку к файлу и делает git commit. Это повторяется несколько раз:
Передача и приём изменений
git remote
Как отмечалось выше, origin — это имя репозитория по умолчанию. Имена нужны, т.к. репозиториев может быть несколько и их нужно как-то различать. Например, у меня была копия репозитория на флешке и я добавил репозиторий flash. Таким образом я мог работать с двумя репозиториями одновременно: origin и flash.
Имя репозитория используется как префикс к имени ветки, чтоб можно было отличать свою ветку от чужой, например master и origin/master
В справке по git remote достаточно хорошо всё описано. Как и ожидается, там есть команды: add, rm, rename, show.
show покажет основные настройки репозитория:
Чтобы добавить существующий репозиторий используем add :
git fetch
Команда говорит сама за себя: получить изменения.
Стоит отметить, что локально никаких изменений не будет. Git не тронет рабочую копию, не тронет ветки и т.д.
Будут скачены новые коммиты, обновлены только удалённые (remote) ветки и тэги. Это полезно потому, что перед обновлением своего репозитория можно посмотреть все изменения, которые «пришли» к вам.
Ниже есть описание команды push, но сейчас нам нужно передать изменения в origin, чтобы наглядно показать как работает fetch:
Теперь от имени dev2 посмотрим, что есть и получим все изменения:
Выглядит это так:
Обратите внимание, что мы находимся в master.
Что можно сделать:
git checkout origin/master — переключиться на удалённый master, чтобы «пощупать» его. При этом нельзя эту ветку редактировать, но можно создать свою локальную и работать с ней.
git merge origin/master — объединить новые изменения со своими. Т.к. у нас локальных изменений не было, то merge превратится в fast-forward:
git pull
В 95% случаев, вам не нужно менять это поведение.
git push
На все вопросы может ответить расширенный вариант использования команды:
git push origin :
Примеры:
Включаемся в проект
К этому моменту уже более-менее понятно, как работает push, pull. Более смутно представляется в чём разница между merge и rebase. Совсем непонятно зачем это нужно и как применять.
Когда кого-нибудь спрашивают:
— Зачем нужна система контроля версий?
Чаще всего в ответе можно услышать:
— Эта система помогает хранить все изменения в проекте, чтобы ничего не потерялось и всегда можно было «откатиться назад».
А теперь задайте себе вопрос: «как часто приходится откатываться назад?» Часто ли вам нужно хранить больше, чем последнее состояние проекта? Честный ответ будет: «очень редко». Я этот вопрос поднимаю, чтобы выделить гораздо более важную роль системы контроля версий в проекте:
Система контроля версий позволяет вести совместную работу над проектом более, чем одному разработчику.
То, на сколько вам удобно работать с проектом совместно с другими разработчиками и то, на сколько система контроля версий вам помогает в этом — самое важное.
Используете %VCS_NAME%? Удобно? Не ограничивает процесс разработки и легко адаптируется под требования? Быстро? Значит эта %VCS_NAME% подходит для вашего проекта лучше всего. Пожалуй, вам не нужно ничего менять.
Типичные сценарии при работе над проектом
Чтобы добиться такого процесса, нужно чётко определиться где и что будет хранится. Т.к. ветки легковесные (т.е. не тратят ресурсов, места и т.д.) под все задачи можно создать отдельные ветки. Это является хорошей практикой. Такой подход даёт возможность легко оперировать наборами изменений, включать их в различные ветки или полностью исключать неудачные варианты.
Исправление багов
Создадим ветку dev и рассмотрим типичный сценарий исправления багов.
И второй разработчик «забирает» новую ветку к себе:
Пусть 2 разработчика ведут работу каждый над своим багом и делают несколько коммитов (пусть вас не смущает, что я таким некрасивым способом генерирую множество случайных коммитов):
Когда работа закончена, передаём изменения в репозиторий. Dev1:
На первый взгляд, кажется, что здесь много действий и всё как-то сложно. Но, если разобраться, то описание того, что нужно сделать намного больше, чем самой работы. И самое главное, мы уже так делали выше. Приступим к практике:
Посмотрим на результат:
Теперь, всё как положено: наши изменения в dev и готовы быть переданы в origin.
Передаём:
Нам откроется редактор и даст возможность исправить нашу историю, например, поменять порядок коммитов или объединить несколько. Об этом писалось выше. Я оставлю как есть:
После того как все конфликты решены, история будет линейной и логичной. Для наглядности я поменял комментарии (интерактивный rebase даёт эту возможность):
Теперь наша ветка продолжает origin/dev и мы можешь отдать наши изменения: актуальные, адаптированные под новые коммиты:
Далее история повторяется. Для удобства или в случае работы над несколькими багами, как говорилось выше, удобно перед началом работы создать отдельные ветки из dev или origin/dev.
Feature branch
Бывает так, что нужно сделать какой-то большой кусок работы, который не должен попадать в основную версию пока не будет закончен. Над такими ветками могут работать несколько разработчиков. Очень серьёзный и объёмный баг может рассматриваться с точки зрения процесса работы в git как фича — отдельная ветка, над которой работают несколько человек. Сам же процесс точно такой же как и при обычном исправлении багов. Только работа идёт не с dev, а с feature/name веткой.
Вообще, такие ветки могут быть коротко-живущими (1-2 неделя) и долго-живущими (месяц и более). Разумеется, чем дольше живёт ветка, тем чаще её нужно обновлять, «подтягивая» в неё изменения из основной ветки. Чем больше ветка, тем, вероятно, больше будет накладных расходов по её сопровождению.
Начнём с коротко-живущих (short-live feature branches)
Обычно ветка создаётся с самого последнего кода, в нашем случае с ветки dev:
Теперь работу на feature1 можно вести в ветке feature/feature1. Спустя некоторое время в нашей ветке будет много коммитов, и работа над feature1 будет закончена. При этом в dev тоже будет много изменений. И наша задача отдать наши изменения в dev.
Выглядеть это будет приблизительно так:
Ситуация напоминает предыдущую: две ветки, одну нужно объединить с другой и передать в репозиторий. Единственное отличие, это две публичные (удалённые) ветки, а не локальные. Это требует небольшой коммуникации. Когда работа закончена, один разработчик должен предупредить другого, что он собирается «слить» изменения в dev и, например, удалить ветку. Таким образом передавать в эту ветку что-либо будет бессмысленно.
А дальше алгоритм почти такой как и был:
Картинка, feature1 стала частью dev, то что на и нужно было:
Делаем push и чистим за собой ненужное:
Долго-живущие ветки (long-live feature branches)
Сложность долго-живущих веток в их поддержке и актуализации. Если делать всё как описано выше то, вероятно быть беде: время идёт, основная ветка меняется, проект меняется, а ваша фича основана на очень старом варианте. Когда настанет время выпустить фичу, она будет настолько выбиваться из проекта, что объединение может быть очень тяжёлым или, даже, невозможным. Именно поэтому, ветку нужно обновлять. Раз dev уходит вперёд, то мы будем просто время от времени перестраивать нашу ветку на dev.
Всё бы хорошо, но только нельзя просто так взять и перестроить публичную ветку: ветка после ребэйза — это уже новый набор коммитов, совершенно другая история. Она не продолжает то, что уже было. Git не примет такие изменения: два разных пути, нет fast-forward’а. Чтобы переписать историю разработчикам нужно договориться.
Кто-то будет переписывать историю и принудительно выкладывать новый вариант, а в этот момент остальные не должны передавать свои изменения в текущую ветку, т.к. она будет перезаписана и всё пропадёт. Когда первый разработчик закончит, все остальные перенесут свои коммиты, которые они успеют сделать во время переписи уже на новую ветку. Кто говорил, что нельзя ребэйзить публичные ветки?
Приступим к практике:
Второй разработчик подключается к работе всё стандартно:
Добавим ещё несколько коммитов в основную ветку dev и история будет такая:
Настало время актуализировать feature/long, но при этом разработка должна продолжиться отдельно. Пусть перестраивать будет dev1. Тогда он предупреждает dev2 об этом и начинает:
В это время dev2 продолжает работать, но знает, что ему нельзя делать push, т.к. нужной ветки ещё нет (а текущая будет удалена).
Первый заканчивает rebase и история будет такой:
Ветка перестроена, а origin/feature/long остался там, где и был. Цели мы достигли, теперь нужно поделиться со всеми:
Git лишний раз напоминает, что что-то не так. Но теперь, мы точно знаем, что мы делаем, и знаем, что так надо:
Теперь можно работать дальше предупредив остальных об окончании работ.
Посмотрим, как эти изменения отразились на окружающих и на dev2 в частности:
N, чтобы указать на предыдущий N-ый коммит.
HEAD
1 — это предыдущий, а HEAD
2 — это предпредыдущий. HEAD
5 — 5 коммитов назад. Удобно, чтобы не запоминать id.
Посмотрим, как теперь нам перестроить только один коммит:
1
Если бы нам надо было перетянуть 4 коммита, то было бы feature/long
Осталось продолжить работу.
Hotfixes
Hotfixы нужны, т.к. бывают очень критичные баги, которые нужно исправить как можно быстрее. При этом нельзя передать вместе с hotfix-ом последний код, т.к. он не оттестирован и на полное тестирование нет времени. Нужно только это исправление, максимально быстро и оттестированное. Чтобы это сделать, достаточно взять последний релиз, который был отправлен на продакшен. Это тот, где у вас остался tag или master. Тэги играют очень важную роль, помогая понять что именно было собрано и куда это попало.
Тэги делаются командой
Всякие полезности
Git позволяет настраивать aliasы для различных команд. Это бывает очень удобно и сокращает время набора команд. Приводить здесь примеры не буду, в Интернете их полно, просто поищите.
Перед тем, как отдавать свои изменения их можно перестроить на самих себя, чтобы привести историю в порядок.
Например, в логах может быть такое:
Перестроим сами себя, но начиная с 6 коммитов назад:
Теперь в интерактивном режиме можно объединить (squash) первые два коммита в один и последние четыре. Тогда история будет выглядеть так:
Такое намного приятнее передавать в общим репозиторий.
Выводы
Причемания и апдейты
gcc рекомендовал посмотреть на Git Extensions.
borNfree подсказал ещё один GUI клиент Source Tree.
zloylos поделился ссылкой на визуализатор для git
olancheg предложил посмотреть на ещё один туториал для новичков.
PS. Что тут обычно пишут, когда первый пост на хабре? Прошу не судить строго, писал как мог.