Персональный блог Игоря Антонова aka "spider_net"

Введение в Git. Учимся контролировать версии исходников


Рубрика: Журнал Хакер -> Статьи
Метки: | | |
Просмотров: 6094
Введение в Git. Учимся контролировать версии исходников

Почему архивы — это плохо?

Я всегда удивлялся, когда видел, как хорошие программисты перед внесением изменений в файл проекта тупо архивируют его, присваивая в качестве имени что-то вроде 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. Условно ее можно разделить на три составляющие: служебная директория (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.

    Клонируем репозиторий 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

    Выполнив эту нехитрую операцию, возвращайся к консоли и введи несколько команд:

    # Добавляем алиас для удаленного репозитория
    # Это позволит нам не писать длиннющие адреса, ведущие на Bitbucket
    # Следующая команда создаст алиас kaskocalc для пустого репозитория kaskocalc.git
    $ git remote add kaskocalc https://guavastudio@bitbucket.org/guavastudio/kaskocalc.git
    # Выполняем отправку файлов из локального репозитория, в ветку master удаленного
    $ git push kaskocalc master

    После отправки последней команды ты увидишь приглашение ввести пароль. Вбивай сюда пароль от аккаунта в Bitbucket. Если не возникло никаких ошибок, то все файлы твоего проекта мигрируют в удаленный репозиторий.

    Локальное хранилище переместилось в 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 оставляет неоднозначное впечатление: куча команд, ветки, консоль и другие непонятные вещи. Чтобы тебе сразу не запутаться во всем этом хозяйстве, я подготовил для тебя большую шпаргалку. В ней я собрал команды, которые чаще всего использую. Надеюсь, эта шпора сможет помочь тебе в трудную минуту.

  • git branch <имя ветки> — создать новую ветку;
  • git branch –list — вывести список всех созданных веток;
  • git help <Имя команды> — вывести справку по определенной команде;
  • git commit –amend — сделать последний коммит еще раз;
  • git add <Имя файла|маска> — подготовить файлы (добавить в версионный контроль);
  • git reset <Имя файла> — убрать файл из индекса;
  • git checkout <имя ветки> — переключится на ветку;
  • git status — получить текущее состояние файлов проекта;
  • git clone <Источник> — клонировать существующий репозиторий;
  • git diff — отобразить список неиндексированных изменений;
  • git diff –cached — вывести список изменений, которые войдут в следующий коммит;
  • git commit -m <комментарий> — сделать коммит (фиксацию изменений) с произвольным комментарием;
  • git fetch — получить все изменения из репозитория;
  • git push <ветка> — закинуть свои изменения на удаленный сервер в определенную ветку;
  • git add -u — подготовить все измененные файлы;
  • git init — создать директорию Git в текущем каталоге;
  • git diff –staged — сравнить индексированные файлы с последним коммитом;
  • git rm <Имя файла> — удалить файл из индекса;
  • git log — отобразить историю коммитов;
  • git checkout – <Имя файла> — отменить изменения, сделанные в файле;
  • git remote -v — просмотреть список соответствия алиасов и URL;
  • git remote add <алиас> — добавить сокращение для URL;
  • git tag <Имя метки> — добавить метку;
  • git merge <Имя ветки> — слияние с веткой;
  • git branch -d <Имя ветки> — удалить ветку.
  • Плагины для популярных 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

    Комментариев: 2 RSS

    Может я чего-то недопонимаю? "Просто лично мне не понравилось, что для работы над проектом я должен иметь постоянное соединение с SVN-сервером". Вот про это я не знал. У меня на домашнем сервере стоит subversion. На рабочем компе и на домашнем компе находятся локальные копии исходников. На работе я один раз подключаюсь к SVN, скачиваю свежие исходники и уже работаю локально с ними. В конце рабочего дня заливаю исходники обратно на SVN - делаю commit с комментариями. Прихожу домой и уже там могу слить свежую ревизию в локальную папку и продолжить работать из дома. Зачем постоянное соединение с SVN? Честно не понимаю. Может я что-то делаю не так?

    Ты не совсем правильно понял или я не совсем красиво написал :-) Скорей всего второе, т.к. когда я прочитал твой коммент, то сам впал в ступор. Я имел в виду не прям постоянное соединение, а саму централизованность SVN. Она крутится всегда на сервере и к нему всегда нужен доступ. Git напротив, децентрализован.

    Оставьте комментарий!
    comments powered by HyperComments