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

Моя цель показать Вам, как с помощью нескольких maven plugin-ов можно сделать вывод в лог Вашей java программы названия ветки и хэш коммита из Git. Это полезно при анализе логов, если у Вас давно не было деплоя и история Вашего инструмента CI затерлась.

Начало


В моем примере я буду использовать: java, maven, spring-core, git. Ссылка на пример. Предположим, что у нас уже имеется проект, написанный на Java и для сборки используется Maven, также он хранится на GitHub.

image


Maven SCM


Для того чтобы maven мог получить доступ к Вашему репозиторию, нужно указать ему методы и пути подключения. Для этого нужно использовать maven scm:

...
	</dependencies>

	<scm>
		<connection>scm:git:https://github.com/<your_username>/<your_projectname>.git</connection>
		<developerConnection>scm:git:https://github.com/<your_username>/<your_projectname>.git</developerConnection>
		<tag>HEAD</tag>
		<url>https://github.com/<your_username>/<your_projectname>.git</url>
	</scm>

	<build>
...

Ссылку для доступа формируется на веб-странице Git-а Вашего проекта в разделе Quick setup. Подробнее об использовании scm и вариантах получения доступа к репозиторию можно прочитать здесь и здесь.

Получение информации о текущей ветки git


Далее нам потребуется добавить maven plugin buildnumber-maven-plugin, который будет формировать buildNumber — хэш коммита и scmBranch — название ветки.

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>buildnumber-maven-plugin</artifactId>
	<version>1.4</version>
	<executions>
		<execution>
			<phase>validate</phase>
			<goals>
				<goal>create</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<doCheck>false</doCheck>
		<doUpdate>false</doUpdate>
	</configuration>
</plugin>

Формирование buildNumber и scmBranch происходит на этапе validate, который происходит после clean в Maven.

Параметры:

  • doCheck — если true, то проект не скомпилируется пока в нем есть локальные изменения
  • doUpdate — позволяет Maven обновить Вашу текущую ревизию

Больше информации об этом.

Работа с properties


После этого вы можете смело добавлять в Ваш application.properties файл следующие переменные: ${buildNumber} и ${scmBranch}

Например я в application.properties добавил следующие параметры:

branch.name=${scmBranch}
commit.hash=${buildNumber}
application.version=${project.version}

Чтобы наш application.properties был найдет, в тэг build нужно добавить:
<resources>		
  <resource>		
    <directory>src/main/resources</directory>		
    <filtering>true</filtering>		
   </resource>		
</resources>


Сборка проекта


Давайте теперь соберем наш проект командой:

mvn clean install -DskipTests

Если вы посмотрите лог, который Вам вывел maven при сборке, то заметите что он выполняет команду git для получения искомых данных:

[INFO] --- buildnumber-maven-plugin:1.4:create (default) @ GitRevision ---
[INFO] Executing: /bin/sh -c cd '/home/<user>/Development/GitRevision' && 'git' 'rev-parse' '--verify' 'HEAD'
[INFO] Working directory: /home/<user>/Development/GitRevision
[INFO] Storing buildNumber: 47c0d1df153b8610392d51d1a7fa0b7b39716e09 at timestamp: 1474375934082
[INFO] Storing buildScmBranch: master

Также Вы можете проверить корректность полученных данных в папке target/classes файл application.properties:

image

Содержимое:

branch.name=master
commit.hash=47c0d1df153b8610392d51d1a7fa0b7b39716e09
application.version=0.0.1-SNAPSHOT


Добавление в лог


Для получения свойств в любом классе, с которым работает spring используется аннотация @Value.

Пример:

public class LoggerExampleImpl implements LoggerExample {
    private static final Logger log = LoggerFactory.getLogger(LoggerExampleImpl.class);


    @Value("${branch.name}")
    private String branchName;

    @Value("${commit.hash}")
    private String commitHash;

    @Value("${application.version}")
    private String version;

    public void printLog() {
        log.info("Project version: {}, git branch: {}, commit hash: {}",
                version, branchName, commitHash);
    }
}

Пример класса с main методом:


public class GitRevisionApplication {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
		LoggerExample loggerExample = context.getBean(LoggerExampleImpl.class);
		loggerExample.printLog();
	}
}

Если мы запустим наше программу, то получим:

2016-09-20 17:06:07 INFO  [main] ClassPathXmlApplicationContext:581 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@66cd51c3: startup date [Tue Sep 20 17:06:07 EEST 2016]; root of context hierarchy
2016-09-20 17:06:07 INFO  [main] XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [spring.xml]
2016-09-20 17:06:07 INFO  [main] PropertyPlaceholderConfigurer:172 - Loading properties file from URL [file:/home/rado/Development/GitRevision/target/classes/application.properties]
2016-09-20 17:06:08 INFO  [main] LoggerExampleImpl:25 - Project version: 0.0.1-SNAPSHOT, git branch: master, commit hash: 52c05227fb27271314d80d39b5026193ff310f04

Сокращение хэша


Хэш нашего коммита слишком длинный, чтобы его урезать нужно указать в buildnumber-maven-plugin максимальное количество символов, которые мы хотим вывести:

    <configuration>
       <shortRevisionLength>5</shortRevisionLength>
    </configuration>

Расширить git данными MANIFEST.MF


Есть возможность добавить эти данные в MANIFEST.MF. Для этого нам необходимо подключить еще один maven plugin: maven-jar-plugin.

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<version>2.3.1</version>
	<configuration>
		<archive>
			<manifest>
				<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
				<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
			</manifest>
			<manifestEntries>
				<Implementation-Version>${project.version}-${buildNumber}</Implementation-Version>
				<Implementation-Build>${scmBranch}</Implementation-Build>
				<Main-Class>com.habrahabr.example.GitRevisionApplication</Main-Class>
			</manifestEntries>
		</archive>
	</configuration>
</plugin>

После сборки нашего проекта мы сможем увидеть в MANIFEST.MF:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: rado
Build-Jdk: 1.8.0_102
Specification-Title: GitRevision
Specification-Version: 0.0.1-SNAPSHOT
Implementation-Title: GitRevision
Implementation-Vendor-Id: com.habrahabr.example
Implementation-Build: master
Implementation-Version: 0.0.1-SNAPSHOT-47c0d1df153b8610392d51d1a7fa0b7
 b39716e09
Main-Class: com.habrahabr.example.GitRevisionApplication

Спасибо. Надеюсь, статья окажется полезна.
Поделиться с друзьями
-->

Комментарии (12)


  1. GreenStore
    22.09.2016 22:01

    Используем немного другой процесс, связанный с выпуском версий.

    Ключевые моменты нашего подхода:

    1. Номер версии — уникальный идентификатор. Если создается ветка, то это отражается в компоненте версии в виде уникального значения (например, «1.0.0.github»).

    2. Источник номера версии находится в файле «version.txt», который может быть легко прочитан скриптом автоматической сборки/дистрибуции и не зависит от системы сборки.

    3. Основанием для сборки является выпуск новой версии, при этом в Git-репозиторий добавляется метка (tag) со значением версии. Т.е. триггером сборки является появление метки. Метка является привычным маркером релиза для сервисов хостинга git-репозиториев.

    4. Для автоматизации переключения версии написана небольшая кросс-платформенная утилита «verctl» (см. https://github.com/gelsrc/verctl/). По ссылке есть и описание утилиты, если это интересно.

    Исходный код упомянутой утилиты является примером описанного подхода.

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


    1. Grief
      22.09.2016 22:45

      Это все разумно и понятно, но как быть с дев-билдами? Как отличить билд с фиксом от билда без фикса?


      1. GreenStore
        22.09.2016 23:10

        Это все разумно и понятно, но как быть с дев-билдами?

        Можно предложить разные варианты.

        1. Префикс метки Git: («v» — release, «d» — dev).

        2. Суффикс метки/версии (например, «1.0.0» — release, «1.0.0.dev» — dev).

        3. По количеству компонент версии (например, «1.0.0» — release, «1.0.0.0.0» — dev).

        Как отличить билд с фиксом от билда без фикса?

        Если делается ответвление от ранее выпущенной версии (накладывается на нее фикс), то это можно выделить увеличением числа компонент.

        Т.е. если базовая версия был «1.2.3», то на ее основе создается ветка «1.2.3.0», «1.2.3.1» и т.д.


        1. Grief
          23.09.2016 00:06

          Да нет, это понятно. Вопрос в том, что придется каждый коммит меткой снабжать или творить для каждого коммита ветки, а это лишняя работа в случае веток (которые в 95% случаев сливаются без проблем, а в 5 — оказываются причиной головной боли) или сомнительная польза в случае меток. Ну вот, например, dev-бранча, за неделю на нее пополо 2-3 новых фичи и 5-10 фиксов/мелких улучшений, каждый билд деплоится для тестирования. Практически каждый день — новый билд или два. Велика ли польза от меток на всех соответствующих коммитах, если можно вытащить автоматически хэш коммита и подтянуть в ui список изменений из git-лога автоматически?


          1. GreenStore
            23.09.2016 05:45

            Вопрос в том, что придется каждый коммит меткой снабжать или творить для каждого коммита ветки, а это лишняя работа в случае веток

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

            Польза от меток — в из читаемости и последовательности.

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


  1. mais
    22.09.2016 23:06

    Git flow решает такие проблемы, посмотрите перевод статьи https://habrahabr.ru/post/106912/


  1. osigida
    23.09.2016 08:05

    неплохо бы добавить в секцию build следующий блок:

    <resources>		
      <resource>		
        <directory>src/main/resources</directory>		
        <filtering>true</filtering>		
       </resource>		
    </resources>
    


    иначе application.property останется не процеснут и значения не будут подставленны


    1. scorpioa
      23.09.2016 08:06

      Спасибо, в проекте по ссылке на github это есть. Добавил в статью.


  1. Akon32
    23.09.2016 14:41

    Использую в качестве идентификатора билда строку, аналогичную полученной командами


    `git describe --tags || git log -n1 --pretty=%h || echo unknown`

    Команды вызываются через sbt, формируются ресурсы для программы и системы упаковки.
    Идентификатор билда не сформируется, если в PATH нет git, но у меня обычно есть.


    1. Heavoc
      23.09.2016 16:16

      git describe --tags
      

      Срабатывает если имеется метка. в ином случае вылет с ошибкой.

      Топик стартер почему-то не хотели или не продумали снабжать ветки метками.


      1. Akon32
        26.09.2016 11:54

        Срабатывает если имеется метка. в ином случае вылет с ошибкой.

        В случае ошибок вызываются следующие команды.


  1. killik
    19.01.2017 06:31

    У необслуживаемых аккумуляторов и не должно быть видимой поверхности электролита — у гелевых он весь содержится, как ни странно, в геле, а у AGM в стекловате, так что и взять пробу на анализ ареометром несколько затруднительно. В Вашем аккумуляторе скорее всего электролит элементарно замерз, а лед ток не проводит. Он бы скорее всего и сам отогрелся, без танцев с бубном :)