1package cli
2
3import (
4 "fmt"
5 "time"
6
7 "arche/internal/object"
8 "arche/internal/store"
9 "arche/internal/wc"
10
11 "github.com/spf13/cobra"
12)
13
14var stackLandBookmark string
15
16var stackLandCmd = &cobra.Command{
17 Use: "land [change-id]",
18 Short: "Promote a draft change to public (land it)",
19 Long: `arche stack land promotes a draft change to public phase, marking it as
20permanently landed. If no change-id is given, the oldest (bottom-most) draft
21in the current stack is landed.
22
23The canonical commit for the change is set to public phase. Any draft commits
24higher in the stack remain draft and are still parented to the now-public commit
25— run 'arche stack rebase' afterwards if any re-chaining is needed.
26
27Use --bookmark to advance (or create) a named bookmark to the landed commit,
28which makes it visible as a branch tip on the forge and to other clones.`,
29 Args: cobra.MaximumNArgs(1),
30 RunE: func(cmd *cobra.Command, args []string) error {
31 r := openRepo()
32 defer r.Close()
33
34 chain, err := collectDraftChain(r)
35 if err != nil {
36 return err
37 }
38 if len(chain) == 0 {
39 return fmt.Errorf("no draft commits in stack — nothing to land")
40 }
41
42 var targetChangeID string
43 var targetCommitID [32]byte
44
45 if len(args) == 1 {
46 resolvedID, rerr := resolveRef(r, args[0])
47 if rerr != nil {
48 return rerr
49 }
50 c, cerr := r.ReadCommit(resolvedID)
51 if cerr != nil {
52 return fmt.Errorf("read commit: %w", cerr)
53 }
54
55 found := false
56 for _, e := range chain {
57 if e.commit.ChangeID == c.ChangeID {
58 found = true
59 break
60 }
61 }
62 if !found {
63 return fmt.Errorf("%s is not in the current draft stack", args[0])
64 }
65
66 canonID, cerr2 := r.Store.GetChangeCommit(c.ChangeID)
67 if cerr2 != nil || canonID == object.ZeroID {
68 canonID = resolvedID
69 }
70 targetCommitID = canonID
71 targetChangeID = c.ChangeID
72 } else {
73 e := chain[0]
74 canonID, cerr := r.Store.GetChangeCommit(e.commit.ChangeID)
75 if cerr != nil || canonID == object.ZeroID {
76 canonID = e.commitID
77 }
78 targetCommitID = canonID
79 targetChangeID = e.commit.ChangeID
80 }
81
82 targetCommit, err := r.ReadCommit(targetCommitID)
83 if err != nil {
84 return fmt.Errorf("read target commit: %w", err)
85 }
86
87 before, _ := r.CaptureRefState()
88 now := time.Now()
89
90 tx, err := r.Store.Begin()
91 if err != nil {
92 return err
93 }
94
95 if err := r.Store.SetPhase(tx, targetCommitID, object.PhasePublic); err != nil {
96 r.Store.Rollback(tx)
97 return fmt.Errorf("set phase: %w", err)
98 }
99
100 if stackLandBookmark != "" {
101 bm := store.Bookmark{Name: stackLandBookmark, CommitID: targetCommitID}
102 if err := r.Store.SetBookmark(tx, bm); err != nil {
103 r.Store.Rollback(tx)
104 return fmt.Errorf("set bookmark %q: %w", stackLandBookmark, err)
105 }
106 }
107
108 changeIDFmt := object.FormatChangeID(targetChangeID)
109 opAfter := buildMergeRefState(targetCommitID, changeIDFmt)
110 op := store.Operation{
111 Kind: "stack-land",
112 Timestamp: now.Unix(),
113 Before: before,
114 After: opAfter,
115 Metadata: fmt.Sprintf("landed %s", changeIDFmt),
116 }
117 if _, err := r.Store.InsertOperation(tx, op); err != nil {
118 r.Store.Rollback(tx)
119 return err
120 }
121
122 if err := r.Store.Commit(tx); err != nil {
123 return err
124 }
125
126 fmt.Printf("Landed %s — %s\n", changeIDFmt, bisectFirstLine(targetCommit.Message))
127 if stackLandBookmark != "" {
128 fmt.Printf("Bookmark %q → %x\n", stackLandBookmark, targetCommitID[:6])
129 }
130
131 _, headID, herr := r.HeadCommit()
132 if herr == nil && headID == targetCommitID {
133 w := wc.New(r)
134 _ = w.Materialize(targetCommit.TreeID, changeIDFmt)
135 }
136
137 remaining := 0
138 for _, e := range chain {
139 if e.commit.ChangeID != targetChangeID {
140 remaining++
141 }
142 }
143 if remaining > 0 {
144 fmt.Printf("%d draft commit(s) remain above the landed change.\n", remaining)
145 fmt.Println("Run 'arche stack rebase' if any re-chaining is needed.")
146 }
147
148 return nil
149 },
150}
151
152func init() {
153 stackLandCmd.Flags().StringVar(&stackLandBookmark, "bookmark", "", "create or advance this bookmark to the landed commit")
154}