]> Lady’s Gitweb - Shushe/blobdiff - GNUmakefile
Make generator metadata more easy to override
[Shushe] / GNUmakefile
index 1bdc8b76fde13491b92c3c5399b9d27e313a2337..4f1fb5b52e538e1d882a8e6f8df0e0f8501c5dd6 100644 (file)
@@ -3,7 +3,7 @@ SHELL = /bin/sh
 # ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 override define makefileinfo
  ╭────────────────────────────╮
-╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞════════════════════════════════╗
+╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞════════════════════════════════
 ║╰────────────────────────────╯                                ║
 ╟┬ ¶ Prerequisites ───────────────────────────────────────────┬╢
 ║│                                                            │║
@@ -46,13 +46,14 @@ override define makefileinfo
 ║╰────────────────────────────────────────────────────────────╯║
 ╟┬ ¶ Usage ───────────────────────────────────────────────────┬╢
 ║│                                                            │║
-║│ • `make all´: Compile, but do not install, all files.      │║
+║│ • `make all´ (default): Compile, but do not install, all   │║
+║│   files.                                                   │║
 ║│                                                            │║
 ║│ • `make clean´: Remove `BUILDDIR´.                         │║
 ║│                                                            │║
 ║│ • `make gone´: Remove `BUILDDIR´ and installed files.      │║
 ║│                                                            │║
-║│ • `make help´ (default): Print this message.               │║
+║│ • `make help´: Print this message.                         │║
 ║│                                                            │║
 ║│ • `make install´: Compile all files and install in         │║
 ║│   `DESTDIR´.                                               │║
@@ -165,13 +166,26 @@ TRANSFORMS := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/transforms/*.xslt))
 # List of types which should be treated as X·M·L.
 XMLTYPES := application/xml text/xml
 
+# The name of the generator program.
+GENERATOR := ⛩️📰 书社
+
+ifdef GIT
+ifneq ($(wildcard $(THISDIR)/.git),)
+# A description of the current git revision of ⛩️📰 书社.
+VERSION := $(shell cd $(THISDIR); $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
+endif
+
+ifneq ($(wildcard .git),)
+# A description of the current git revision of the working directory.
+SRCREV := $(shell $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
+endif
+endif
+
 # Set to a non·empty value to print all commands as they run.
 VERBOSE :=
 
 # The default target for this makefile.
-#
-# Print help and exit.
-.DEFAULT_GOAL := help
+.DEFAULT_GOAL := all
 
 # ━ § BEGIN MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
@@ -202,7 +216,7 @@ override silent := $(if $(VERBOSE),,@)
 override sedesc = $(subst /,[/],$(subst $$,\$$,$(subst *,\*,$(subst .,\.,$(subst [,\[,$(subst ^,\^,$(subst \,\\,$1)))))))
 
 # (callable) Percent‐decode the given strings.
-override perdec = $(foreach encoded,$1,$(shell $(PRINTF) '%s\n' $(call quote,$(encoded)) | $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0-9A-Fa-f]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0-9A-Fa-f]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf("%s",$$0)}/%/{sub("%","0x");printf("\\%04o",$$0)}' | $(XARGS) -0 $(PRINTF) '%b'))
+override perdec = $(foreach encoded,$1,$(shell $(PRINTF) '%s\n' $(call quote,$(encoded)) | $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0-9A-Fa-f]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0-9A-Fa-f]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf "%s",$$0}/%/{sub("%","0x");cmd="$(XARGS) $(PRINTF) \"%04o\"";printf "%s","\\";printf "%s",$$0|cmd;close(cmd)}' | $(XARGS) -0 $(PRINTF) '%b'))
 
 # (callable) Percent‐encode the given strings.
 #
@@ -221,18 +235,6 @@ sourceincludes := $(shell $(FIND) $(foreach dir,$(INCLUDEDIR),$(call quote,$(dir
 # (overridable) Collect all of the applicable source files from the source directory, removing any which are also includes.
 sourcefiles := $(filter-out $(sourceincludes),$(shell $(FIND) $(foreach dir,$(SRCDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f))
 
-ifdef GIT
-ifneq ($(wildcard $(THISDIR)/.git),)
-# (overridable) The name of the current revision of ⛩️📰 书社, or its hash if the current revision is not a tag.
-thisrev = $(shell cd $(THISDIR); $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
-endif
-
-ifneq ($(wildcard .git),)
-# (overridable) The name of the current revision of the working directory, or its hash if the current revision is not a tag.
-srcrev = $(shell $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
-endif
-endif
-
 # Figure out the file type of each source file and source include.
 ifneq ($(wildcard $(BUILDDIR)/magic.mgc),)
 override types := $(shell $(SED) 's/^ *//;s/ *$$//;s/ {2,}/ /g' <<< $(call quote,$(sourcefiles) $(sourceincludes)) | $(TR) ' ' '\n' | $(FILE) -m $(call quote,$(BUILDDIR)/magic.mgc) --mime-type --separator '|' --files-from - | $(SED) 's/| */|/g')
@@ -301,7 +303,7 @@ override unparsed = $(foreach file,$1,$(patsubst %|$(file),%,$(filter %|$(file),
 
 ifneq ($(wildcard $(BUILDDIR)/dependencies),)
 # Pair each file with a list of dependencies for it.
-override dependenciesforfile := $(foreach file,$(sourcefiles),$(file)|$(subst $(space),|,$(shell $(CAT) $(call quote,$(BUILDDIR)/dependencies) | $(SED) $(call quote,/^$(call sedesc,$(call localuri,$(file)))$$/$(comma)/^[^  ]/!d;/^ /!d;s/^ //))))
+override dependenciesforfile := $(foreach file,$(filter-out $(assetfiles),$(sourcefiles) $(sourceincludes)),$(file)|$(subst $(space),|,$(shell $(CAT) $(call quote,$(BUILDDIR)/dependencies) | $(SED) $(call quote,/^$(call sedesc,$(call localuri,$(file)))$$/$(comma)/^[^    ]/!d;/^ /!d;s/^ //))))
 
 # (callable) Get the list of dependency leiris for the given source files.
 #
@@ -361,45 +363,45 @@ override wrapplaintext = $(PRINTF) '%s\n' "$$($(PRINTF) '%b' '<?xml version=\042
 
 # ─ ¶ Phony Targets ───────────────────────────────────────────────────
 
-# Provide help.
-help:
-       @$(PRINTF) '%b' '$(subst $(newline),\n,$(makefileinfo))'
-
 # Compile all files, or error if any are recursive.
-all: $(call compiled,$(recursivefiles) $(compilablefiles));
+all : $(call compiled,$(recursivefiles) $(compilablefiles)) ;
 
 # Destroy buildfiles.
-clean:
+clean :
        $(if $(BUILDDIR),$(silent)$(RM) -rf $(call quote,$(BUILDDIR)/),)
 
 # Destroy build directory and installed files.
-gone: clean uninstall;
+gone : clean uninstall ;
+
+# Provide help.
+help :
+       @$(PRINTF) '%b' '$(subst $(newline),\n,$(makefileinfo))'
 
 # Install the compiled files into `DESTDIR´, or error if any are recursive.
-install: $(call installed,$(recursivefiles) $(installablefiles));
+install : $(call installed,$(recursivefiles) $(installablefiles)) ;
 
 # List all source files and includes and their computed types.
-list:
+list :
        @$(PRINTF) '%b' $(call quote,$(foreach file,$(sort $(sourcefiles)) $(sort $(sourceincludes)),\0033[1m$(file)\0033[22m|$(call typeoffile,$(file))|[\0033[3m$(if $(filter $(file),$(xmlfiles)),xml,$(if $(filter $(file),$(plaintextfiles)),text,asset))$(if $(filter $(file),$(sourceincludes)),|include,)\0033[23m]$(if $(call dependencies,$(file))$(call recursives,$(file)), $(strip $(foreach recursive,$(call recursives,$(file)),\0033[93;41m•|Recursive|Dependency|\0033[39;49m|$(recursive)) $(foreach dependency,$(call dependencies,$(file)),\0033[2m•|Dependency|\0033[22m|$(dependency))))$(if $(filter $(file),$(sourcefiles)), →|<\0033[4m/$(call destination,$(file))\0033[24m>,) )) | $(TR) ' |' '\n '
 
-# Destroy build directory and installed files.
-uninstall:
+# Destroy installed files.
+uninstall :
        $(foreach file,$(installablefiles),$(if $(wildcard $(call installed,$(file))),$(silent)$(PRINTF) '%s\n' $(call quote,Removing </$(patsubst $(DESTDIR)/%,%,$(call installed,$(file)))>…)$(newline)$(silent)$(RM) -f $(call quote,$(call installed,$(file)))$(newline),))
 
 # Raise an error when attempting to build any files with recursive dependencies.
-$(call compiled,$(recursivefiles)):
+$(call compiled,$(recursivefiles)) :
        @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$(call uncompiled,$@)´ has recursive dependencies:\n$(subst |, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(call uncompiled,$@)),•|$(recursive))))) && false
 
 # ─ ¶ Special Targets ─────────────────────────────────────────────────
 
 # Perform secondary expansion; this enables pattern rules to determine their prerequisites based on the matched pattern.
-.SECONDEXPANSION: ;
+.SECONDEXPANSION : ;
 
 # Don’t use any implicit rules.
-.SUFFIXES: ;
+.SUFFIXES : ;
 
 # Phony rules; always consider these out·of·date.
-.PHONY: all default clean gone info install list uninstall $(call compiled,$(recursivefiles));
+.PHONY : all default clean gone info install list uninstall $(call compiled,$(recursivefiles)) ;
 
 ifneq ($(wildcard $(BUILDDIR)/.update-types)$(wildcard $(BUILDDIR)/dependencies)$(wildcard $(BUILDDIR)/destinations),)
 # Reload this make·file if the dependency graph or output destinations have changed.
@@ -407,7 +409,7 @@ ifneq ($(wildcard $(BUILDDIR)/.update-types)$(wildcard $(BUILDDIR)/dependencies)
 # The dependency graph and output destinations are used to set the values of variables in this make·file, so it’s important to ensure that they are actually up·to·date prior to executing any later rules.
 #
 # This recipe only exists after types have been updated or when the dependency graph or destinations file already exists.
-$(THISDIR)/GNUmakefile:: $(BUILDDIR)/dependencies $(BUILDDIR)/destinations
+$(THISDIR)/GNUmakefile :: $(BUILDDIR)/dependencies $(BUILDDIR)/destinations
        $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
        $(silent)$(RM) -f $(BUILDDIR)/.update-types
        @$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m'
@@ -419,7 +421,7 @@ ifeq ($(wildcard $(BUILDDIR)/.update-types),)
 # These are used to classify source files, so if they have changed then the make·file must be reloaded.
 #
 # This recipe sleeps for one second to ensure that files built after the restart have a more current time·stamp.
-$(THISDIR)/GNUmakefile:: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt
+$(THISDIR)/GNUmakefile :: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt
        $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
        $(silent)$(RM) -f $(call quote,$(BUILDDIR)/dependencies) $(call quote,$(BUILDDIR)/destinations)
        @$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m'
@@ -431,7 +433,7 @@ endif
 
 # Create symbolic links from the build directory’s store of magic files
 # to their corresponding sources.
-$(call magicfile,$(MAGIC)): $(BUILDDIR)/magic/%: $$(call magicsource,$$@)
+$(call magicfile,$(MAGIC)) : $(BUILDDIR)/magic/%: $$(call magicsource,$$@)
        $(silent)$(call ensuredirectory,$(dir $@))
        $(silent)$(LN) -sf $(call quote,$(realpath $<)) $(call quote,$@)
 
@@ -439,14 +441,14 @@ $(call magicfile,$(MAGIC)): $(BUILDDIR)/magic/%: $$(call magicsource,$$@)
 #
 # It must be updated if any of the files in the magic directory change.
 # It ⁜also⁜ should be updated if any of the files in the magic directory are deleted, but this isn’t tracked presently.
-$(BUILDDIR)/magic.mgc: $(call magicfile,$(MAGIC))
+$(BUILDDIR)/magic.mgc : $(call magicfile,$(MAGIC))
        $(foreach outdated,$(filter-out $^,$(wildcard $(BUILDDIR)/magic/*)),$(silent)$(RM) $(call quote,$(outdated))$(newline))
        @$(ECHO) "Compiling new magic…"
        $(silent)$(call ensuredirectory,$(dir $@))
        $(silent)cd $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(BUILDDIR)/magic))
 
 # Generate the main parser.
-$(BUILDDIR)/parser.catalog: $(PARSERS)
+$(BUILDDIR)/parser.catalog : $(PARSERS)
        @$(ECHO) "Generating catalog of parsers…"
        $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
        $(foreach parser,$(PARSERS),$(silent)$(XMLCATALOG) --add uri $(call quote,$(call id,$(parser),parser)) $(call quote,file:///$(call pathenc,$(abspath $(parser)))) --noout $(call quote,$@)$(newline))
@@ -458,21 +460,21 @@ $(BUILDDIR)/parser.xslt: $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2parse
 #
 # Even plain X·M·L files are parsed, because they may contain X·H·T·M·L `<script>´ elements which contain other kinds of data.
 # Asset files are turned into H·T·M·L embeds pointing to `data:´ U·R·I’s.
-$(call parsed,$(sourcefiles) $(sourceincludes)): %: $$(call unparsed,$$@) $(typeupdates)
+$(call parsed,$(sourcefiles) $(sourceincludes)) : % : $$(call unparsed,$$@) $(typeupdates)
        @$(PRINTF) '%s\n' $(call quote,Processing `$<´…)
        $(silent)$(call ensuredirectory,$(dir $@))
-       $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,<object xmlns="http://www.w3.org/1999/xhtml" type="$(call typeoffile,$<)" data="$(call datauri,$<)"/>) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(CAT) $(call quote,$<)) | $(XSLTPROC) -o $(call quote,$@) --stringparam BUILDTIME '$(shell TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ')' --stringparam SRCTIME '$(shell TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$(call unparsed,$@)))'$(if $(thisrev), --stringparam VERSION $(call quote,$(thisrev)),)$(if $(srcrev), --stringparam SRCREV $(call quote,$(srcrev)),) $(call quote,$(BUILDDIR)/parser.xslt) -)
+       $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,<object xmlns="http://www.w3.org/1999/xhtml" type="$(call typeoffile,$<)" data="$(call datauri,$<)"/>) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(CAT) $(call quote,$<)) | $(XSLTPROC) -o $(call quote,$@) --stringparam BUILDTIME '$(shell TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ')' --stringparam SRCTIME '$(shell TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$(call unparsed,$@)))'$(if $(GENERATOR), --stringparam GENERATOR $(call quote,$(GENERATOR)))$(if $(VERSION), --stringparam VERSION $(call quote,$(VERSION)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/parser.xslt) -)
 
 # Generate a catalog of all parsed files, for use when processing includes.
 #
 # This does not depend on actually transforming the files.
-$(BUILDDIR)/catalog: $(sourcefiles) $(sourceincludes) $(typeupdates)
+$(BUILDDIR)/catalog : $(sourcefiles) $(sourceincludes) $(typeupdates)
        @$(ECHO) "Generating catalog of parsed files…"
        $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
        $(foreach source,$(sourcefiles) $(sourceincludes),$(silent)$(XMLCATALOG) --add uri $(call quote,$(call localuri,$(source))) $(call quote,file:///$(call pathenc,$(abspath $(call parsed,$(source))))#$(if $(filter $(source),$(assetfiles)),asset,xml)) --noout $(call quote,$@)$(newline))
 
 # Build a list of dependencies for each parsed file.
-$(BUILDDIR)/dependencies: $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $(xmlfiles)) $(THISDIR)/lib/catalog2dependencies.xslt
+$(BUILDDIR)/dependencies : $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $(xmlfiles)) $(THISDIR)/lib/catalog2dependencies.xslt
        @$(ECHO) "Identifying dependencies…"
        $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2dependencies.xslt) $(call quote,$<)
 
@@ -480,31 +482,31 @@ $(BUILDDIR)/dependencies: $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $(
 #
 # This depends on parsing non·asset source files, but not assets or includes.
 # It does not require knowing the dependencies.
-$(BUILDDIR)/destinations: $(BUILDDIR)/catalog $(call parsed,$(filter-out $(assetfiles),$(sourcefiles))) $(THISDIR)/lib/catalog2destinations.xslt
+$(BUILDDIR)/destinations : $(BUILDDIR)/catalog $(call parsed,$(filter-out $(assetfiles),$(sourcefiles))) $(THISDIR)/lib/catalog2destinations.xslt
        @$(ECHO) "Identifying output destinations…"
        $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2destinations.xslt) $(call quote,$<)
 
 # Generate the main transform.
-$(BUILDDIR)/transform.catalog: $(TRANSFORMS)
+$(BUILDDIR)/transform.catalog : $(TRANSFORMS)
        @$(ECHO) "Generating catalog of transforms…"
        $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
        $(foreach transform,$(TRANSFORMS),$(silent)$(XMLCATALOG) --add uri $(call quote,$(call id,$(transform),transform)) $(call quote,file:///$(call pathenc,$(abspath $(transform)))) --noout $(call quote,$@)$(newline))
-$(BUILDDIR)/transform.xslt: $(BUILDDIR)/transform.catalog $(THISDIR)/lib/catalog2transform.xslt
+$(BUILDDIR)/transform.xslt : $(BUILDDIR)/transform.catalog $(THISDIR)/lib/catalog2transform.xslt
        @$(ECHO) "Generating main transform…"
        $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2transform.xslt) $(call quote,$<)
 
 # Generate the output files using the dependencies as necessary.
-$(call compiled,$(compilablefiles)): $(BUILDDIR)/public/%: $$(call parsed,$$(call uncompiled,$$@)) $(BUILDDIR)/transform.xslt $$(call parsed,$$(call dependencies,$$(call uncompiled,$$@)))
+$(call compiled,$(compilablefiles)) : $(BUILDDIR)/public/% : $$(call parsed,$$(call uncompiled,$$@)) $(BUILDDIR)/transform.xslt $$(call parsed,$$(call dependencies,$$(call uncompiled,$$@)))
        $(silent)$(call ensuredirectory,$(dir $@))
        @$(PRINTF) '%s\n' $(call quote,Compiling </$*>…)
-       $(silent)$(XSLTPROC) -o $(call quote,$@) --stringparam CATALOG 'catalog' --stringparam BUILDTIME '$(shell TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ')' --stringparam SRCTIME '$(shell TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$(call uncompiled,$@)))' --stringparam PATH $(call quote,/$*)$(if $(thisrev), --stringparam VERSION $(call quote,$(thisrev)),)$(if $(srcrev), --stringparam SRCREV $(call quote,$(srcrev)),) $(call quote,$(BUILDDIR)/transform.xslt) $(call quote,$<)
-$(call compiled,$(filter $(assetfiles),$(sourcefiles))): $(BUILDDIR)/public/%: $$(call uncompiled,$$@)
+       $(silent)$(XSLTPROC) -o $(call quote,$@) --stringparam CATALOG 'catalog' --stringparam BUILDTIME '$(shell TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ')' --stringparam SRCTIME '$(shell TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$(call uncompiled,$@)))' --stringparam PATH $(call quote,/$*)$(if $(GENERATOR), --stringparam GENERATOR $(call quote,$(GENERATOR)))$(if $(VERSION), --stringparam VERSION $(call quote,$(VERSION)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/transform.xslt) $(call quote,$<)
+$(call compiled,$(filter $(assetfiles),$(sourcefiles))) : $(BUILDDIR)/public/% : $$(call uncompiled,$$@)
        @$(PRINTF) '%s\n' $(call quote,Compiling </$*>…)
        $(silent)$(call ensuredirectory,$(dir $@))
        $(silent)$(CP) $(call quote,$<) $(call quote,$@)
 
 # Install compiled files (or error in the case of recursive ones).
-$(call installed,$(filter $(assetfiles),$(sourcefiles)) $(recursivefiles) $(compilablefiles)): $(DESTDIR)/%: $(BUILDDIR)/public/%
+$(call installed,$(filter $(assetfiles),$(sourcefiles)) $(recursivefiles) $(compilablefiles)) : $(DESTDIR)/% : $(BUILDDIR)/public/%
        @$(PRINTF) '%s\n' $(call quote,Installing </$*>…)
        $(silent)$(call ensuredirectory,$(dir $@))
        $(silent)$(CP) $(call quote,$<) $(call quote,$@)
This page took 0.029558 seconds and 4 git commands to generate.