preview
Переходим на MQL5 Algo Forge (Часть 4): Работа с версиями и выпуск релизов

Переходим на MQL5 Algo Forge (Часть 4): Работа с версиями и выпуск релизов

MetaTrader 5Примеры |
52 0
Yuriy Bykov
Yuriy Bykov

Введение

Наш переход на MQL5 Algo Forge продолжается, и, вслед за настройкой рабочего процесса с собственными репозиториями, мы обратились к одной из главных причин этого перехода — возможности лёгкого использования кода сообщества. В части 3 мы рассмотрели, как добавить в свой проект публичную библиотеку из чужого репозитория.

Эксперимент с подключением библиотеки SmartATR к советнику SimpleCandles наглядно показал, что прямой путь — простое клонирование — не всегда удобен, особенно если код требует доработок. Мы детально отработали корректный рабочий процесс через создание форка, который стал нашей личной копией чужого репозитория для исправления ошибок и модификаций, с возможностью в будущем предложить эти изменения автору через Pull Request.

Несмотря на некоторые ограничения интерфейса MetaEditor, комбинация с веб-интерфейсом хранилища MQL5 Algo Forge позволила успешно выполнить всю цепочку действий от клонирования до коммита правок и финального соединения проекта с внешней библиотекой. Таким образом, мы не только решили конкретную задачу, но и рассмотрели универсальный шаблон действий для интеграции любых сторонних компонентов.

В сегодняшней статье мы более детально рассмотрим этап публикации в репозитории сделанных правок, совокупность которых образует некоторое законченное решение — будь то добавление новой функциональности к проекту или исправление обнаруженной ошибки. Это — фиксация или релиз новой версии продукта. Посмотрим, как можно организовать этот процесс, и какие возможности предоставляет нам в этом хранилище MQL5 Algo Forge.


Поиск ветки

В предыдущих частях мы рекомендовали использовать отдельную ветку репозитория для внесения набора правок, решающих конкретную задачу. Однако, после завершения работы над ней, ветку желательно влить в какую-то другую (основную) ветку и удалить. В противном случае, репозиторий может превратиться в сплошные заросли камыша, в которых будет легко заблудиться даже владельцу репозитория. Поэтому отработанные ветки надо удалять. Но иногда может возникать необходимость вернуть код в состояние, в котором он находился непосредственно перед удалением определённой ветки. Как это можно сделать?

Прежде всего, давайте проясним, что ветка — это просто последовательность коммитов, упорядоченных по времени. Технически ветка представляет собой указатель на какой-то коммит, считающийся последним в цепочке последовательных коммитов. Поэтому удаление ветки не удаляет коммиты. В худшем случае они могут начать считаться относящимися к другой ветке, или даже объединяться в один суммарный коммит, но всё равно они так или иначе продолжают существовать в репозитории (за редкими исключениями). Поэтому возможность вернуться в состояние "перед удалением ветки" это всё равно, что вернуться к одному из коммитов, расположенному в какой-то существующей ветке. Тогда вопрос состоит в том, как найти этот нужный коммит.

Давайте посмотрим на состояние репозитория SimpleCandles, в котором он оказался после внесения правок, упомянутых в части 3:

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

Такое дерево коммитов для каждого репозитория можно увидеть на отдельной странице репозитория веб-интерфейса хранилища MQL5 Algo Forge. Этот скриншот был сделан уже некоторое время назад, поэтому если сейчас зайти на эту страницу, то мы увидим уже несколько другую картину: в дереве коммитов будут присутствовать новые коммиты, переплетение веток тоже будет иным, из-за добавленных коммитов слияния.

Также напротив некоторых коммитов мы видим названия веток. Они показываются у самых последних коммитов в каждой ветке. На приведенном скриншоте можно насчитать шесть различных веток: main, develop, convert-to-utf8, article-17608-close-manager, article-17607 и article-19436-forge3. Последняя упомянутая ветка — это ветка изменений, вносимых при написании предыдущей части 3. Но когда велась работа над частью 2, мы тоже создали отдельную ветку для планируемых изменений. Она называлась article-17698-forge2, и теперь она уже удалена, поэтому ни у какого коммита рядом нет этого названия ветки. Где же нам найти её?

Если посмотреть на полный комментарий к коммиту 58beccf233, то в нем упоминается название этой ветки и сообщается, что она была влита в ветку develop:

Итак, нужный коммит найден, но находить его таким образом неудобно. Более того, если бы мы не воспользовались механизмом Pull Request для слияния веток, а выполняли бы их через консольные команды git merge, то мы могли бы указать свой произвольный комментарий к коммиту слияния. Тогда найти этот коммит стало бы ещё сложнее, так как в его комментарий название ветки могло и не попасть.

Теперь у нас есть возможность переключиться на этот коммит, приведя наш локальный репозиторий в состояние, как после этого коммита. Для этого мы можем использовать хеш нужного коммита в команде git chekout. Однако тут есть свои нюансы. Если мы попробуем выполнить переключение на этот коммит в MetaEditor, выбрав его из истории, открываемой при нажатии на пункт контекстного меню проекта "Git Log":

... то получим сообщение об ошибке:

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


Что такое теги

Тег (или метка) в системе контроля версий Git — это дополнительное имя, присвоенное какому-то коммиту. Также можно назвать тег указателем или ссылкой на конкретную версию кода в репозитории, так как он действительно указывает на определённый коммит, Использование тега позволяет в любой момент вернуться к состоянию кода, соответствующего данному коммиту с тегом. Теги помогают отметить важные моменты в разработке проекта, например, релиз версии, этап завершённой работы или стабильную версию. В веб-интерфейсе хранилища MQL5 Algo Forge теги выбранного репозитория можно посмотреть на отдельной странице.

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

Чтобы создать тег через веб-интерфейс, можно зайти на страницу любого коммита (например, этого) и, нажав на кнопку Operations, выбрать пункт "Create tag":

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

Для создания тега через консольные команды Git используется команда git tag. Для создания лёгкого тега достаточно указать один параметр — имя создаваемого тега:

git tag <имя-тега>

# Например
git tag v1.0

Для создания аннотированного тега потребуется указать дополнительные параметры:

git tag -a <имя-тега> -m "Описание тега"

# Например:
git tag -a v1.0 -m "Релиз версии 1.0"

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


Указатель HEAD

Сказав про теги, стоит упомянуть и про такую вещь, как указатель HEAD. По поведению он похож на тег с фиксированным именем HEAD, который автоматически перемещается на последний коммит в текущей извлечённой ветке. Также HEAD может называться «маркером текущей ветки» или «указателем на активную ветку». Он отвечает на вопрос "Где мы находимся в нашем репозитории в данный момент?". Но он не является тегом как таковым.

Физически этот указатель хранится в файле .git/HEAD в репозитории. Содержимое HEAD может содержать либо символическую ссылку (тег, название ветки), либо хеш коммита. При переключении между ветками указатель HEAD автоматически обновляется, чтобы указывать на последний коммит в текущей ветке. Когда мы добавляем новый коммит, то Git не только создаёт объект коммита, но и перемещает на него указатель HEAD.

Таким образом, имя "HEAD" можно использовать в консольных командах Git вместо хеша последнего коммита или названия текущей ветки, а используя специальные символы '~' и '^', можно ссылаться на коммиты, находящиеся позади последнего коммита. Например, "HEAD~2" будет обозначать коммит, находящийся на два коммита позади (то есть раньше) последнего коммита. Но мы сейчас не будем углубляться в такие тонкости.

Для дальнейшего нам надо упомянуть ещё про два возможных состояния, в которых может находиться репозиторий кода. Обычное состояние называется "attached HEAD" и означает, что новые создаваемые коммиты будут возникать спереди последнего коммита текущей ветки. В этом состоянии все правки добавляются в ветке последовательно и бесконфликтно.

Другое состояние, называемое "detached HEAD", возникает, когда указатель HEAD начинает указывать на коммит, который не является последним в какой-либо ветке. Это может произойти, например, при: 
  • переключении репозитория на конкретный прошлый коммит (например, командой git checkout <commit-hash>);
  • переключении репозитория по имени тега (например, git checkout tags/<tag-name>);
  • переключении репозитория на ветку, которая пока ещё присутствует в вышестоящем репозитории, но уже удалена в локальном репозитории (например, git checkout origin/<branch-name>).

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


Пока тегов нет

Вернёмся теперь к попытке переключить наш локальный репозиторий на конкретный коммит, который когда-то был последним в удалённой ветке article-17698-forge2.

Дело в том, что переключение репозитория на состояние конкретного прошлого коммита не является в Git чем-то повседневным. При нормальной работе нам не понадобится выполнять подобную операцию. Но если всё-таки мы решили это сделать, то репозиторий, переключенный на конкретный коммит, по хешу переходит в упомянутое выше состояние "detached HEAD". Теперь он встроен в ветку develop, и после него в этой ветке уже есть другие более свежие коммиты, то есть этот коммит не является последним в ветке.

Тем не менее, если мы воспользуемся консольными командами для выполнения такой операции переключения, то результат будет получен. Однако Git будет честно предупреждать о состоянии "detached HEAD":

 

Внимательные читатели могут заметить, что на последнем скриншоте мы переключались на коммит с хешем 58beccf233, а в результате Git показывает, что указатель HEAD находится на коммите с хешем 58beccf. Куда делись последние три цифры? Всё в порядке, они никуда не пропали. Просто Git может правильно понимать в командах не только полный хеш, но и какую-то его часть. Поэтому в разных интерфейсах работы с Git мы можем встретить хеши, сокращённые до разного количества символов (как правило, от 4 до 10).

При желании мы всегда можем увидеть полый хеш коммита, например, выполнив команду git log. Он будет содержать 40 цифр:

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


Использование кодировки UTF-8

Упомянем ещё один интересный аспект. В более ранних версиях редактор MetaEditor использовал для сохранения исходных кодов кодировку UTF-16LE. Но файлы, записанные в этой кодировке, почему-то считались Git бинарными, а не текстовыми. Поэтому мы не могли посмотреть при коммите, какие именно строки кода были изменены (хотя в Visual Studio Code всё равно могли). Максимум, что показывалось, — это размеры файлов до и после правок в рамках данного коммита.

Вот как это выглядело в веб-интерфейсе хранилища MQL5 Algo Forge:

Сейчас новые файлы, создаваемые в MetaEditior, сохраняются в кодировке UTF-8, и даже использование символов национальных алфавитов не приводит к автоматическому переключению на кодировку UTF-16LE. Поэтому есть смысл озаботиться конвертацией старых файлов, которые перекочевали в новое хранилище с чуть более древних времен в кодировку UTF-8. После выполнения такого преобразования, начиная со следующего коммита, можно видеть конкретные изменённые строки и символы. Например, в веб-интерфейсе хранилища MQL5 Algo Forge это может выглядеть примерно так:

Но это было отступление, давайте вернёмся к обсуждению публикации новой версии кода в репозитории.


Возвращаемся к главной задаче

Итак, среди веток нашего репозитория выделим две ветки: article-17608-close-manager и article-17607. Изменения, сделанные в них, ещё не влиты в ветку develop, поскольку работы по связанным с ветками задачам ещё не завершены. То есть эти ветки ещё будут развиваться, поэтому вливать правки из них в develop пока рано. Мы хотим продолжить одну из них (article-17607), довести до некоторого логического завершения, и после этого слить с веткой develop. Достигнутое состояние кода пометим тегом с номером версии.

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

Влить правки из develop в article-17607 можно разными способами. Например, мы можем через веб-интерфейс создать Pull Request и повторить процесс слияния, который описывали в прошлой части. Но так следует поступать в тех случаях, когда новый, непроверенный код мы хотим влить в ветку, содержащую рабочий проверенный код. Сейчас ситуация обратная: из ветки с проверенным, рабочим кодом мы хотим перенести правку в ветку с новым, ещё непроверенным кодом. Поэтому вполне допустимо для выполнения слияния воспользоваться консольными командами Git. Воспользуемся консолью и будем контролировать ход процесса в Visual Studio Code.

Прежде всего, проверим текущее состояние репозитория. В разделе системы контроля версий мы видим историю коммитов, с указанием имён веток. Текущая ветка сейчас article-19436-forge3, в которой были сделаны последние правки. Справа в терминале виден результат консольной команды git status:

Результат выполнения команды подтверждает, что наш репозиторий сейчас действительно находится в ветке article-19436-forge3 и её состояние синхронизировано с одноимённой веткой в вышестоящем репозитории.

Переключаемся на ветку article-17607, используя команду git checkout article-17607:

И следующей командой git merge develop выполняем слияние текущей ветки с веткой develop:

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

Выполним команду git push для отправки информации об изменениях в вышестоящий репозиторий:

Проверим хранилище MQL5 Algo Forge и увидим, что сделанные нами шаги по слиянию веток благополучно перенеслись в вышестоящий репозиторий:

Последний коммит на скриншоте — это коммит слияния веток develop и article-17607

Посмотрите также на свободный конец ветки article-19436-forge3, который не соединён ни с какой другой веткой. Правки из этой ветки пока не влиты в ветку develop, так как они ещё не завершены. Поэтому просто не будем сейчас на них обращать внимание, когда придет время, мы сможем продолжить эту ветку.

На этом подготовка к продолжению разработки в ветке article-17607 завершена и можно переходить к работе над кодом. Решение задачи, для которой была создана эта ветка, изложено в другой статье. Здесь же мы не будем повторяться, а перейдём сразу к описанию действий по закреплению достигнутого после решения задачи состояния кода.


Выполняем слияние

Перед публикацией какого-то состояния кода нам нужно его перенести в основную ветку. Наша основная ветка — это main. В неё будут вливаться правки из ветки разработки —develop. А в ветку разработки будут вливаться правки веток отдельных задач разработки. Мы пока что не готовы перенести новый код в ветку main, поэтому ограничимся только переносом правок в ветку develop. Для демонстрации возможностей этого механизма выбор той или иной ветки, играющей роль основной, не является столь важным.

Посмотрим на состояние репозитория SimpleCandles, в котором он оказался после завершения работы над выбранной задачей:

Как видно, последний по времени коммит сделан в ветке article-17607. Создадим через веб-интерфейс хранилища MQL5 Algo Forge запрос на слияние (Pull Request) этой ветки в ветку develop, как это было описано ранее:

Проверим, что всё получилось так, как задумано. Посмотрим снова на страницу истории коммитов с деревом веток:

Видим, что коммит с хешем 432d8a0fd7 уже не помечен, как последний коммит в ветке article-17607. Зато перед ним появился новый коммит с хешем 001d35b4a7, который отмечен как последний в ветке develop. Поскольку этот коммит фиксирует слияние двух веток, то будем дальше называть его коммитом слияния.

Зайдем на страницу коммита слияния и создадим новый тег. В начале статьи мы уже показывали, где это можно сделать, а теперь пришло время осуществить создание:

В появившемся окошке введем название "v0.1", потому что это ещё далеко не финальная версия. Мы пока не знаем еще, сколько добавлений будет сделано в этот проект, но хочется надеяться, что немало. Поэтому такой маленький номер версии — это скорее напоминание самим себе, что впереди ещё много работы. Кстати, не похоже, чтобы веб-интерфейс хранилища позволял создавать аннотированные теги.

Итак, тег успешно создан, результат можно увидеть на следующей странице:

 или на отдельной странице тегов репозитория:

Если мы выполним команду обновления локального репозитория (git pull), то созданный тег появится в нём. Однако в интерфейсе MetaEditor увидеть теги репозитория пока негде, поэтому покажем, как они отображаются в Visual Studio Code. Если навести мышь на нужный коммит в дереве коммитов, то во всплывающей подсказке будет видна цветная метка с именем связанного тега:

Теперь, когда тег создан, мы можем либо успокоиться на этом и использовать это имя в команде git checkout для перехода именно в это состояние кода, либо пойти дальше и создать релиз на основе этого состояния. 


Создаём релиз

Релизы — это механизм маркировки и распространения конкретных версий нашего программного обеспечения вне зависимости от используемого языка программирования. Если коммиты и ветки — это "рабочий процесс" разработки, то релизы — это его "официальные результаты", которые мы хотим опубликовать. Основные цели использования этого механизма таковы:

  • Версионирование. Мы отмечаем конкретные состояния кода в репозитории как обладающие определённой стабильностью, то есть отсутствием ошибок (по крайней мере явных) при реализованной функциональности. Другие пользователи смогут использовать именно такие версии кода.

  • Распространение бинарных файлов. К релизам можно прикреплять скомпилированные и прочие файлы (.ex5, .dll, .zip) избавляя пользователей от необходимости самостоятельной компиляции, если у них нет в этом особой необходимости.

  • Информирование пользователей. К релизу желательно добавлять описание, как правило, включающее в себя списки сделанных изменений, новых возможностей, исправленных ошибок и прочую информацию, касающуюся выпуска этой конкретной версии. Основная задача описания — дать понять пользователю, стоит ли обновляться до этой версии.
Упомянем, что CI/CD системы, к которым можно отнести и MQL5 Algo Forge, могут, например, автоматически создавать релизы при слиянии какой-либо ветки с основной веткой main, выполнять автоматическую сборку и публикацию бинарных файлов. Но для осуществления таких процессов необходимо выполнить предварительную настройку, прописав все необходимые сценарии обработки важных событий. Это уже гораздо более сложные, но не являющиеся необходимыми вещи, поэтому пока не будем рассматривать данные возможности.
Релиз создаётся на основе какого-либо существующего тега, либо в процессе создания релиза одновременно создаётся и новый тег. Мы тег уже создали, поэтому создадим новый релиз на его основе. Для этого на странице тегов репозитория кликнем на пункт "New release" у нужного тега:

В открывшейся форме редактирования мы можем указать следующие основные свойства:
  • название релиза, ветку и тег из этой ветки (новый или ранее выбранный);
  • описание релиза (Release notes) — что добавлено нового, что исправлено, какие известные проблемы были решены;
  • прикрепленные файлы, например, скомпилированные программы, документация или ссылки на внешние источники.
Вот как это выглядит в веб-интерфейсе хранилища MQL5 Algo Forge:

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

Всё! Новая версия опубликована и готова к использованию. Чуть позже мы отредактировали имя релиза, которое не обязано совпадать с используемым тегом, и добавили ссылку на упоминаемую выше статью с описанием решения поставленной задачи.


Заключение

На этом мы немного приостановимся и окинем взглядом проделанную работу. Мы не просто изучили технические аспекты работы с системой контроля версий, а прошли полный путь трансформации — от разрозненных правок к целостному и структурированному процессу управления кодом. Особое значение имеет освоенный нами финальный этап — оформление завершенных работ в виде официальных версий полноценного продукта, готового к представлению пользователям. Пусть наш конкретный рассматриваемый репозиторий ещё не достиг такого уровня зрелости, но мы предприняли все усилия, чтобы быть готовыми к подобному переходу.

Рассмотренный подход принципиально меняет восприятие проекта. Из простого набора исходных файлов он превращается в упорядоченную систему с четкой историей изменений и фиксации рабочих состояний системы, позволяющую в любой момент вернуться к стабильной версии. Это полезно всем — и разработчикам, и пользователям готовых решений.

Таким образом, овладение описанным инструментарием выводит работу с хранилищем MQL5 Algo Forge на качественно новый уровень, открывая возможности для более сложных и масштабных проектов в будущем.

Спасибо за внимание, до следующих встреч!
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Алгоритм Поиска Ворона — Crow Search Algorithm (CSA) Алгоритм Поиска Ворона — Crow Search Algorithm (CSA)
Алгоритм Поиска Ворона (CSA) — это элегантная метаэвристика, вдохновленная умением ворон прятать пищу и находить чужие тайники, которая решает задачи оптимизации через баланс между следованием за успешными решениями и случайным исследованием пространства поиска. Выясним, насколько алгоритм производителен.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Торгуем опционы без опционов (Часть 3): Сложные опционные стратегии Торгуем опционы без опционов (Часть 3): Сложные опционные стратегии
Рассматриваются флэтовые (не направленные) и трендовые (направленные) опционные стратегии и их реализация на MQL5. Модернизируется эксперт, написанный в предыдущей статье. Добавляется отображение опционных уровней. Теперь пора рассмотреть работу и реализовать те стратегии, которые используются на практике опционными трейдерами.