mirror of
https://github.com/hanzo-ds/go.git
synced 2026-10-30 19:45:01 +00:00
Source: the unexported clickhouse struct becomes client, the ch receiver and chproto import alias follow, error message prefixes read datastore, and the JSON serializer interface drops the vendor infix. resources' metadata key and its accessor are renamed together, with a test proving the embedded YAML still unmarshals — a silent drift there would have made every connection take the unsupported-server-version branch. Codegen: all three templates still emitted the upstream module paths, so regenerating would have quietly undone the rename. Note the templates have drifted from the files they produce (regenerating today drops QBit and scanTypeDuration), so the generated files are left as tracked and only the templates are corrected. Dead links: an earlier mechanical pass invented URLs that do not resolve — github.com/ClickHouse/datastore-go, hanzo-ds/driver issue numbers that belong to upstream, and mangled names of independent projects (mailru, uptrace, nikepan, EpicStep). Third-party names are not ours to rewrite, so those directories are removed rather than repaired, and provenance comments now point at real files. Dev infra: compose runs our own server image at the tag production runs, since the vendor's alpine tag does not exist in our registry. Cluster hostnames, nginx upstreams and server configs are renamed as one set. CHANGELOG.md is upstream's release history for versions this module never released; rewriting it would fabricate a lineage, so it is dropped and the upstream base recorded in README instead. The two long-standing vet findings are fixed by keying the struct literals rather than suppressed. LICENSE and contributors/list keep the vendor name on purpose: one is the copyright notice Apache-2.0 requires, the other is real people's names. CI now enforces build, vet, test, gofmt and a grep gate.
302 lines
6.3 KiB
Go
302 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var skipWorkingTreeIsDirtyCheck = flag.Bool("skip-working-tree-is-dirty-check", false, "Skip working tree is dirty check")
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if !(*skipWorkingTreeIsDirtyCheck) && gitRepositoryWorkingTreeIsDirty() {
|
|
log.Fatalln("Git working tree is dirty")
|
|
return
|
|
}
|
|
|
|
releaseURL := getLatestDraftReleaseURL()
|
|
|
|
log.Println("Latest draft release URL:")
|
|
log.Println(releaseURL)
|
|
|
|
r := getRelease(releaseURL)
|
|
|
|
log.Println("Release tag:")
|
|
log.Println(r.TagName)
|
|
log.Println("Release body:")
|
|
log.Println(r.Body)
|
|
|
|
major, minor, patch, err := parseSemVer(r.TagName)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
|
|
if len(r.Body) == 0 {
|
|
log.Fatalln("Release body is empty")
|
|
return
|
|
}
|
|
|
|
changelogPath := changelogFilePath()
|
|
prependReleaseToChangelog(changelogPath, r)
|
|
|
|
if err := updateClientInfo(major, minor, patch); err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
|
|
runGoGenerate()
|
|
runGoFmt()
|
|
|
|
gitHubOutputReleaseURLIfAvailable(releaseURL)
|
|
}
|
|
|
|
func gitHubOutputReleaseURLIfAvailable(url string) {
|
|
if len(url) == 0 {
|
|
return
|
|
}
|
|
|
|
gitHubOutputFile, exists := os.LookupEnv("GITHUB_OUTPUT")
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
f, err := os.OpenFile(gitHubOutputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
|
|
defer func() {
|
|
if err := f.Close(); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}()
|
|
|
|
if _, err := f.WriteString(fmt.Sprintf("RELEASE_URL=%s\n", url)); err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func runGoFmt() {
|
|
cmd := exec.Command("go", "fmt", "./...")
|
|
cmd.Dir = getRootPath()
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func runGoGenerate() {
|
|
cmd := exec.Command("go", "generate", "./...")
|
|
cmd.Dir = getRootPath()
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func gitRepositoryWorkingTreeIsDirty() bool {
|
|
cmd := exec.Command("git", "status", "--porcelain")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return false
|
|
}
|
|
return len(out) > 0
|
|
}
|
|
|
|
func parseSemVer(version string) (major, minor, patch int, err error) {
|
|
// Define a regular expression to match SemVer format
|
|
re := regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)$`)
|
|
|
|
// Apply the regular expression to the version string
|
|
match := re.FindStringSubmatch(version)
|
|
|
|
// Check if the version string matches the SemVer format
|
|
if len(match) != 4 {
|
|
err = fmt.Errorf("invalid SemVer format: %s", version)
|
|
return
|
|
}
|
|
|
|
// Parse the major, minor, and patch components as integers
|
|
major, err = strconv.Atoi(match[1])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
minor, err = strconv.Atoi(match[2])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
patch, err = strconv.Atoi(match[3])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func prependReleaseToChangelog(changelogPath string, r release) {
|
|
f, err := os.OpenFile(changelogPath, os.O_RDWR, 0666)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
content, err := io.ReadAll(f)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return
|
|
}
|
|
|
|
f.Seek(0, io.SeekStart)
|
|
f.WriteString(fmt.Sprintf("# %s, %s ", r.TagName, time.Now().Format("2006-01-02")))
|
|
f.WriteString(r.Body)
|
|
f.WriteString("\n\n")
|
|
f.Write(content)
|
|
}
|
|
|
|
func changelogFilePath() string {
|
|
rootPath := getRootPath()
|
|
changelogPath := rootPath + "/CHANGELOG.md"
|
|
return changelogPath
|
|
}
|
|
|
|
func getRootPath() string {
|
|
wd, _ := os.Getwd()
|
|
rootPath := strings.Replace(wd, "internal/cmd/release", "", 1)
|
|
return rootPath
|
|
}
|
|
|
|
func getRelease(releaseURL string) release {
|
|
req, err := http.NewRequest(http.MethodGet, releaseURL, nil)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
req.Header.Set("Authorization", "token "+os.Getenv("GITHUB_TOKEN"))
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
var release release
|
|
if err := json.NewDecoder(res.Body).Decode(&release); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
return release
|
|
}
|
|
|
|
type release struct {
|
|
URL string `json:"url"`
|
|
Body string `json:"body"`
|
|
TagName string `json:"tag_name"`
|
|
}
|
|
|
|
func getLatestDraftReleaseURL() string {
|
|
// Fetch the latest release from GitHub repository using GitHub API
|
|
req, err := http.NewRequest(http.MethodGet, "https://api.github.com/repos/hanzo-ds/go/releases?per_page=100", nil)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", "token "+os.Getenv("GITHUB_TOKEN"))
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
var releases []struct {
|
|
URL string `json:"url"`
|
|
Draft bool `json:"draft"`
|
|
}
|
|
if err := json.NewDecoder(res.Body).Decode(&releases); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
// filter out releases that are not drafts
|
|
for i := 0; i < len(releases); {
|
|
if releases[i].Draft {
|
|
return releases[i].URL
|
|
}
|
|
}
|
|
|
|
log.Fatalln("No draft releases found")
|
|
return ""
|
|
}
|
|
|
|
func updateClientInfo(major, minor, patch int) error {
|
|
// Open the client_info.go file for reading and writing
|
|
file, err := os.OpenFile(getRootPath()+"/client_info.go", os.O_RDWR, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read the contents of the file into memory
|
|
bytes, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Replace the ClientVersionMajor, ClientVersionMinor, and ClientVersionPatch lines
|
|
reMajor := regexp.MustCompile(`ClientVersionMajor\s+=\s+\d+`)
|
|
reMinor := regexp.MustCompile(`ClientVersionMinor\s+=\s+\d+`)
|
|
rePatch := regexp.MustCompile(`ClientVersionPatch\s+=\s+\d+`)
|
|
newLines := []string{
|
|
fmt.Sprintf("ClientVersionMajor = %d", major),
|
|
fmt.Sprintf("ClientVersionMinor = %d", minor),
|
|
fmt.Sprintf("ClientVersionPatch = %d", patch),
|
|
}
|
|
scanner := bufio.NewScanner(strings.NewReader(string(bytes)))
|
|
var newContent string
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if reMajor.MatchString(line) {
|
|
line = newLines[0]
|
|
} else if reMinor.MatchString(line) {
|
|
line = newLines[1]
|
|
} else if rePatch.MatchString(line) {
|
|
line = newLines[2]
|
|
}
|
|
newContent += line + "\n"
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the updated content back to the file
|
|
if _, err := file.Seek(0, 0); err != nil {
|
|
return err
|
|
}
|
|
if err := file.Truncate(0); err != nil {
|
|
return err
|
|
}
|
|
if _, err := file.Write([]byte(newContent)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|