diff --git a/RELEASES.md b/RELEASES.md index a7f862e33..f3bfd20d5 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -42,15 +42,42 @@ In the following example, we'll assume that we're making a backport branch for the 0.35.x line. 1. Start on `master` + 2. Create and push the backport branch: ```sh git checkout -b v0.35.x git push origin v0.35.x ``` +3. Create a PR to update the documentation directory for the backport branch. + + We only maintain RFC and ADR documents on master, to avoid confusion. + In addition, we rewrite Markdown URLs pointing to master to point to the + backport branch, so that generated documentation will link to the correct + versions of files elsewhere in the repository. For context on the latter, + see https://github.com/tendermint/tendermint/issues/7675. + + To prepare the PR: + ```sh + # Remove the RFC and ADR documents from the backport. + # We only maintain these on master to avoid confusion. + git rm -r docs/rfc docs/architecture + + # Update absolute links to point to the backport. + go run ./scripts/linkpatch -recur -target v0.35.x -skip-path docs/DOCS_README.md,docs/README.md docs + + # Create and push the PR. + git checkout -b update-docs-v035x + git commit -m "Update docs for v0.35.x backport branch." docs + git push -u origin update-docs-v035x + ``` + + Be sure to merge this PR before making other changes on the newly-created + backport branch. + After doing these steps, go back to `master` and do the following: -1. Tag `master` as the dev branch for the _next_ major release and push it back up. +1. Tag `master` as the dev branch for the _next_ major release and push it up to GitHub. For example: ```sh git tag -a v0.36.0-dev -m "Development base for Tendermint v0.36." diff --git a/docs/README.md b/docs/README.md index 256b65303..3137d611a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ Tendermint?](introduction/what-is-tendermint.md). To get started quickly with an example application, see the [quick start guide](introduction/quick-start.md). -To learn about application development on Tendermint, see the [Application Blockchain Interface](https://github.com/tendermint/tendermint/tree/master/spec/abci). +To learn about application development on Tendermint, see the [Application Blockchain Interface](../spec/abci). For more details on using Tendermint, see the respective documentation for [Tendermint Core](tendermint-core/), [benchmarking and monitoring](tools/), and [network deployments](nodes/). diff --git a/go.mod b/go.mod index cac883c8d..136dd3af3 100644 --- a/go.mod +++ b/go.mod @@ -67,6 +67,7 @@ require ( github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect github.com/containerd/continuity v0.2.1 // indirect github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48 // indirect + github.com/creachadair/atomicfile v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingajkin/go-header v0.4.2 // indirect github.com/dgraph-io/badger/v2 v2.2007.2 // indirect diff --git a/go.sum b/go.sum index a73ebda02..cd3e0e4f5 100644 --- a/go.sum +++ b/go.sum @@ -217,6 +217,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/atomicfile v0.2.4 h1:GRjpQLmz/78I4+nBQpGMFrRa9yrL157AUTrA6hnF0YU= +github.com/creachadair/atomicfile v0.2.4/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= diff --git a/scripts/linkpatch/linkpatch.go b/scripts/linkpatch/linkpatch.go new file mode 100644 index 000000000..42054d478 --- /dev/null +++ b/scripts/linkpatch/linkpatch.go @@ -0,0 +1,205 @@ +// Program linkpatch rewrites absolute URLs pointing to targets in GitHub in +// Markdown link tags to target a different branch. +// +// This is used to update documentation links for backport branches. +// See https://github.com/tendermint/tendermint/issues/7675 for context. +package main + +import ( + "bytes" + "flag" + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/creachadair/atomicfile" +) + +var ( + repoName = flag.String("repo", "tendermint/tendermint", "Repository name to match") + sourceBranch = flag.String("source", "master", "Source branch name (required)") + targetBranch = flag.String("target", "", "Target branch name (required)") + doRecur = flag.Bool("recur", false, "Recur into subdirectories") + + skipPath stringList + skipMatch regexpFlag + + // Match markdown links pointing to absolute URLs. + // This only works for "inline" links, not referenced links. + // The submetch selects the URL. + linkRE = regexp.MustCompile(`(?m)\[.*?\]\((https?://.*?)\)`) +) + +func init() { + flag.Var(&skipPath, "skip-path", "Skip these paths (comma-separated)") + flag.Var(&skipMatch, "skip-match", "Skip URLs matching this regexp (RE2)") + + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %[1]s [options] ... + +Rewrite absolute Markdown links targeting the specified GitHub repository +and source branch name to point to the target branch instead. Matching +files are updated in-place. + +Each path names either a directory to list, or a single file path to +rewrite. By default, only the top level of a directory is scanned; use -recur +to recur into subdirectories. + +Options: +`, filepath.Base(os.Args[0])) + flag.PrintDefaults() + } +} + +func main() { + flag.Parse() + switch { + case *repoName == "": + log.Fatal("You must specify a non-empty -repo name (org/repo)") + case *targetBranch == "": + log.Fatal("You must specify a non-empty -target branch") + case *sourceBranch == "": + log.Fatal("You must specify a non-empty -source branch") + case *sourceBranch == *targetBranch: + log.Fatalf("Source and target branch are the same (%q)", *sourceBranch) + case flag.NArg() == 0: + log.Fatal("You must specify at least one file/directory to rewrite") + } + + r, err := regexp.Compile(fmt.Sprintf(`^https?://github.com/%s/(?:blob|tree)/%s`, + *repoName, *sourceBranch)) + if err != nil { + log.Fatalf("Compiling regexp: %v", err) + } + for _, path := range flag.Args() { + if err := processPath(r, path); err != nil { + log.Fatalf("Processing %q failed: %v", path, err) + } + } +} + +func processPath(r *regexp.Regexp, path string) error { + fi, err := os.Lstat(path) + if err != nil { + return err + } + if fi.Mode().IsDir() { + return processDir(r, path) + } else if fi.Mode().IsRegular() { + return processFile(r, path) + } + return nil // nothing to do with links, device files, sockets, etc. +} + +func processDir(r *regexp.Regexp, root string) error { + return filepath.Walk(root, func(path string, fi fs.FileInfo, err error) error { + if err != nil { + return err + } + if fi.IsDir() { + if skipPath.Contains(path) { + log.Printf("Skipping %q (per -skip-path)", path) + return filepath.SkipDir // explicitly skipped + } else if !*doRecur && path != root { + return filepath.SkipDir // skipped because we aren't recurring + } + return nil // nothing else to do for directories + } else if skipPath.Contains(path) { + log.Printf("Skipping %q (per -skip-path)", path) + return nil // explicitly skipped + } else if filepath.Ext(path) != ".md" { + return nil // nothing to do for non-Markdown files + } + + return processFile(r, path) + }) +} + +func processFile(r *regexp.Regexp, path string) error { + log.Printf("Processing file %q", path) + input, err := os.ReadFile(path) + if err != nil { + return err + } + + pos := 0 + var output bytes.Buffer + for _, m := range linkRE.FindAllSubmatchIndex(input, -1) { + href := string(input[m[2]:m[3]]) + u := r.FindStringIndex(href) + if u == nil || skipMatch.MatchString(href) { + if u != nil { + log.Printf("Skipped URL %q (by -skip-match)", href) + } + output.Write(input[pos:m[1]]) // copy the existing data as-is + pos = m[1] + continue + } + + // Copy everything before the URL as-is, then write the replacement. + output.Write(input[pos:m[2]]) // everything up to the URL + fmt.Fprintf(&output, `https://github.com/%s/blob/%s%s`, *repoName, *targetBranch, href[u[1]:]) + + // Write out the tail of the match, everything after the URL. + output.Write(input[m[3]:m[1]]) + pos = m[1] + } + output.Write(input[pos:]) // the rest of the file + + _, err = atomicfile.WriteAll(path, &output, 0644) + return err +} + +// stringList implements the flag.Value interface for a comma-separated list of strings. +type stringList []string + +func (lst *stringList) Set(s string) error { + if s == "" { + *lst = nil + } else { + *lst = strings.Split(s, ",") + } + return nil +} + +// Contains reports whether lst contains s. +func (lst stringList) Contains(s string) bool { + for _, elt := range lst { + if s == elt { + return true + } + } + return false +} + +func (lst stringList) String() string { return strings.Join([]string(lst), ",") } + +// regexpFlag implements the flag.Value interface for a regular expression. +type regexpFlag struct{ *regexp.Regexp } + +func (r regexpFlag) MatchString(s string) bool { + if r.Regexp == nil { + return false + } + return r.Regexp.MatchString(s) +} + +func (r *regexpFlag) Set(s string) error { + c, err := regexp.Compile(s) + if err != nil { + return err + } + r.Regexp = c + return nil +} + +func (r regexpFlag) String() string { + if r.Regexp == nil { + return "" + } + return r.Regexp.String() +}