WsReleaseRollbackDraftMojo.java

package network.ike.plugin.ws;

import network.ike.workspace.WorkspaceGraph;
import org.apache.maven.api.plugin.MojoException;
import org.apache.maven.api.plugin.annotations.Mojo;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * Preview rolling the working set back from a failed release mission
 * (IKE-Network/ike-issues#1010).
 *
 * <p>For the root and every cloned member, reports the release-cadence
 * commits sitting unpushed on top of the branch, the local tags on
 * them, and the commit a rollback would reset to. Pushed history is
 * never touched; repositories with uncommitted changes, no upstream,
 * or mission commits buried beneath later work are reported as refusals
 * — and {@code ws:release-rollback-publish} is all-or-nothing over the
 * set, refusing while any repository refuses.
 *
 * <p>Usage: {@code mvn ws:release-rollback-draft}
 */
@Mojo(name = "release-rollback-draft", projectRequired = false,
        aggregator = true)
public class WsReleaseRollbackDraftMojo extends AbstractWorkspaceMojo {

    /** Draft by default; the publish subclass flips it. */
    boolean publish = false;

    /** Creates this goal instance. */
    public WsReleaseRollbackDraftMojo() {}

    @Override
    protected WorkspaceReportSpec runGoal() throws MojoException {
        WorkspaceGraph graph = loadGraph();
        File root = workspaceRoot();
        List<String> members = graph.topologicalSort(
                new LinkedHashSet<>(graph.manifest().subprojects().keySet()));

        getLog().info("");
        getLog().info("ike-komet-wsr — Release rollback"
                .replace("ike-komet-wsr", root.getName()));
        getLog().info("══════════════════════════════════════════════════");
        getLog().info(publish
                ? "  Mode:  PUBLISH — resets and tag deletions are real"
                : "  Mode:  DRAFT — nothing is reset");
        getLog().info("");

        List<WorkspaceReleaseRollback.RepoPlan> plans = new ArrayList<>();
        List<String> refusals = new ArrayList<>();
        for (String name : members) {
            File dir = new File(root, name);
            if (!new File(dir, ".git").exists()) {
                getLog().info("  ⚠ " + name + " — not cloned, skipping");
                continue;
            }
            plans.add(WorkspaceReleaseRollback.plan(name, dir));
        }
        plans.add(WorkspaceReleaseRollback.plan("(workspace root)", root));

        int discarding = 0;
        for (WorkspaceReleaseRollback.RepoPlan plan : plans) {
            if (plan.refusal() != null) {
                refusals.add(plan.name() + ": " + plan.refusal());
                getLog().error("  ✗ " + plan.name() + " — " + plan.refusal());
                continue;
            }
            if (!plan.hasWork()) {
                getLog().info("  = " + plan.name() + " — nothing to roll back");
                continue;
            }
            discarding++;
            getLog().info("  ↩ " + plan.name() + " — reset to "
                    + plan.targetSha().substring(0, 9));
            for (String line : plan.discards()) {
                getLog().info("      discard " + line);
            }
            for (String tag : plan.tags()) {
                getLog().info("      delete tag " + tag);
            }
        }
        getLog().info("");

        if (!refusals.isEmpty()) {
            if (publish) {
                throw new MojoException("Rollback refused — the working set"
                        + " rolls back atomically and " + refusals.size()
                        + " repositories block it:\n    "
                        + String.join("\n    ", refusals));
            }
            getLog().warn("Publish would refuse: " + refusals.size()
                    + " repositories block an atomic rollback.");
        }

        if (publish && discarding > 0) {
            for (WorkspaceReleaseRollback.RepoPlan plan : plans) {
                if (plan.hasWork()) {
                    getLog().info("  Rolling back " + plan.name());
                    WorkspaceReleaseRollback.apply(plan, getLog());
                }
            }
            getLog().info("Rollback complete: " + discarding
                    + " repositories reset.");
        } else if (!publish) {
            getLog().info("[DRAFT] " + discarding + " repositories would"
                    + " reset; run ws:release-rollback-publish to apply.");
        } else {
            getLog().info("Nothing to roll back anywhere.");
        }

        return new WorkspaceReportSpec(
                publish ? WsGoal.RELEASE_ROLLBACK_PUBLISH
                        : WsGoal.RELEASE_ROLLBACK_DRAFT,
                "# Release rollback\n\n" + discarding
                        + " repositories with mission commits; "
                        + refusals.size() + " refusals.\n");
    }
}