Class FeatureStartSiblingPublishMojo

java.lang.Object
network.ike.plugin.ws.FeatureStartSiblingPublishMojo
All Implemented Interfaces:
org.apache.maven.api.plugin.Mojo
Direct Known Subclasses:
FeatureStartSiblingPublishVerifyMojo

@Mojo(name="feature-start-sibling-publish", projectRequired=false, aggregator=true) public class FeatureStartSiblingPublishMojo extends Object
Start a feature in a sibling workspace clone beside the primary (IKE-Network/ike-issues#201 epic, #207, reshaped into the feature-start 2×2 in #770).

This is the sibling column of ws:feature-start: where ws:feature-start-publish branches the primary in place — which under Syncthing rewrites the working tree while the other machine's .git/HEAD stays put, the failure mode that motivates this goal — ws:feature-start-sibling-publish makes a second clone of the whole workspace alongside the primary, checked out on feature/<name> from inception. The primary never leaves its current branch; the feature lives in its own directory and is disposable (rm -rf) after merge. The same isolation makes concurrent same-machine work safe: each sibling has its own working tree, so two lines of work never stage each other's edits.

What it does (workspace mode):

  1. Validates feature through FeatureName and computes the sibling directory <parent>/<baseName>-<feature>/ from the resolved working set's baseName; refuses if it already exists.
  2. Resolves the base branch to clone from: -Dfrom when given, else the primary's current branch — guarded so a sibling is not silently cut from an unexpected branch (see SiblingBaseResolution).
  3. Clones the workspace root, then each subproject in topological order, into the sibling with git clone --reference <primary>/<component> --dissociate -b <base> <remote> <sibling>/<component>. --reference borrows the object database from the primary's local clone (the order-of-magnitude win for large histories like tinkar-core's 492 MB); --dissociate then copies the borrowed objects so the sibling is fully self-contained.
  4. Creates feature/<name> in each clone and applies the same version qualification and BOM/property cascade that ws:feature-start-publish produces in-place, via the shared FeatureStartSupport.
  5. Rewrites the sibling's workspace.yaml branch fields and commits.

No push. Externally visible side effects stay opt-in (per feedback_workspace_ops_completion). The clones, branches, version commits, and workspace.yaml update are all recoverable local effects and happen by default; pushing to origin is not.

mvn ws:feature-start-sibling-publish -Dfeature=jira-456
#   creates ../<workspace>-jira-456/ with every component on
#   feature/jira-456, then: cd ../<workspace>-jira-456 && work.
See Also:
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates this goal instance.
  • Method Summary

    Modifier and Type
    Method
    Description
    protected boolean
    Whether the -Dallow-uncommitted escape hatch is set — bypasses a TreePreflight.REQUIRE_UNMODIFIED preflight (#780).
    protected boolean
    confirm(String label, boolean defaultYes)
    Prompt the user with a yes/no question, accepting "y"/"yes"/"n"/"no" (case-insensitive).
    protected boolean
    Whether the -Ddefer-commit cascade hand-off is set — the goal skips its own TreePreflight.REQUIRE_UNMODIFIED preflight and AuthoredCommit.IN_ISOLATION commit so the invoking goal owns the commit (AuthoredCommit.DEFER_TO_CALLER, #780).
    final void
    Run the goal and write its report.
    protected org.apache.maven.api.plugin.Log
    Access the Maven logger.
    protected network.ike.plugin.support.IkePrompter
    The IkePrompter for this goal — built lazily from the session's interactive flag, or the test-injected instance.
    protected org.apache.maven.api.Session
    Access the Maven session injected by Maven 4's plugin DI.
    protected String
    gitBranch(File subprojectDir)
    Get the current branch of a subproject directory.
    protected String
    gitShortSha(File subprojectDir)
    Get the short SHA of HEAD for a subproject directory.
    protected String
    gitStatus(File subprojectDir)
    Run git status --porcelain on a subproject directory and return the output (empty string = clean).
    protected String
    header(String goalName)
    Format a goal header line using the workspace name.
    protected boolean
    Check whether a workspace.yaml exists in the directory hierarchy.
    protected network.ike.workspace.WorkspaceGraph
    Load the manifest and build the workspace graph.
    protected WsGoal
    The goal identity used for this run's report file.
    protected String
    requireParam(String currentValue, String propertyName, String promptLabel)
    Prompt the user interactively for a required parameter when it was not supplied on the command line.
    protected network.ike.workspace.Cohort
    Resolve the Cohort an artifact / release-style goal acts on, honoring -Dworkspace.manifest (IKE-Network/ike-issues#612).
    protected Path
    Resolve the manifest path — explicit parameter, or search upward.
    protected network.ike.workspace.WorkingSet
    Resolve the WorkingSet this goal acts on, honoring the -Dworkspace.manifest override and the workspace-vs-single-repo decision in one place (IKE-Network/ike-issues#611).
    Run this goal's work and return the report it produced.
    protected String
    selectFromList(String label, List<String> options)
    Prompt the user to pick from a numbered list.
    protected void
    setLog(org.apache.maven.api.plugin.Log log)
    Replace the logger.
    protected final boolean
    Whether to build the sibling reactor after creating it.
    protected static network.ike.workspace.FeatureName
    Validate a feature-name string with FeatureName.of(String) and surface any rule violation as a clean MojoException (the raw IllegalArgumentException would otherwise bubble up as a Maven internal error).
    protected static network.ike.workspace.MavenVersion
    Validate a Maven version string with MavenVersion.of(String) and surface any rule violation as a MojoException (ike-issues#295).
    protected static network.ike.workspace.SubprojectName
    Validate a subproject-name string with SubprojectName.of(String) and surface any rule violation as a MojoException (ike-issues#295).
    protected boolean
    Default for the post-create reactor build when -Dverify is not given.
    protected void
    verifySibling(File siblingRoot)
    Build the sibling's whole reactor from its root, failing loud if it does not build.
    protected String
    Read the workspace name from the root POM's artifactId.
    protected File
    Resolve the workspace root directory (parent of workspace.yaml).

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • FeatureStartSiblingPublishMojo

      public FeatureStartSiblingPublishMojo()
      Creates this goal instance.
  • Method Details

    • shouldVerify

      protected final boolean shouldVerify()
      Whether to build the sibling reactor after creating it. The base goal returns the -Dverify flag; the -verify subclass overrides verifyByDefault() so the build is on by default.
      Returns:
      true when the post-create reactor build should run
    • verifyByDefault

      protected boolean verifyByDefault()
      Default for the post-create reactor build when -Dverify is not given. false here; FeatureStartSiblingPublishVerifyMojo overrides it to true.
      Returns:
      false — the base goal does not build unless asked
    • reportGoal

      protected WsGoal reportGoal()
      The goal identity used for this run's report file. The base goal reports as WsGoal.FEATURE_START_SIBLING_PUBLISH; the -verify subclass overrides this so its report lands in its own file.
      Returns:
      the WsGoal this invocation reports as
    • verifySibling

      protected void verifySibling(File siblingRoot) throws org.apache.maven.api.plugin.MojoException
      Build the sibling's whole reactor from its root, failing loud if it does not build. Runs verifyGoals (default clean install -DskipTests -T 1C) as a subprocess against the freshly created sibling, mirroring ws:checkpoint-publish's pre-tag reactor gate (#689).

      On failure the clone is left intact — the message says so — so the user can fix the build in place rather than re-clone. Overridable so the #777 coverage can drive the pass/fail outcome without a nested build.

      Parameters:
      siblingRoot - the sibling workspace root to build from
      Throws:
      org.apache.maven.api.plugin.MojoException - if the reactor build fails, or the subprocess setup itself fails
    • runGoal

      protected WorkspaceReportSpec runGoal() throws org.apache.maven.api.plugin.MojoException
      Run this goal's work and return the report it produced.

      Implementations do the goal's actual work here and return a WorkspaceReportSpec — the goal identity and the Markdown body. The base class resolves the workspace root and writes the file. A goal cannot be implemented without producing a report, which is the structural fix for the missing-report bug class (IKE-Network/ike-issues#413 / #407).

      On failure, throw MojoException as usual — a failed goal produces no report, and Maven surfaces the exception.

      Returns:
      the report this goal produced (never null)
      Throws:
      org.apache.maven.api.plugin.MojoException - if the goal fails
    • getLog

      protected org.apache.maven.api.plugin.Log getLog()
      Access the Maven logger.
      Returns:
      the logger instance
    • allowUncommitted

      protected boolean allowUncommitted()
      Whether the -Dallow-uncommitted escape hatch is set — bypasses a TreePreflight.REQUIRE_UNMODIFIED preflight (#780).
      Returns:
      true to proceed despite an uncommitted working tree
    • deferCommit

      protected boolean deferCommit()
      Whether the -Ddefer-commit cascade hand-off is set — the goal skips its own TreePreflight.REQUIRE_UNMODIFIED preflight and AuthoredCommit.IN_ISOLATION commit so the invoking goal owns the commit (AuthoredCommit.DEFER_TO_CALLER, #780).
      Returns:
      true to defer the preflight + commit to the caller
    • getSession

      protected org.apache.maven.api.Session getSession()
      Access the Maven session injected by Maven 4's plugin DI.
      Returns:
      the injected session (may be null in unit tests)
    • setLog

      protected void setLog(org.apache.maven.api.plugin.Log log)
      Replace the logger. Used when a mojo is constructed directly (not via Maven's DI container) and so never had a logger injected — WsSyncMojo drives PullWorkspaceMojo and PushMojo instances it created itself.
      Parameters:
      log - the replacement logger
    • getPrompter

      protected network.ike.plugin.support.IkePrompter getPrompter()
      The IkePrompter for this goal — built lazily from the session's interactive flag, or the test-injected instance. Callers that pass it to a static helper (e.g. FeatureFinishSupport.promptStaleBranchCleanup(File, List, String, String, IkePrompter, Log)) use this.
      Returns:
      the prompter (never null)
    • loadGraph

      protected network.ike.workspace.WorkspaceGraph loadGraph()
      Load the manifest and build the workspace graph.
      Returns:
      the workspace dependency graph
      Throws:
      org.apache.maven.api.plugin.MojoException - if the manifest cannot be read
    • resolveManifest

      protected Path resolveManifest()
      Resolve the manifest path — explicit parameter, or search upward.
      Returns:
      path to the workspace manifest file
      Throws:
      org.apache.maven.api.plugin.MojoException - if the manifest cannot be found
    • workspaceRoot

      protected File workspaceRoot()
      Resolve the workspace root directory (parent of workspace.yaml).
      Returns:
      the workspace root directory
      Throws:
      org.apache.maven.api.plugin.MojoException - if the manifest cannot be found
    • gitStatus

      protected String gitStatus(File subprojectDir)
      Run git status --porcelain on a subproject directory and return the output (empty string = clean).
      Parameters:
      subprojectDir - the subproject directory to check
      Returns:
      git status output, empty if clean
    • gitBranch

      protected String gitBranch(File subprojectDir)
      Get the current branch of a subproject directory.
      Parameters:
      subprojectDir - the subproject directory to check
      Returns:
      the current branch name
    • gitShortSha

      protected String gitShortSha(File subprojectDir)
      Get the short SHA of HEAD for a subproject directory.
      Parameters:
      subprojectDir - the subproject directory to check
      Returns:
      the short SHA of HEAD
    • isWorkspaceMode

      protected boolean isWorkspaceMode()
      Check whether a workspace.yaml exists in the directory hierarchy. Does not throw — returns false if no manifest is found.
      Returns:
      true if running inside a workspace, false for a bare repo
    • resolveWorkingSet

      protected network.ike.workspace.WorkingSet resolveWorkingSet()
      Resolve the WorkingSet this goal acts on, honoring the -Dworkspace.manifest override and the workspace-vs-single-repo decision in one place (IKE-Network/ike-issues#611). When a workspace.yaml is configured or found by searching upward, the working set is that workspace's subprojects plus the root; otherwise it is the current repository — a working set of one. This is the single home for the isWorkspaceMode() + bare-mode branch the working-tree goals otherwise each carry, delegating to the shared WorkingSetResolver.
      Returns:
      the resolved working set (never null)
    • resolveCohort

      protected network.ike.workspace.Cohort resolveCohort()
      Resolve the Cohort an artifact / release-style goal acts on, honoring -Dworkspace.manifest (IKE-Network/ike-issues#612). When a workspace.yaml is configured or found by searching upward, the cohort is its subprojects in topological order (the aggregator root excluded); otherwise it is the current repository — a cohort of one. The dependency-ordered counterpart to resolveWorkingSet(), delegating to the shared CohortResolver.
      Returns:
      the resolved cohort (never null)
    • requireParam

      protected String requireParam(String currentValue, String propertyName, String promptLabel)
      Prompt the user interactively for a required parameter when it was not supplied on the command line.

      Delegates to the IkePrompter (IKE-Network/ike-issues#385): an inline prompt on a real terminal, an own-line prompt in a piped IDE runner. In batch mode it throws a clear error directing the user to pass the property explicitly.

      Parameters:
      currentValue - the value from the @Parameter field (may be null)
      propertyName - the -D property name (for the error message)
      promptLabel - human-readable label shown in the prompt; callers pass it without trailing punctuation (a ": " separator is appended here)
      Returns:
      the resolved value — either the original or user-supplied
      Throws:
      org.apache.maven.api.plugin.MojoException - if no value can be obtained
    • validateFeatureName

      protected static network.ike.workspace.FeatureName validateFeatureName(String feature)
      Validate a feature-name string with FeatureName.of(String) and surface any rule violation as a clean MojoException (the raw IllegalArgumentException would otherwise bubble up as a Maven internal error). Use this anywhere a feature name leaves the -Dfeature= command-line boundary (ike-issues#205).
      Parameters:
      feature - the candidate feature name (must already be resolved — i.e. non-null after requireParam(String, String, String) or auto-detection)
      Returns:
      the validated FeatureName value
      Throws:
      org.apache.maven.api.plugin.MojoException - if feature fails the FeatureName syntax rules
    • validateSubprojectName

      protected static network.ike.workspace.SubprojectName validateSubprojectName(String subproject)
      Validate a subproject-name string with SubprojectName.of(String) and surface any rule violation as a MojoException (ike-issues#295).
      Parameters:
      subproject - the candidate subproject name (already resolved — non-null after requireParam(String, String, String) or POM derivation)
      Returns:
      the validated SubprojectName value
      Throws:
      org.apache.maven.api.plugin.MojoException - if subproject fails the SubprojectName syntax rules
    • validateMavenVersion

      protected static network.ike.workspace.MavenVersion validateMavenVersion(String version)
      Validate a Maven version string with MavenVersion.of(String) and surface any rule violation as a MojoException (ike-issues#295). Per feedback_no_semver_assumption the validator accepts single-segment monotonic, semver-like, calendar-based, and branch-qualified versions; it does not enforce semver.
      Parameters:
      version - the candidate version string
      Returns:
      the validated MavenVersion value
      Throws:
      org.apache.maven.api.plugin.MojoException - if version fails the MavenVersion syntax rules
    • confirm

      protected boolean confirm(String label, boolean defaultYes)
      Prompt the user with a yes/no question, accepting "y"/"yes"/"n"/"no" (case-insensitive). When invoked in a non-interactive context, the default is used.
      Parameters:
      label - the question to display (without trailing punctuation)
      defaultYes - whether true (yes) is the default
      Returns:
      true for yes, false for no
      Throws:
      org.apache.maven.api.plugin.MojoException - if no answer can be obtained
    • selectFromList

      protected String selectFromList(String label, List<String> options)
      Prompt the user to pick from a numbered list. Returns the chosen option, or null when the list is empty.
      Parameters:
      label - prompt header (printed via the Prompter as a message)
      options - ordered list of choices
      Returns:
      the chosen option, or null if options is empty
      Throws:
      org.apache.maven.api.plugin.MojoException - if no valid choice can be obtained
    • workspaceName

      protected String workspaceName()
      Read the workspace name from the root POM's artifactId. Falls back to "Workspace" if the POM cannot be read.
      Returns:
      the workspace name derived from the root POM artifactId
    • header

      protected String header(String goalName)
      Format a goal header line using the workspace name. Example: "komet-ws — Status"
      Parameters:
      goalName - the goal name to display in the header
      Returns:
      the formatted header string
    • execute

      public final void execute() throws org.apache.maven.api.plugin.MojoException
      Run the goal and write its report.

      This method is final: every ws:* goal follows the same template — do the work, then write exactly one report. Subclasses supply the work and the report content by implementing runGoal(); they cannot override execute() to skip the report. That is what makes report-writing structural — a goal that compiles necessarily writes a report, so the #407 bug class (a goal runs fine but silently writes none) becomes compiler-impossible (IKE-Network/ike-issues#413).

      The report lands in its per-goal file at the workspace root (ws꞉goal-name.md); WorkspaceReport self-heals the nearest .gitignore so reports never land in git. A -publish run does not delete the matching -draft report — both are timestamped history (ike-issues#413).

      Specified by:
      execute in interface org.apache.maven.api.plugin.Mojo
      Throws:
      org.apache.maven.api.plugin.MojoException - if the goal fails