in cmd/sync-tags/main.go [70:371]
func main() {
// repository flags used when the repository is not k8s.io/kubernetes
commitMsgTag := flag.String("commit-message-tag", "Kubernetes-commit", "the git commit message tag used to point back to source commits")
sourceRemote := flag.String("source-remote", "", "the source repo remote (e.g. upstream")
sourceBranch := flag.String("source-branch", "", "the source repo branch (not qualified, just the name; defaults to equal <branch>)")
prefix := flag.String("prefix", "kubernetes-", "a string to put in front of upstream tags")
pushScriptPath := flag.String("push-script", "", "git-push command(s) are appended to this file to push the new tags to the origin remote")
dependencies := flag.String("dependencies", "", "comma-separated list of repo:branch pairs of dependencies")
skipFetch := flag.Bool("skip-fetch", false, "skip fetching tags")
mappingOutputFile := flag.String("mapping-output-file", "", "a file name to write the source->dest hash mapping to ({{.Tag}} is substituted with the tag name, {{.Branch}} with the local branch name)")
publishSemverTags := flag.Bool("publish-v0-semver", false, "publish v0.x.y tag at destination repo for v1.x.y tag at the source repo")
flag.Usage = Usage
flag.Parse()
if *sourceRemote == "" {
glog.Fatalf("source-remote cannot be empty")
}
if *sourceBranch == "" {
glog.Fatalf("source-branch cannot be empty")
}
var dependentRepos []string
if len(*dependencies) > 0 {
for _, pair := range strings.Split(*dependencies, ",") {
ps := strings.Split(pair, ":")
dependentRepos = append(dependentRepos, ps[0])
}
}
// open repo at "."
r, err := gogit.PlainOpen(".")
if err != nil {
glog.Fatalf("Failed to open repo at .: %v", err)
}
h, err := r.Head()
if err != nil {
glog.Fatalf("Failed to get HEAD: %v", err)
}
localBranch := h.Name().Short()
if localBranch == "" {
glog.Fatalf("Failed to get current branch.")
}
// get first-parent commit list of upstream branch
srcUpdateBranch, err := r.ResolveRevision(plumbing.Revision(fmt.Sprintf("refs/remotes/%s/%s", *sourceRemote, *sourceBranch)))
if err != nil {
glog.Fatalf("Failed to open upstream branch %s: %v", *sourceBranch, err)
}
srcHead, err := cache.CommitObject(r, *srcUpdateBranch)
if err != nil {
glog.Fatalf("Failed to open upstream branch %s head: %v", *sourceBranch, err)
}
srcFirstParents, err := git.FirstParentList(r, srcHead)
if err != nil {
glog.Fatalf("Failed to get upstream branch %s first-parent list: %v", *sourceBranch, err)
}
// delete remote tags locally
if !*skipFetch {
fmt.Printf("Removing all local copies of origin and %s tags.\n", *sourceRemote)
if err := removeRemoteTags(r, "origin", *sourceRemote); err != nil {
glog.Fatalf("Failed to iterate through tags: %v", err)
}
}
// get upstream tags
if !*skipFetch {
fmt.Printf("Fetching tags from remote %q.\n", *sourceRemote)
err = fetchTags(r, *sourceRemote)
if err != nil {
glog.Fatalf("Failed to fetch tags for %q: %v", *sourceRemote, err)
}
}
srcTagCommits, err := remoteTags(r, *sourceRemote)
if err != nil {
glog.Fatalf("Failed to iterate through %s tags: %v", *sourceRemote, err)
}
// get all origin tags
if !*skipFetch {
fmt.Printf("Fetching tags from remote %q.\n", "origin")
err = fetchTags(r, "origin")
if err != nil {
glog.Fatalf("Failed to fetch tags for %q: %v", "origin", err)
}
}
bTagCommits, err := remoteTags(r, "origin")
if err != nil {
glog.Fatalf("Failed to iterate through origin tags: %v", err)
}
// filter tags by source branch
srcFirstParentCommits := map[string]struct{}{}
for _, kc := range srcFirstParents {
srcFirstParentCommits[kc.Hash.String()] = struct{}{}
}
for name, kh := range srcTagCommits {
// ignore non-annotated tags
tag, err := r.TagObject(kh)
if err != nil {
delete(srcTagCommits, name)
continue
}
// delete tag not on the source branch
if _, ok := srcFirstParentCommits[tag.Target.String()]; !ok {
delete(srcTagCommits, name)
}
}
var sourceCommitsToDstCommits map[plumbing.Hash]plumbing.Hash
mappingFilesWritten := map[string]bool{}
// create or update tags from srcTagCommits as local tags with the given prefix
createdTags := []string{}
for name, kh := range srcTagCommits {
bName := name
if *prefix != "" {
bName = *prefix + name[1:] // remove the v
}
var (
semverTag = ""
publishSemverTag = false
)
// if we are publishing semver tags
if *publishSemverTags {
// and this is a valid v1... semver tag
if _, semverErr := semver.Parse(name[1:]); semverErr == nil && strings.HasPrefix(name, "v1.") {
publishSemverTag = true
semverTag = "v0." + strings.TrimPrefix(name, "v1.") // replace v1.x.y with v0.x.y
}
}
// ignore non-annotated tags
tag, err := r.TagObject(kh)
if err != nil {
continue
}
// ignore old tags
if tag.Tagger.When.Before(time.Date(2017, 9, 1, 0, 0, 0, 0, time.UTC)) {
// TODO: Fix or remove
// fmt.Printf("Ignoring old tag origin/%s from %v\n", bName, tag.Tagger.When)
continue
}
// skip if either tag exists at origin
_, nonSemverTagAtOrigin := bTagCommits[bName]
_, semverTagAtOrigin := bTagCommits[semverTag]
if nonSemverTagAtOrigin || (publishSemverTag && semverTagAtOrigin) {
continue
}
// if any of the tag exists locally,
// delete the tags, clear the cache and recreate them
if tagExists(bName) {
commit, commitTime, err := taggedCommitHashAndTime(r, bName)
if err != nil {
glog.Fatalf("Failed to get tag %s: %v", bName, err)
}
rev := commit.String()
pseudoVersion := fmt.Sprintf("v0.0.0-%s-%s", commitTime.UTC().Format("20060102150405"), rev[:12])
fmt.Printf("Clearing cache for local tag %s.\n", pseudoVersion)
if err := cleanCacheForTag(pseudoVersion); err != nil {
glog.Fatalf("Failed to clean go mod cache for %s: %v", pseudoVersion, err)
}
if err := deleteTag(bName); err != nil {
glog.Fatalf("Failed to delete tag %s: %v", bName, err)
}
}
if publishSemverTag && tagExists(semverTag) {
fmt.Printf("Clearing cache for local tag %s.\n", semverTag)
if err := cleanCacheForTag(semverTag); err != nil {
glog.Fatalf("Failed to clean go mod cache for %s: %v", semverTag, err)
}
if err := deleteTag(semverTag); err != nil {
glog.Fatalf("Failed to delete tag %s: %v", semverTag, err)
}
}
// lazily compute kube commit map
if sourceCommitsToDstCommits == nil {
bRevision, err := r.ResolveRevision(plumbing.Revision(fmt.Sprintf("refs/heads/%s", localBranch)))
if err != nil {
glog.Fatalf("Failed to open branch %s: %v", localBranch, err)
}
fmt.Printf("Computing mapping from kube commits to the local branch %q at %s because %q seems to be relevant.\n", localBranch, bRevision.String(), bName)
bHeadCommit, err := cache.CommitObject(r, *bRevision)
if err != nil {
glog.Fatalf("Failed to open branch %s head: %v", localBranch, err)
}
bFirstParents, err := git.FirstParentList(r, bHeadCommit)
if err != nil {
glog.Fatalf("Failed to get branch %s first-parent list: %v", localBranch, err)
}
sourceCommitsToDstCommits, err = git.SourceCommitToDstCommits(r, *commitMsgTag, bFirstParents, srcFirstParents)
if err != nil {
glog.Fatalf("Failed to map upstream branch %s to HEAD: %v", *sourceBranch, err)
}
}
// map kube commit to local branch
bh, found := sourceCommitsToDstCommits[tag.Target]
if !found {
// this means that the tag is not on the current source branch
continue
}
// store source->dest hash mapping for debugging
if *mappingOutputFile != "" {
fname := mappingOutputFileName(*mappingOutputFile, localBranch, bName)
if !mappingFilesWritten[fname] {
fmt.Printf("Writing source->dest hash mapping to %q\n", fname)
f, err := os.Create(fname)
if err != nil {
glog.Fatal(f)
}
if err := writeKubeCommitMapping(f, sourceCommitsToDstCommits, srcFirstParents); err != nil {
glog.Fatal(err)
}
defer f.Close()
mappingFilesWritten[fname] = true
}
}
if len(dependentRepos) > 0 {
wt := checkoutBranchTagCommit(r, bh, dependentRepos)
// update go.mod to point to actual tagged version in the dependencies. This version might differ
// from the one currently in go.mod because the other repo could have gotten more commit for this
// tag, but this repo didn't. Compare https://github.com/kubernetes/publishing-bot/issues/12 for details.
var changed bool
_, err = os.Stat("go.mod")
if err == nil {
if publishSemverTag {
changed = updateGoMod(semverTag, dependentRepos, true)
} else {
changed = updateGoMod(bName, dependentRepos, false)
}
}
if changed {
if publishSemverTag {
bh = createCommitToFixDeps(wt, semverTag)
} else {
bh = createCommitToFixDeps(wt, bName)
}
}
}
// create semver annotated tag
if publishSemverTag {
fmt.Printf("Tagging %v as %q.\n", bh, semverTag)
err = createAnnotatedTag(bh, semverTag, tag.Tagger.When, dedent.Dedent(fmt.Sprintf(`
Kubernetes release %s
Based on https://github.com/kubernetes/kubernetes/releases/tag/%s
`, name, name)))
if err != nil {
glog.Fatalf("Failed to create tag %q: %v", semverTag, err)
}
createdTags = append(createdTags, semverTag)
}
// create non-semver prefixed annotated tag
fmt.Printf("Tagging %v as %q.\n", bh, bName)
err = createAnnotatedTag(bh, bName, tag.Tagger.When, dedent.Dedent(fmt.Sprintf(`
Kubernetes release %s
Based on https://github.com/kubernetes/kubernetes/releases/tag/%s
`, name, name)))
if err != nil {
glog.Fatalf("Failed to create tag %q: %v", bName, err)
}
createdTags = append(createdTags, bName)
}
// write push command for new tags
// we use git push --atomic because it treats
// any existing releases which have only non-semver tags as no-ops
// and both semver and non-semver tags are targeted in a single operation
if *pushScriptPath != "" && len(createdTags) > 0 {
pushScript, err := os.OpenFile(*pushScriptPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
glog.Fatalf("Failed to open push-script %q for appending: %v", *pushScriptPath, err)
}
defer pushScript.Close()
_, err = pushScript.WriteString(fmt.Sprintf("git push --atomic origin %s\n", "refs/tags/"+strings.Join(createdTags, " refs/tags/")))
if err != nil {
glog.Fatalf("Failed to write to push-script %q: %q", *pushScriptPath, err)
}
}
}