diff --git a/README.md b/README.md index 11e3f3d..76a9cf0 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ mpd: startercode: url: git@gitlab.example.org:mpd/startercode/blatt-01.git fromBranch: startercode + # tag: startercode # optional: sets this tag in generated repos at startercode commit template: true templateMessage: Initial Commit ``` diff --git a/config/repo.go b/config/repo.go index d2c5e46..dd59e9b 100644 --- a/config/repo.go +++ b/config/repo.go @@ -27,6 +27,11 @@ func startercode(assignmentKey string) *Startercode { fromBranch = fB } + tag := "" + if t := viper.GetString(assignmentKey + ".startercode.tag"); len(t) > 0 { + tag = t + } + template := viper.GetBool(assignmentKey + ".startercode.template") templateMessage := "Initial" @@ -44,6 +49,7 @@ func startercode(assignmentKey string) *Startercode { return &Startercode{ URL: url, FromBranch: fromBranch, + Tag: tag, Template: template, TemplateMessage: templateMessage, ToBranch: toBranch, diff --git a/config/repo_test.go b/config/repo_test.go index e499815..8ceccdc 100644 --- a/config/repo_test.go +++ b/config/repo_test.go @@ -19,6 +19,9 @@ func TestStartercodeDefaultsAndReplication(t *testing.T) { if s.FromBranch != "main" || s.ToBranch != "main" { t.Fatalf("unexpected startercode defaults: %#v", s) } + if s.Tag != "" { + t.Fatalf("startercode tag = %q, want empty", s.Tag) + } } func TestStartercodeOverrides(t *testing.T) { @@ -26,6 +29,7 @@ func TestStartercodeOverrides(t *testing.T) { viper.Set("course.a1.startercode", map[string]string{"url": "git@example.org:starter.git"}) viper.Set("course.a1.startercode.url", "git@example.org:starter.git") viper.Set("course.a1.startercode.fromBranch", "template") + viper.Set("course.a1.startercode.tag", "v1.2.3") viper.Set("course.a1.startercode.toBranch", "submission") viper.Set("course.a1.startercode.additionalBranches", []string{"release", "demo"}) @@ -33,6 +37,9 @@ func TestStartercodeOverrides(t *testing.T) { if s.FromBranch != "template" || s.ToBranch != "submission" { t.Fatalf("startercode branches = %#v", s) } + if s.Tag != "v1.2.3" { + t.Fatalf("startercode tag = %q, want %q", s.Tag, "v1.2.3") + } if !reflect.DeepEqual(s.AdditionalBranches, []string{"release", "demo"}) { t.Fatalf("startercode additional branches = %#v", s.AdditionalBranches) } diff --git a/config/show.go b/config/show.go index 1ec835b..aa460fd 100644 --- a/config/show.go +++ b/config/show.go @@ -109,6 +109,7 @@ func (cfg *AssignmentConfig) Show() { fieldCandidate(0, "Container-Registry"), fieldCandidate(2, "URL"), fieldCandidate(2, "FromBranch"), + fieldCandidate(2, "Tag"), fieldCandidate(2, "ToBranch"), fieldCandidate(2, "AdditionalBranches"), fieldCandidate(2, "ReplicateFromStartercode"), @@ -170,6 +171,7 @@ func (cfg *AssignmentConfig) Show() { } else { writeSectionField("URL", cfg.Startercode.URL) writeSectionField("FromBranch", cfg.Startercode.FromBranch) + writeSectionField("Tag", cfg.Startercode.Tag) writeSectionField("Template", cfg.Startercode.Template) writeSectionField("TemplateMessage", cfg.Startercode.TemplateMessage) writeSectionField("ToBranch", cfg.Startercode.ToBranch) diff --git a/config/types.go b/config/types.go index 12ca586..49e1a9c 100644 --- a/config/types.go +++ b/config/types.go @@ -76,6 +76,7 @@ type Seeder struct { type Startercode struct { URL string FromBranch string + Tag string Template bool TemplateMessage string ToBranch string diff --git a/config/urls_show_test.go b/config/urls_show_test.go index 5a48dbe..ee6ef52 100644 --- a/config/urls_show_test.go +++ b/config/urls_show_test.go @@ -137,6 +137,22 @@ func TestShow_WithStartercode_WithIssues(t *testing.T) { cfg.Show() } +func TestStartercodeURL_UsesFromBranchEvenWhenTagConfigured(t *testing.T) { + cfg := &AssignmentConfig{ + Startercode: &Startercode{ + URL: "git@gitlab.example.org:mpd/starter/blatt-01.git", + FromBranch: "startercode", + Tag: "v1.0.0", + }, + } + + out := captureStdout(t, func() { cfg.StartercodeURL() }) + want := "https://gitlab.example.org/mpd/starter/blatt-01/-/tree/startercode\n" + if out != want { + t.Fatalf("StartercodeURL() = %q, want %q", out, want) + } +} + func TestShow_WithBranches(t *testing.T) { cfg := &AssignmentConfig{ Branches: []BranchRule{{Name: "main", Protect: true, Default: true, AllowForcePush: true}, {Name: "develop", MergeOnly: true, CodeOwnerApprovalRequired: true}}, diff --git a/docs/configuration.md b/docs/configuration.md index a6609a9..6c25154 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -183,6 +183,7 @@ Clone from a starter repository to each student repo: startercode: url: git@gitlab.example.org:mpd/startercode/blatt-01.git fromBranch: template # Clone this branch + # tag: startercode # Optional: create this tag in generated repos at startercode commit toBranch: main # Into this branch additionalBranches: [] # Push starter/ to repo/ ``` @@ -193,6 +194,7 @@ startercode: |---|---|---|---| | `url` | SSH URL of starter repo | — | Required if startercode block exists | | `fromBranch` | Source branch in starter | `main` | Must exist in starter repo | +| `tag` | Tag created in generated repos | empty | Marks the pushed startercode commit in each target repo | | `toBranch` | Target branch in new repos | `main` | Usually production branch | | `additionalBranches` | Additional branches mirrored from starter repo | `[]` | Each `x` maps `starter/x -> repo/x` | diff --git a/git/sourcerepo.go b/git/sourcerepo.go index 3bc6653..19318f7 100644 --- a/git/sourcerepo.go +++ b/git/sourcerepo.go @@ -17,6 +17,7 @@ import ( ) func PrepareSourceRepo(url, fromBranch string, singleCommit bool, commitMessage string) (*SourceRepo, error) { + cfg := yacspin.Config{ Frequency: 100 * time.Millisecond, CharSet: yacspin.CharSets[69], @@ -125,7 +126,7 @@ func PrepareSourceRepo(url, fromBranch string, singleCommit bool, commitMessage return nil, err } - singleCommitBranchName := fmt.Sprintf("orphan-%s-%d", fromBranch, time.Now().UnixNano()) + singleCommitBranchName := fmt.Sprintf("orphan-%s-%d", sourceRef.Short(), time.Now().UnixNano()) refName := plumbing.NewBranchReferenceName(singleCommitBranchName) committerName := "glabs" diff --git a/gitlab/generate.go b/gitlab/generate.go index 4bec0c1..c9b6c84 100644 --- a/gitlab/generate.go +++ b/gitlab/generate.go @@ -29,7 +29,12 @@ func (c *Client) Generate(assignmentCfg *config.AssignmentConfig) { var starterrepo *git.SourceRepo if assignmentCfg.Startercode != nil { - starterrepo, err = git.PrepareSourceRepo(assignmentCfg.Startercode.URL, assignmentCfg.Startercode.FromBranch, assignmentCfg.Startercode.Template, assignmentCfg.Startercode.TemplateMessage) + starterrepo, err = git.PrepareSourceRepo( + assignmentCfg.Startercode.URL, + assignmentCfg.Startercode.FromBranch, + assignmentCfg.Startercode.Template, + assignmentCfg.Startercode.TemplateMessage, + ) if err != nil { fmt.Println(err) diff --git a/gitlab/starterrepo.go b/gitlab/starterrepo.go index 6336ea9..334d17d 100644 --- a/gitlab/starterrepo.go +++ b/gitlab/starterrepo.go @@ -2,9 +2,11 @@ package gitlab import ( "fmt" + "strings" git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" cfg "github.com/obcode/glabs/v2/config" g "github.com/obcode/glabs/v2/git" "github.com/rs/zerolog/log" @@ -52,6 +54,36 @@ func (c *Client) pushStartercode(assignmentCfg *cfg.AssignmentConfig, from *g.So return fmt.Errorf("cannot push to remote: %w", err) } + tagName := strings.TrimSpace(assignmentCfg.Startercode.Tag) + if tagName != "" { + sourceRef, err := from.Repo.Reference(from.Ref, true) + if err != nil { + return fmt.Errorf("cannot resolve source reference for tag %q: %w", tagName, err) + } + + tagRef := plumbing.NewTagReferenceName(tagName) + if err := from.Repo.Storer.SetReference(plumbing.NewHashReference(tagRef, sourceRef.Hash())); err != nil { + return fmt.Errorf("cannot set local tag %q: %w", tagName, err) + } + + tagRefSpec := config.RefSpec(fmt.Sprintf("+%s:%s", tagRef.String(), tagRef.String())) + log.Debug(). + Str("refSpec", string(tagRefSpec)). + Str("name", project.Name). + Str("toURL", project.SSHURLToRepo). + Str("tag", tagName). + Msg("pushing startercode tag") + + err = from.Repo.Push(&git.PushOptions{ + RemoteName: remote.Config().Name, + RefSpecs: []config.RefSpec{tagRefSpec}, + Auth: from.Auth, + }) + if err != nil { + return fmt.Errorf("cannot push startercode tag %q to remote: %w", tagName, err) + } + } + for _, additionalBranch := range assignmentCfg.Startercode.AdditionalBranches { if additionalBranch == "" { continue diff --git a/gitlab/update.go b/gitlab/update.go index ab1699c..30a2c90 100644 --- a/gitlab/update.go +++ b/gitlab/update.go @@ -22,7 +22,12 @@ func (c *Client) Update(assignmentCfg *config.AssignmentConfig) { var starterrepo *git.SourceRepo if assignmentCfg.Startercode != nil { - starterrepo, err = git.PrepareSourceRepo(assignmentCfg.Startercode.URL, assignmentCfg.Startercode.FromBranch, assignmentCfg.Startercode.Template, assignmentCfg.Startercode.TemplateMessage) + starterrepo, err = git.PrepareSourceRepo( + assignmentCfg.Startercode.URL, + assignmentCfg.Startercode.FromBranch, + assignmentCfg.Startercode.Template, + assignmentCfg.Startercode.TemplateMessage, + ) if err != nil { fmt.Println(err)