Skip to content

Commit 46faa06

Browse files
authored
fix: tag create --all iterates all modules with per-module prefix (#251)
* fix: tag create --all now iterates all modules with per-module prefix resolution Previously tag create --all only processed the first module (modules[0]). Now it iterates all discovered modules, resolves per-module tag prefix via config merge, skips duplicate tags with info message, and continues on individual module failure with a summary error. * test: tag create --all multi-module iteration tests Add TestCreateTagsForAllModules with 4 subtests (all succeed, missing version file, duplicate tag skipped, creation failure continues) and TestResolveModuleConfig with 2 subtests. * fix: update TestCLI_TagCreate_MultiModule for new --all behavior The test expected single-module output (Using version from module) but now --all iterates all modules. Updated assertions to match new behavior.
1 parent 7126a47 commit 46faa06

2 files changed

Lines changed: 365 additions & 9 deletions

File tree

internal/commands/tag/tagcmd.go

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,32 @@ func resolveModuleConfig(cfg *config.Config, path string) (*config.Config, strin
151151
}
152152

153153
// runCreateCmd creates a git tag for the current version.
154+
// When --all is used with multiple modules, it iterates all modules and creates
155+
// a tag for each one instead of only tagging the first module.
154156
func (tc *TagCommand) runCreateCmd(ctx context.Context, cmd *cli.Command, cfg *config.Config) error {
155-
path, err := resolveVersionPath(ctx, cmd, cfg)
157+
execCtx, err := clix.GetExecutionContext(ctx, cmd, cfg)
156158
if err != nil {
157159
return err
158160
}
159161

162+
// Multi-module mode: create tags for all modules.
163+
if execCtx.IsMultiModule() && len(execCtx.Modules) > 1 {
164+
return tc.createTagsForAllModules(ctx, cmd, cfg, execCtx)
165+
}
166+
167+
// Single-module mode (or multi-module with only one selected module).
168+
path := execCtx.Path
169+
if execCtx.IsMultiModule() && len(execCtx.Modules) == 1 {
170+
mod := execCtx.Modules[0]
171+
printer.PrintInfo(fmt.Sprintf("Using version from module %q (%s)", mod.Name, mod.RelPath))
172+
path = mod.Path
173+
}
174+
175+
return tc.createTagForPath(ctx, cmd, cfg, path)
176+
}
177+
178+
// createTagForPath creates a single git tag for the version at the given path.
179+
func (tc *TagCommand) createTagForPath(ctx context.Context, cmd *cli.Command, cfg *config.Config, path string) error {
160180
version, err := semver.ReadVersion(path)
161181
if err != nil {
162182
return fmt.Errorf("failed to read version from %s: %w", path, err)
@@ -198,6 +218,74 @@ func (tc *TagCommand) runCreateCmd(ctx context.Context, cmd *cli.Command, cfg *c
198218
return nil
199219
}
200220

221+
// createTagsForAllModules iterates all modules in the execution context and
222+
// creates a tag for each one. It continues on individual module failure and
223+
// returns a summary error if any modules failed.
224+
func (tc *TagCommand) createTagsForAllModules(ctx context.Context, cmd *cli.Command, cfg *config.Config, execCtx *clix.ExecutionContext) error {
225+
var failedModules []string
226+
totalModules := len(execCtx.Modules)
227+
228+
shouldPush := cmd.Bool("push")
229+
230+
for _, mod := range execCtx.Modules {
231+
printer.PrintInfo(fmt.Sprintf("Processing module %q (%s)", mod.Name, mod.RelPath))
232+
233+
version, err := semver.ReadVersion(mod.Path)
234+
if err != nil {
235+
printer.PrintError(fmt.Sprintf(" Failed to read version for module %q: %v", mod.Name, err))
236+
failedModules = append(failedModules, mod.Name)
237+
continue
238+
}
239+
240+
effectiveCfg, modulePath := resolveModuleConfig(cfg, mod.Path)
241+
tmConfig := buildTagManagerConfig(effectiveCfg)
242+
prefix := tagmanager.InterpolatePrefix(tmConfig.Prefix, modulePath)
243+
tagName := prefix + version.String()
244+
245+
exists, err := tc.gitOps.TagExists(ctx, tagName)
246+
if err != nil {
247+
printer.PrintError(fmt.Sprintf(" Failed to check tag existence for module %q: %v", mod.Name, err))
248+
failedModules = append(failedModules, mod.Name)
249+
continue
250+
}
251+
if exists {
252+
printer.PrintInfo(fmt.Sprintf(" Tag %s already exists, skipping module %q", tagName, mod.Name))
253+
continue
254+
}
255+
256+
message := cmd.String("message")
257+
if message == "" {
258+
data := tagmanager.NewTemplateData(version, prefix, modulePath)
259+
message = tagmanager.FormatMessage(tmConfig.MessageTemplate, data)
260+
}
261+
262+
if err := tc.createTag(ctx, tagName, message, tmConfig); err != nil {
263+
printer.PrintError(fmt.Sprintf(" Failed to create tag for module %q: %v", mod.Name, err))
264+
failedModules = append(failedModules, mod.Name)
265+
continue
266+
}
267+
268+
printer.PrintSuccess(fmt.Sprintf(" Created tag %s for module %q", tagName, mod.Name))
269+
270+
pushThis := shouldPush || tmConfig.Push
271+
if pushThis {
272+
if err := tc.gitOps.PushTag(ctx, tagName); err != nil {
273+
printer.PrintError(fmt.Sprintf(" Failed to push tag %s for module %q: %v", tagName, mod.Name, err))
274+
failedModules = append(failedModules, mod.Name)
275+
continue
276+
}
277+
printer.PrintSuccess(fmt.Sprintf(" Pushed tag %s to remote", tagName))
278+
}
279+
}
280+
281+
if len(failedModules) > 0 {
282+
return fmt.Errorf("%d of %d module(s) failed tag creation: %s",
283+
len(failedModules), totalModules, strings.Join(failedModules, ", "))
284+
}
285+
286+
return nil
287+
}
288+
201289
// createTag creates a tag based on the configuration.
202290
func (tc *TagCommand) createTag(ctx context.Context, tagName, message string, cfg *tagmanager.Config) error {
203291
switch {

0 commit comments

Comments
 (0)