diff --git a/config.go b/config.go new file mode 100644 index 0000000..f6f132c --- /dev/null +++ b/config.go @@ -0,0 +1,142 @@ +package rcpr + +import ( + "os" +) + +const ( + defaultConfigFile = ".rcpr" + defaultConfigContent = `# config file for rcpr in git config format +[rcpr] +` + envReleaseBranch = "RCPR_RELEASE_BRANCH" + envVersionFile = "RCPR_VERSION_FILE" + configReleaseBranch = "rcpr.releaseBranch" + configVersionFile = "rcpr.versionFile" +) + +type config struct { + releaseBranch *configValue + versionFile *configValue + + c *commander + conf string +} + +func newConfig(c *commander) *config { + cfg := &config{conf: defaultConfigFile, c: c} + if rb := os.Getenv(envReleaseBranch); rb != "" { + cfg.releaseBranch = &configValue{ + value: rb, + source: srcEnv, + } + } else { + out, _, err := c.gitE("config", "-f", cfg.conf, configReleaseBranch) + if err != nil { + cfg.releaseBranch = &configValue{ + value: out, + source: srcConfigFile, + } + } + } + + if rb := os.Getenv(envVersionFile); rb != "" { + cfg.releaseBranch = &configValue{ + value: rb, + source: srcEnv, + } + } else { + out, _, err := c.gitE("config", "-f", cfg.conf, configVersionFile) + if err != nil { + cfg.releaseBranch = &configValue{ + value: out, + source: srcConfigFile, + } + } + } + return cfg +} + +func (cfg *config) set(key, value string) error { + if !exists(cfg.conf) { + if err := cfg.initializeFile(); err != nil { + return err + } + } + if value == "" { + value = "-" // value "-" represents null (really?) + } + _, _, err := cfg.c.gitE("config", "-f", cfg.conf, key, value) + if err != nil { + // in this case, config file might be invalid or broken, so retry once. + if err = cfg.initializeFile(); err != nil { + return err + } + _, _, err = cfg.c.gitE("config", "-f", cfg.conf, key, value) + } + return err +} + +func (cfg *config) initializeFile() error { + if err := os.RemoveAll(cfg.conf); err != nil { + return err + } + if err := os.WriteFile(cfg.conf, []byte(defaultConfigContent), 0666); err != nil { + return err + } + return nil +} + +func (cfg *config) SetRelaseBranch(br string) error { + if err := cfg.set(configReleaseBranch, br); err != nil { + return err + } + cfg.releaseBranch = &configValue{ + value: br, + source: srcDetect, + } + return nil +} + +func (cfg *config) SetVersionFile(fpath string) error { + if err := cfg.set(configVersionFile, fpath); err != nil { + return err + } + cfg.versionFile = &configValue{ + value: fpath, + source: srcDetect, + } + return nil +} + +func (cfg *config) RelaseBranch() *configValue { + return cfg.releaseBranch +} + +func (cfg *config) VersionFile() *configValue { + return cfg.versionFile +} + +type configValue struct { + value string + source configSource +} + +func (cv *configValue) String() string { + if cv.value == "-" { + return "" + } + return cv.value +} + +func (cv *configValue) Empty() bool { + return cv.String() == "" +} + +type configSource int + +const ( + srcEnv configSource = iota + srcConfigFile + srcDetect +) diff --git a/rcpr.go b/rcpr.go index 049d99f..8038aa7 100644 --- a/rcpr.go +++ b/rcpr.go @@ -36,6 +36,7 @@ func printVersion(out io.Writer) error { type rcpr struct { c *commander gh *github.Client + cfg *config remoteName, owner, repo string } @@ -47,23 +48,25 @@ func (rp *rcpr) latestSemverTag() string { return "" } -func (rp *rcpr) initialize(ctx context.Context) error { +func newRcpr(ctx context.Context, c *commander) (*rcpr, error) { + rp := &rcpr{c: c} + var err error rp.remoteName, err = rp.detectRemote() if err != nil { - return err + return nil, err } remoteURL, _, err := rp.c.gitE("config", "remote."+rp.remoteName+".url") if err != nil { - return err + return nil, err } u, err := parseGitURL(remoteURL) if err != nil { - return fmt.Errorf("failed to parse remote") + return nil, fmt.Errorf("failed to parse remote") } m := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") if len(m) < 2 { - return fmt.Errorf("failed to detect owner and repo from remote URL") + return nil, fmt.Errorf("failed to detect owner and repo from remote URL") } rp.owner = m[0] repo := m[1] @@ -74,20 +77,21 @@ func (rp *rcpr) initialize(ctx context.Context) error { cli, err := ghClient(ctx, "", u.Hostname()) if err != nil { - return err + return nil, err } rp.gh = cli isShallow, _, err := rp.c.gitE("rev-parse", "--is-shallow-repository") if err != nil { - return err + return nil, err } if isShallow == "true" { if _, _, err := rp.c.gitE("fetch", "--unshallow"); err != nil { - return err + return nil, err } } - return nil + rp.cfg = newConfig(rp.c) + return rp, nil } func isRcpr(pr *github.PullRequest) bool { @@ -114,10 +118,8 @@ func Run(ctx context.Context, argv []string, outStream, errStream io.Writer) err } // main logic follows - rp := &rcpr{ - c: &commander{outStream: outStream, errStream: errStream, dir: "."}, - } - if err := rp.initialize(ctx); err != nil { + rp, err := newRcpr(ctx, &commander{outStream: outStream, errStream: errStream, dir: "."}) + if err != nil { return err } @@ -133,10 +135,20 @@ func Run(ctx context.Context, argv []string, outStream, errStream io.Writer) err // XXX: Do I need to take care of past tags with and without v-prefixes? // It might be good to be able to enforce presence or absence in a configuration file item. - releaseBranch, _ := rp.defaultBranch() // TODO: make release branch configable + var releaseBranch string + if rp.cfg.releaseBranch != nil { + releaseBranch = rp.cfg.releaseBranch.String() + } if releaseBranch == "" { - releaseBranch = defaultReleaseBranch + releaseBranch, _ := rp.defaultBranch() + if releaseBranch == "" { + releaseBranch = defaultReleaseBranch + } + if err := rp.cfg.SetRelaseBranch(releaseBranch); err != nil { + return err + } } + branch, _, err := rp.c.gitE("symbolic-ref", "--short", "HEAD") if err != nil { return fmt.Errorf("failed to git symbolic-ref: %w", err) @@ -166,12 +178,17 @@ func Run(ctx context.Context, argv []string, outStream, errStream io.Writer) err return err } if len(pulls) > 0 && isRcpr(pulls[0]) { - rp.c.git("checkout", "HEAD~") - vfile, err := detectVersionFile(".", currVer) - if err != nil { - return err + var vfile string + if rp.cfg.versionFile == nil { + rp.c.git("checkout", "HEAD~") + vfile, err = detectVersionFile(".", currVer) + if err != nil { + return err + } + rp.c.git("checkout", releaseBranch) + } else { + vfile = rp.cfg.versionFile.String() } - rp.c.git("checkout", releaseBranch) var nextTag string if vfile != "" { @@ -252,16 +269,26 @@ func Run(ctx context.Context, argv []string, outStream, errStream io.Writer) err nextVer := guessNextSemver(currVer, currRcpr) - // TODO: make configurable version file - vfile, err := detectVersionFile(".", currVer) - if err != nil { - return err + var vfile string + if rp.cfg.versionFile == nil { + vfile, err = detectVersionFile(".", currVer) + if err != nil { + return err + } + if vfile != "" { + if err := rp.cfg.SetVersionFile(vfile); err != nil { + return err + } + } + } else { + vfile = rp.cfg.versionFile.String() } if vfile != "" { if err := bumpVersionFile(vfile, currVer, nextVer); err != nil { return err } } + rp.c.gitE("add", "-f", rp.cfg.conf) // ignore any errors // TODO To be able to run some kind of change script set by configuration in advance.