diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62ab9e0..3def256 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.17', '1.16', '1.15' ] + go: [ '1.17', '1.16' ] steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 @@ -32,7 +32,7 @@ jobs: runs-on: macos-latest strategy: matrix: - go: [ '1.17', '1.16', '1.15' ] + go: [ '1.17', '1.16' ] steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 @@ -41,14 +41,14 @@ jobs: - name: Install libgit2 run: | brew install libssh2 - brew install libgit2 + brew install carlsberg/tap/libgit2@1.1.0 - run: go build # build-windows: # runs-on: windows-latest # strategy: # matrix: - # go: [ '1.17', '1.16', '1.15' ] + # go: [ '1.17', '1.16' ] # steps: # - uses: actions/checkout@v2 # - uses: actions/setup-go@v2 @@ -78,4 +78,4 @@ jobs: # export PATH="/c/msys64/:/c/msys64/usr/:/c/msys64/usr/bin:$PATH" # go get -v ./... # ./make.bat build - # - run: go build \ No newline at end of file + # - run: go build diff --git a/README.md b/README.md index 5ffb661..5685242 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # git-semver -[![Build](https://github.com/crqra/git-semver/actions/workflows/build.yml/badge.svg)](https://github.com/crqra/git-semver/actions/workflows/build.yml) [![LoC](https://tokei.rs/b1/github/crqra/git-semver)](https://github.com/crqra/git-semver) +[![Build](https://github.com/carlsberg/git-semver/actions/workflows/build.yml/badge.svg)](https://github.com/carlsberg/git-semver/actions/workflows/build.yml) [![LoC](https://tokei.rs/b1/github/crqra/git-semver)](https://github.com/crqra/git-semver) > Git extension to easily manage your project's version based on [Semantic Versioning][semver] and [Conventional Commits][conventional-commits] diff --git a/cmd/git-semver/git-semver.go b/cmd/git-semver/git-semver.go index 55eaecc..5219a0c 100644 --- a/cmd/git-semver/git-semver.go +++ b/cmd/git-semver/git-semver.go @@ -3,6 +3,7 @@ package git_semver import ( "fmt" "log" + "strings" "github.com/Masterminds/semver" git_semver "github.com/crqra/git-semver/internal/git-semver" @@ -61,6 +62,26 @@ var bumpCmd = &cobra.Command{ nextVersion := git_semver.BumpVersion(*latestVersion, increment) + versionFiles := make([]git_semver.VersionFile, 0) + versionFilenamesAndKeys, err := cmd.Flags().GetStringArray("version-file") + if err != nil { + log.Fatal(err) + } + + for _, filenameAndKey := range versionFilenamesAndKeys { + slice := strings.Split(filenameAndKey, ":") + + if len(slice) != 2 { + log.Fatalf("%s is not correctly formatted. Should be `filename:key`", filenameAndKey) + } + + versionFiles = append(versionFiles, git_semver.VersionFile{Filename: slice[0], Key: slice[1]}) + } + + if err := git_semver.UpdateVersionFiles(repo, versionFiles, *latestVersion, nextVersion); err != nil { + log.Fatal(err) + } + if err := git_semver.TagVersion(repo, nextVersion); err != nil { log.Fatal(err) } @@ -148,4 +169,6 @@ func init() { rootCmd.AddCommand(bumpCmd) rootCmd.AddCommand(nextCmd) rootCmd.AddCommand(latestCmd) + + bumpCmd.Flags().StringArrayP("version-file", "f", make([]string, 0), "Specify version files to be updated with the new version in the format `filename:key` (i.e. `package.json:\"version\"`)") } diff --git a/internal/git-semver/mod.go b/internal/git-semver/mod.go index bcb07e1..33f1639 100644 --- a/internal/git-semver/mod.go +++ b/internal/git-semver/mod.go @@ -2,6 +2,8 @@ package git_semver import ( "fmt" + "io/fs" + "io/ioutil" "log" "regexp" "sort" @@ -13,6 +15,10 @@ import ( type Increment int64 type Change string +type VersionFile struct { + Filename string + Key string +} const ( Major Increment = 4 @@ -63,6 +69,54 @@ func ListVersions(repo *git.Repository) ([]*semver.Version, error) { return versions, nil } +func CreateCommit(repo *git.Repository, message string) error { + sig, err := repo.DefaultSignature() + if err != nil { + return err + } + + head, err := repo.Head() + if err != nil { + return err + } + + idx, err := repo.Index() + if err != nil { + return err + } + + idx.AddAll(make([]string, 0), git.IndexAddDefault, func(_, _ string) int { + return 0 + }) + + treeId, err := idx.WriteTree() + if err != nil { + return err + } + + err = idx.Write() + if err != nil { + return err + } + + tree, err := repo.LookupTree(treeId) + if err != nil { + return err + } + + commitTarget, err := repo.LookupCommit(head.Target()) + if err != nil { + return err + } + + _, err = repo.CreateCommit("refs/heads/main", sig, sig, message, tree, commitTarget) + if err != nil { + return err + } + + return nil +} + func ListCommits(repo *git.Repository) ([]*git.Commit, error) { revwalk, err := repo.Walk() if err != nil { @@ -195,3 +249,50 @@ func PushVersionTagToRemotes(repo *git.Repository, version semver.Version) error return nil } + +func UpdateVersionFiles(repo *git.Repository, versionFiles []VersionFile, currentVersion semver.Version, nextVersion semver.Version) error { + if len(versionFiles) == 0 { + return nil + } + + for _, versionFile := range versionFiles { + err := updateVersionFileVersion(versionFile, currentVersion, nextVersion) + if err != nil { + return err + } + } + + err := CreateCommit(repo, fmt.Sprintf("bump: %s -> %s", currentVersion.String(), nextVersion.String())) + if err != nil { + return err + } + + return nil +} + +func regexForVersionFileKey(key string, currentVersion semver.Version) (*regexp.Regexp, error) { + return regexp.Compile(fmt.Sprintf("%s(.{1,})?%s", key, currentVersion.String())) +} + +func updateVersionFileVersion(versionFile VersionFile, currentVersion semver.Version, nextVersion semver.Version) error { + r, err := regexForVersionFileKey(versionFile.Key, currentVersion) + if err != nil { + return err + } + + contents, err := ioutil.ReadFile(versionFile.Filename) + if err != nil { + return err + } + + match := string(r.Find(contents)) + newVersionString := strings.Replace(match, currentVersion.String(), nextVersion.String(), 1) + newContents := strings.Replace(string(contents), match, newVersionString, 1) + + err = ioutil.WriteFile(versionFile.Filename, []byte(newContents), fs.ModePerm) + if err != nil { + return err + } + + return nil +}