@@ -2,6 +2,7 @@ package main
22
33import (
44 "fmt"
5+ "log"
56 "os"
67 "os/exec"
78 "regexp"
@@ -13,39 +14,79 @@ import (
1314var (
1415 // describeRegexp parses the count and revision part of the “git describe --long” output.
1516 describeRegexp = regexp .MustCompile (`-\d+-g([0-9a-f]+)\s*$` )
17+
18+ // semverRegexp checks if a string is a valid Go semver,
19+ // from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
20+ // with leading "v" added.
21+ semverRegexp = regexp .MustCompile (`^v(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` )
1622)
1723
1824// TODO: also support other VCS
19- func pkgVersionFromGit (gitdir string ) (string , error ) {
20- cmd := exec .Command ("git" , "describe" , "--exact-match" , "--tags" )
25+ func pkgVersionFromGit (gitdir string , forcePrerelease bool ) (version string , hasRelease , isRelease bool , err error ) {
26+ var latestTag string
27+ var commitsAhead int
28+
29+ // Find @latest version tag (whether annotated or not)
30+ cmd := exec .Command ("git" , "describe" , "--abbrev=0" , "--tags" )
2131 cmd .Dir = gitdir
22- if tag , err := cmd .Output (); err == nil {
23- version := strings .TrimSpace (string (tag ))
24- if strings .HasPrefix (version , "v" ) {
25- version = version [1 :]
32+ if out , err := cmd .Output (); err == nil {
33+ latestTag = strings .TrimSpace (string (out ))
34+ hasRelease = true
35+ log .Printf ("Found latest tag %q" , latestTag )
36+
37+ if ! semverRegexp .MatchString (latestTag ) {
38+ log .Printf ("WARNING: Latest tag %q is not a valid SemVer version\n " , latestTag )
39+ // TODO: Enforce strict sementic versioning with leading "v"?
40+ }
41+
42+ // Count number of commits since @latest version
43+ cmd = exec .Command ("git" , "rev-list" , "--count" , latestTag + "..HEAD" )
44+ cmd .Dir = gitdir
45+ out , err := cmd .Output ()
46+ if err != nil {
47+ return "" , true , false , err
48+ }
49+ commitsAhead , err = strconv .Atoi (strings .TrimSpace (string (out )))
50+ if err != nil {
51+ return "" , true , false , err
52+ }
53+
54+ if commitsAhead == 0 {
55+ // Equivalent to "git describe --exact-match --tags"
56+ log .Printf ("Latest tag %q matches master" , latestTag )
57+ } else {
58+ log .Printf ("INFO: master is ahead of %q by %v commits" , latestTag , commitsAhead )
59+ }
60+
61+ version = strings .TrimPrefix (latestTag , "v" )
62+ isRelease = true
63+
64+ if forcePrerelease {
65+ log .Printf ("INFO: Force packaging master (prerelease) as requested by user" )
66+ } else {
67+ return version , hasRelease , isRelease , nil
2668 }
27- return version , nil
2869 }
2970
71+ // Packaging @master (prerelease)
72+
73+ // 1.0~rc1 < 1.0 < 1.0+b1, as per
74+ // https://www.debian.org/doc/manuals/maint-guide/first.en.html#namever
75+ mainVer := "0.0~"
76+ if hasRelease {
77+ mainVer = version + "+"
78+ }
79+
80+ // Find committer date, UNIX timestamp
3081 cmd = exec .Command ("git" , "log" , "--pretty=format:%ct" , "-n1" )
3182 cmd .Dir = gitdir
3283 lastCommitUnixBytes , err := cmd .Output ()
3384 if err != nil {
34- return "" , err
85+ return "" , hasRelease , isRelease , err
3586 }
3687 lastCommitUnix , err := strconv .ParseInt (strings .TrimSpace (string (lastCommitUnixBytes )), 0 , 64 )
3788 if err != nil {
38- return "" , err
39- }
40-
41- // Find the most recent tag (whether annotated or not)
42- cmd = exec .Command ("git" , "describe" , "--abbrev=0" , "--tags" )
43- cmd .Dir = gitdir
44- // 1.0~rc1 < 1.0 < 1.0+b1, as per
45- // https://www.debian.org/doc/manuals/maint-guide/first.en.html#namever
46- lastTag := "0.0~"
47- if lastTagBytes , err := cmd .Output (); err == nil {
48- lastTag = strings .TrimPrefix (strings .TrimSpace (string (lastTagBytes )), "v" ) + "+"
89+ return "" , hasRelease , isRelease , err
4990 }
5091
5192 // This results in an output like 4.10.2-232-g9f107c8
@@ -60,19 +101,19 @@ func pkgVersionFromGit(gitdir string) (string, error) {
60101 cmd .Stderr = os .Stderr
61102 revparseBytes , err := cmd .Output ()
62103 if err != nil {
63- return "" , err
104+ return "" , hasRelease , isRelease , err
64105 }
65106 lastCommitSha = strings .TrimSpace (string (revparseBytes ))
66107 } else {
67108 submatches := describeRegexp .FindSubmatch (describeBytes )
68109 if submatches == nil {
69- return "" , fmt .Errorf ("git describe output %q does not match expected format" , string (describeBytes ))
110+ return "" , hasRelease , isRelease , fmt .Errorf ("git describe output %q does not match expected format" , string (describeBytes ))
70111 }
71112 lastCommitSha = string (submatches [1 ])
72113 }
73- version : = fmt .Sprintf ("%sgit%s.%s" ,
74- lastTag ,
114+ version = fmt .Sprintf ("%sgit%s.%s" ,
115+ mainVer ,
75116 time .Unix (lastCommitUnix , 0 ).UTC ().Format ("20060102" ),
76117 lastCommitSha )
77- return version , nil
118+ return version , hasRelease , isRelease , nil
78119}
0 commit comments