Giter VIP home page Giter VIP logo

git-revisions's Introduction

git-revisions

Deploy to Clojars Clojars Project cljdoc badge

Generate software revision strings based on Git and system context data.

Quick Start

This repository describes the use of git-revisions core!

What you are probably really looking for is a build tool specific library. For one, see

Reading core documentation will be helpful, tool specific documentation exists in the library repositories.

Table of Contents

Table of contents generated with markdown-toc

Glossary

  • lookup Function which can resolve a value based on given keyword's namespace.
  • revision Alternate word for version chosen for semantics; this plugin can in theory support any revisioning scheme, not just common versioning schemes, so it fits better
  • segment Each revision string consists of one or more segments. A segment is defined by whether it is required or optional and its content is further specified by parts.
  • part is a single token in revision segment, such as MAJOR version number in Semantic Versioning.
  • resolution/generation The general process of producing the revision string.

Usage

Basic usage of the core library is

(require '[git-revisions.core :as revisions])

(def configuration {})  ; content described below

(defn get-revision
  [configuration]
  (let [{:keys [format adjust revision-file]} configuration]
    (revisions/revision-generator format adjust (when (some? revision-file)
                                                  {:output-path  revision-file
                                                   :project-root (:root ".")}))))

Exact utilization depends on the used build tool, which is why the helper libraries exist.

Prerequisites

Project work directory is a Git repository

No way around it, at least git init must have been run in the directory.

Initial revision tag must already exist in repository

If no tags exist yet, add an all-zeroes tag such as v0.0.0 to any commit:

git tag -a v0.0.0 -m "initial version"

If you're not sure, you can check if a suitable tag exists with the command

git describe --tags --dirty --long
v3.0.1-28-gf2e808057  # this repository has the tag v3.0.1

Suitable format for the tag is dependent on Configuration/Tag Pattern.

Resolution logic

There's roughly three phases in the plugin's resolution process:

  1. Lookup generation and gathering. The tool starts by creating lookup sources which can then be referenced in the configuration.
  2. Revision string resolution. The segments and parts of the revision are resolved using the lookups and some limited crossreferencing to produce the final revision string.
  3. Emit metadata. Return next revision string, output metadata file if configured.

Available lookups

All lookups are referenced as namespaced keywords, wherein the namespace defines the kind of lookup to be done. For example :env/user looks up the value of environment variable USER.

More in-depth documentation for lookups is available in Available Lookups.

Configuration

Top-level configuration is as follows:

{:format        ...  ; required
 :adjust        ...  ; optional
 :revision-file ...  ; optional
 }

Format (:format)

Use either a predefined built-in pattern:

{:format :semver}

or define your own:

{:format {:tag-pattern ...
          :pattern     ...
          :adjustments ...
          :constants   ...}}
  • :semver is built-in configuration set for the Semantic Versioning scheme.
  • :commit-hash is built-in configuration for using Git commit hash as-is as the version string.

You can either use a built-in pattern or make your own. Built-ins are heavily opinionated, so if you have a bit of time, it is highly recommended for you to define your own.

The built-ins are documented separately in Built-in formats, instructions for defining your own are below.

More built-in formats as contributions are more than welcome!

Tag Pattern (:tag-pattern)

Regular expression with named groups. Used as lookup source for :rev/* keys.

; use any matching Git tag as-is as direct revision pattern
{:format {:tag-pattern #"(?<everything>.+)$"
          :pattern     [:segment/always [:rev/everything]]}}
Revision pattern (:pattern)

Vector of segments expressed as ordered pairing of segment directives and segment parts for the pattern.

  • :segment/always is always included in the revision string
  • :segment/when inspects the first value of the vector and based on its truthiness includes the following parts in the resulting revision string
  • :segment/when-not complement of :segment/when
  • Strings are always included as-is
{:format {:pattern [:segment/when     [:env/kenobi "Hello there."]
                    :segment/when     [:env/grievous " General Kenobi!"]]}}
; When environment variable KENOBI is set, produces "Hello there."
; When environment variable GRIEVOUS is set, produces "General Kenobi!"
; When neither are set, an empty string is produced.
; When both are set, both are included as is; "Hello there. General Kenobi!"

or more practical

; only CI can build non-snapshots, optionally including pre-release tag
{:format {:pattern [:segment/when-not [:env/ci "-SNAPSHOT"]
                    :segment/when     [:env/mylibrary_prerelease "-" :env/mylibrary_prerelease]]}}
Revision adjustments (:adjustments)

Map of labeled adjustments to be executed conditionally during resolution based on lookup context.

{:format {:tag-pattern #"(?<numbers>\d.+)$"
          :pattern     [:segment/always [:rev/numbers]]
          :adjustments {:bump {:rev/numbers :inc}}}
 :adjust [:env/project_next_revision :bump]}

The adjust selector controls the revision adjustment done during resolving. In the example above the :adjust selector is set to look up the value first from environment variable PROJECT_NEXT_VERSION and if such isn't present, use the :bump adjustment instead.

All parts referenced in the revision pattern can be adjusted.

For example Semantic Versioning Specification item 8 declares that when the MAJOR part is incremented, MINOR and PATCH parts must be reset to zero. This can be expressed - and in fact is in the built-in configuration with

{:format {...
          :adjustments {:major {:rev/major :inc :rev/minor :clear :rev/patch :clear}}
          ...}}

Available operations for adjustments are

  • :inc Increment numeric part by one
  • :clear Always use 0 as value
Common constants (:constants)

Define constants to be used for revision string resolution.

{:format {:pattern   [:segment/always [:constants/coca-cola]]
          :constants {:coca-cola "Pepsi"}}}
; resulting revision is always "Pepsi"

Adjust selector (:adjust)

Control the modification of the revision string based on external information, such as environment variable set by Continuous Integration. Should be set so that last value is some known adjustment to ensure the revision automatically rolls onwards. Without :adjust the revision will be stuck in whatever tag was previously defined defeating the purpose of the tool.

{:format {:tag-pattern #"v(?<numbers>\d.+)$"
          :pattern     [:segment/always "v" [:rev/numbers]]
          :adjustments {:bump {:rev/numbers :inc}}}
 :adjust [:bump]}
; For tag v27 produces revision v28

Revision metadata output file (:revision-file)

Capture plugin's metadata into a file and use it as static source for metadata API endpoint, or with another plugin, in template substitutions or whatever else you come up with.

{:revision-file "resources/metadata.edn"
 ...}

Git metadata and the produced version string are written to the file.

The given path must resolve as absolute path within the current project root.

Motivation, prior art and differences

Using Git tags to control project version isn't a new problem and by no means an unsolved issue, in fact there are alternative libraries already available which solve this same issue but in a different way:

The main two differences compared to the ones above are

  1. Entirely data/configuration driven, admittedly overengineered, as probably is obvious if you made it this far
  2. Zero interaction after initial Git tagging

Additional motivator in creating our own was including the ability to alter specific parts of the revision string in automatic, externally configured fashion.

Acknowledgements

All the contributors to the other plugins, in no specific order, non-exhaustively:

License

Copyright © 2022 Esko Suomi

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.

This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.

git-revisions's People

Contributors

esuomi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

git-revisions's Issues

Add support for CalVer patterns

CalVer is a date-based versioning scheme which defines a set of common patterns for expressing parts.

The most sensible implementation probably would be a :calver/* lookup with some example patterns/built-ins. In addition having a set of generic datetime lookups as well (probably under :dt/*) would be a fine addition as well.

Refactor plugin to use non-deprecated Leiningen plugin functionality

The current method used for implementation is deprecated and may disappear in the future, so in lieu of future proofing this plugin, that of course needs to be resolved.

The most sensible way to do this is to in fact split the Leiningen plugin wrapper to its own project and the core of the plugin to its own. This also opens up the door for alternative build tool implementations.

Implement separate release workflow

To avoid spamming Clojars too much there should be a separate release workflow based on some branching strategy for the releases with some sensible defaults, eg. LEIN_REVISIONS_ADJUSTMENTS being set to minor by default unless otherwise specified.

Replace cuddlefish with something built-in to better extract the parent version tag

Current solutions is based entirely on output of git describe --tags --dirty --long which is a carry-over from using cuddlefish library; this works but may be error prone in cases where repositories have tags for other purposes as well.

Something else should be used instead which lists all tags and uses the :tag-pattern to match and extract suitable revision metadata.

Document built-in pattern `:semver`

As it says on the subject. Even though it's kinda self explainatory, it really isn't, since it's controlled by a few magical environment variables.

Running the plugin on empty/untagged repository produces unhelpful exception being thrown

As seen below, this should be caught and handled so that a human friendly message is printed.

java.lang.NullPointerException: Cannot invoke "java.lang.CharSequence.length()" because "s" is null
 at clojure.string$trim.invokeStatic (string.clj:238)
    clojure.string$trim.invoke (string.clj:234)
    lein_git_revisions.plugin$git_context$fn__763.invoke (plugin.clj:187)
    clojure.lang.PersistentVector.reduce (PersistentVector.java:343)
    clojure.core$reduce.invokeStatic (core.clj:6829)
    clojure.core$reduce.invoke (core.clj:6812)
    lein_git_revisions.plugin$git_context.invokeStatic (plugin.clj:184)
    lein_git_revisions.plugin$git_context.invoke (plugin.clj:157)
    lein_git_revisions.plugin$revision_generator.invokeStatic (plugin.clj:215)
    lein_git_revisions.plugin$revision_generator.invoke (plugin.clj:208)
    lein_git_revisions.plugin$middleware.invokeStatic (plugin.clj:249)
    lein_git_revisions.plugin$middleware.invoke (plugin.clj:242)
    clojure.lang.Var.invoke (Var.java:384)
    leiningen.core.project$fn__7990.invokeStatic (project.clj:843)
    leiningen.core.project/fn (project.clj:843)
    clojure.lang.AFn.applyToHelper (AFn.java:156)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$memoize$fn__6894.doInvoke (core.clj:6342)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$apply.invoke (core.clj:662)
    leiningen.core.project$memoize_when$fn__7987.doInvoke (project.clj:838)
    clojure.lang.RestFn.invoke (RestFn.java:421)
    leiningen.core.project$apply_middleware.invokeStatic (project.clj:854)
    leiningen.core.project$apply_middleware.invoke (project.clj:846)
    clojure.core.protocols$fn__8181.invokeStatic (protocols.clj:168)
    clojure.core.protocols/fn (protocols.clj:124)
    clojure.core.protocols$fn__8136$G__8131__8145.invoke (protocols.clj:19)
    clojure.core.protocols$seq_reduce.invokeStatic (protocols.clj:31)
    clojure.core.protocols$fn__8168.invokeStatic (protocols.clj:75)
    clojure.core.protocols/fn (protocols.clj:75)
    clojure.core.protocols$fn__8110$G__8105__8123.invoke (protocols.clj:13)
    clojure.core$reduce.invokeStatic (core.clj:6830)
    clojure.core$reduce.invoke (core.clj:6812)
    leiningen.core.project$apply_middleware.invokeStatic (project.clj:848)
    leiningen.core.project$apply_middleware.invoke (project.clj:846)
    leiningen.core.project$activate_middleware.invokeStatic (project.clj:880)
    leiningen.core.project$activate_middleware.invoke (project.clj:876)
    leiningen.core.project$set_profiles.invokeStatic (project.clj:969)
    leiningen.core.project$set_profiles.doInvoke (project.clj:962)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    leiningen.core.project$init_project.invokeStatic (project.clj:1044)
    leiningen.core.project$init_project.invoke (project.clj:1019)
    leiningen.core.project$read.invokeStatic (project.clj:1115)
    leiningen.core.project$read.invoke (project.clj:1112)
    leiningen.core.project$read.invokeStatic (project.clj:1116)
    leiningen.core.project$read.invoke (project.clj:1112)
    leiningen.core.main$_main$fn__7429.invoke (main.clj:448)
    leiningen.core.main$_main.invokeStatic (main.clj:442)
    leiningen.core.main$_main.doInvoke (main.clj:439)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.main$main_opt.invokeStatic (main.clj:514)
    clojure.main$main_opt.invoke (main.clj:510)
    clojure.main$main.invokeStatic (main.clj:664)
    clojure.main$main.doInvoke (main.clj:616)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:40)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.