Skip to content

Make gut template apply interactive #228

@dylanhand

Description

@dylanhand

Template Apply Improvements

Summary

The current template apply workflow requires multiple manual commands and is error-prone. This document proposes consolidating the entire workflow into a single interactive command.

Current Workflow

The current process requires 6+ separate steps (full instructions):

# 1. Pull template and make changes
gut pull -o giellalt -r ^template-lang-und
# ... edit template files ...

# 2. Manually bump rev_id in .gut/template.toml

# 3. Commit template changes

# 4. Apply to all repos
gut template apply -o giellalt -r ^lang- -t template-lang-und

# 5. Manually inspect repos, resolve conflicts, clean up .rej/.orig files
gut status -v -o giellalt -r ^lang-
find . -name '*.rej' -delete -o -name '*.orig' -delete

# 6. Commit all repos (first commit - the actual changes)
gut commit -o giellalt -r ^lang- -m "[Template merge] Message"

# 7. Update delta.toml (second commit - bookkeeping)
gut template apply --continue -o giellalt -r ^lang- -t template-lang-und

# 8. Push target repos
gut push -o giellalt -r ^lang-

# 9. Push template repo (often forgotten!)
gut push -o giellalt -r template-lang-und

Problems with the Current Workflow

  1. Too many commands -- 6+ separate invocations for what is conceptually one operation.
  2. Two commits per repo -- The instructions produce one commit for the patched files and a second for the delta.toml update. This is unnecessary; --continue does its own commit but requires the repo to be clean first, forcing the user to commit beforehand.
  3. Manual rev_id bumping -- Easy to forget. The rev_id in .gut/template.toml must be incremented by hand before applying.
  4. Template push often forgotten -- If the template repo isn't pushed, the SHA written into target repos' delta.toml doesn't exist on the remote, breaking future applies for other users.
  5. No guidance on conflicts -- After apply, the user must manually find which repos have conflicts, inspect .rej files, resolve them, and clean up temp files.
  6. Redundant arguments across commands -- The same -o giellalt -r ^lang- -t template-lang-und flags must be repeated for every command in the sequence (apply, commit, continue, push). Easy to mistype, and if the regex differs between steps, repos can be missed or double-processed.

Proposed Changes

1. Single Interactive Command

The entire apply-resolve-commit-push cycle becomes one invocation:

gut template apply -t ../template-lang-und -o giellalt -r "^lang-"

The --continue and --abort flags remain as recovery mechanisms for interrupted sessions, and a --no-interactive flag preserves the current behavior for scripting.

2. Auto-bump rev_id

The command automatically increments rev_id in .gut/template.toml and commits the change to the template repo. If there are no new changes since the last apply, the command exits early.

3. Push Template First

The template repo is pushed before applying to target repos. This ensures the SHA written into each target's delta.toml always exists on the remote. This is the safer ordering because:

  • If apply fails partway through, some target repos just haven't been updated yet -- re-running fixes it.
  • The alternative (push template last) risks target repos referencing a SHA that doesn't exist on the remote if the template push fails.

4. Single Commit Per Repo

The patched files and the updated .gut/delta.toml are committed together in one commit. The separate gut commit + gut template apply --continue two-step is eliminated.

The user is prompted once for a commit message, which is formatted as:

[Template merge] <user message> (rev N)

One question here: is [Template merge] required, or simply a convention?

If it's only a convention, then another option is something like:

[Apply template changes N] <user message>

Another nice-to-have here is to offer to automatically use the commit message that was used in the template being applied, e.g., if template-lang-und was changed with commit message "Add dark mode support to documentation", gut could offer to use that in place of <user message> above.

Open to thoughts/suggestions.

5. Auto-commit Clean Repos, Prompt for Conflicts

Repos that patch cleanly are committed immediately. Conflicted repos are presented to the user with details on which files and how many hunks failed. The user is told to go fix them and come back.

6. Warn About Remaining .rej and .orig Files

.rej and .orig files are left in place for the user to reference during conflict resolution. When the user signals they're done resolving, the command checks for any remaining .rej files and warns before committing. This prevents accidentally committing with unresolved conflicts while keeping the user in control of cleanup.

7. Push Target Repos at the End

After all commits, the command offers to push all target repos.

Interactive Flow Example

$ gut template apply -t ../template-lang-und -o giellalt -r "^lang-"

Checking template-lang-und...
  No uncommitted changes.
  3 commits ahead of remote.
  Bumping rev_id 4 → 5... done.
  Push template-lang-und? [Y/n] y
  Pushed. ✓

Applying template to 47 repos...

 Repo              Status
 lang-sme          ✓ Clean
 lang-nob          ✓ Clean
 lang-fin          ✓ Clean
 ...               ...
 lang-smj          ⚠ 1 conflict
 lang-sma          ⚠ 2 conflicts
 lang-fao          ✗ Skipped (repo not clean)

44 repos applied cleanly.
2 repos have conflicts:

  lang-smj:
    Makefile — 1 hunk failed (Makefile.rej)

  lang-sma:
    Makefile — 1 hunk failed (Makefile.rej)
    .github/workflows/build.yml — 1 hunk failed (build.yml.rej)

1 repo skipped:

  lang-fao — has uncommitted changes

Resolve conflicts in the repos listed above, then press Enter to continue.
(Or type "skip" to commit only the clean repos, or "abort" to roll back everything.)
> ⏎

Checking resolved repos...
  lang-smj ✓
  lang-sma — still has .github/workflows/build.yml.rej. Resolved? [y/n] y
  lang-sma ✓

Commit message: Update build system for new CI runner
→ [Template merge] Update build system for new CI runner (rev 5)

Commit 46 repos? [Y/n] y
Committing... ✓

Push 46 repos? [Y/n] y
Pushing... ✓

Done. 46 repos updated to rev 5.

1 repo was skipped (not clean before apply):
  lang-fao

To apply to skipped repos later, clean them and re-run:
  gut template apply -t ../template-lang-und -o giellalt -r "^lang-fao$"

Remaining Considerations

  • rev_id: With the template always pushed before apply, the template_sha in delta.toml should always be valid on the remote, making rev_id no longer needed for the primary code path. However, we keep it for two reasons: (1) human readability in commit messages and delta.toml, and (2) as a fallback recovery mechanism -- if a template repo is force-pushed, rebased, recreated, or shallow-cloned, the original SHA becomes invalid, and rev_id allows the system to walk the template history and find the matching commit. Auto-bumping removes the manual step while preserving this safety net.
  • --no-interactive mode: Preserves current behavior for CI/scripting. Applies patches, prints results, and exits. User runs --continue/--abort manually.
  • Conflict resolution validation: When the user presses Enter after resolving, we should check that .rej files are gone and changes are staged. If not, report which repos still need attention and prompt again.

Out of scope

Git-style merge conflict markers

Currently, when a patch hunk fails, the Unix patch command leaves behind .rej and .orig files. The user must manually compare the .rej (the failed hunk) against the target file to figure out what to do. This is awkward and unfamiliar to most developers.

Ideally, failed hunks would produce inline conflict markers in the target file itself, just like git merge:

<<<<<<< lang-smj (current)
GTLANG=smj
EXTRA_FLAG=true
=======
GTLANG2=smj
>>>>>>> template-lang-und (incoming)

This would let users resolve conflicts with the merge tools they already know (VS Code, IntelliJ, vimdiff, etc.) instead of manually cross-referencing .rej files. It would also mean git diff and gut status would show the conflicts naturally.

This requires replacing the Unix patch command with a three-way merge approach (using the old template state, new template state, and current target state as the three trees), which is a larger architectural change. This can be addressed in a separate issue.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions