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 shelveCmd = &cobra.Command{
15 Use: "shelve [name]",
16 Short: "Save and revert working copy changes without creating a commit",
17 Long: `Capture the current working copy as a named shelf, then revert the working
18copy to HEAD. The shelf is stored in store.db and is not visible in arche log.
19
20 arche shelve [name] - save WC changes; default name is "default"
21 arche unshelve [name] - restore a shelf onto the working copy
22 arche shelve list - list all shelves
23 arche shelve drop <name> - delete a shelf`,
24 Args: cobra.MaximumNArgs(1),
25 RunE: func(cmd *cobra.Command, args []string) error {
26 r := openRepo()
27 defer r.Close()
28
29 ss, ok := r.Store.(store.ShelfStore)
30 if !ok {
31 return fmt.Errorf("store does not support shelves")
32 }
33
34 name := "default"
35 if len(args) == 1 {
36 name = args[0]
37 }
38
39 existing, err := ss.GetShelf(name)
40 if err != nil {
41 return err
42 }
43 if existing != nil {
44 return fmt.Errorf("shelf %q already exists; drop it first with: arche shelve drop %s", name, name)
45 }
46
47 head, headID, err := r.HeadCommit()
48 if err != nil {
49 return err
50 }
51
52 w := wc.New(r)
53
54 treeID, err := w.SnapshotTree()
55 if err != nil {
56 return fmt.Errorf("snapshot tree: %w", err)
57 }
58
59 if treeID == head.TreeID {
60 return fmt.Errorf("nothing to shelve: working copy is clean")
61 }
62
63 tx, err := r.Store.Begin()
64 if err != nil {
65 return err
66 }
67 sh := store.Shelf{
68 Name: name,
69 TreeID: treeID,
70 BaseCommitID: headID,
71 CreatedAt: time.Now().Unix(),
72 Description: shelveDesc,
73 }
74 if err := ss.CreateShelf(tx, sh); err != nil {
75 r.Store.Rollback(tx) //nolint:errcheck
76 return err
77 }
78 if err := r.Store.Commit(tx); err != nil {
79 return err
80 }
81
82 headChangeID := "ch:" + object.FormatChangeID(head.ChangeID)
83 if err := w.Materialize(head.TreeID, headChangeID); err != nil {
84 return fmt.Errorf("revert WC to HEAD: %w", err)
85 }
86
87 fmt.Printf("Shelved changes as %q (base %x)\n", name, headID[:4])
88 return nil
89 },
90}
91
92var shelveDesc string
93
94var shelveListCmd = &cobra.Command{
95 Use: "list",
96 Short: "List all shelves",
97 RunE: func(cmd *cobra.Command, args []string) error {
98 r := openRepo()
99 defer r.Close()
100
101 ss, ok := r.Store.(store.ShelfStore)
102 if !ok {
103 return fmt.Errorf("store does not support shelves")
104 }
105
106 shelves, err := ss.ListShelves()
107 if err != nil {
108 return err
109 }
110 if len(shelves) == 0 {
111 fmt.Println("(no shelves)")
112 return nil
113 }
114 for _, s := range shelves {
115 ts := time.Unix(s.CreatedAt, 0).Format("2006-01-02")
116 desc := s.Description
117 if desc == "" {
118 desc = "(no description)"
119 }
120 fmt.Printf("%-20s %s %s\n", s.Name, ts, desc)
121 }
122 return nil
123 },
124}
125
126var shelveDropCmd = &cobra.Command{
127 Use: "drop <name>",
128 Short: "Delete a shelf",
129 Args: cobra.ExactArgs(1),
130 RunE: func(cmd *cobra.Command, args []string) error {
131 r := openRepo()
132 defer r.Close()
133
134 ss, ok := r.Store.(store.ShelfStore)
135 if !ok {
136 return fmt.Errorf("store does not support shelves")
137 }
138
139 tx, err := r.Store.Begin()
140 if err != nil {
141 return err
142 }
143 if err := ss.DropShelf(tx, args[0]); err != nil {
144 r.Store.Rollback(tx) //nolint:errcheck
145 return err
146 }
147 if err := r.Store.Commit(tx); err != nil {
148 return err
149 }
150 fmt.Printf("Dropped shelf %q\n", args[0])
151 return nil
152 },
153}
154
155var unshelveCmd = &cobra.Command{
156 Use: "unshelve [name]",
157 Aliases: []string{"shelf-apply"},
158 Short: "Apply a shelved WC snapshot onto the current working copy",
159 Long: `Restore a previously shelved working copy state. The shelf is not removed
160after unshelving; use arche shelve drop <name> to delete it.`,
161 Args: cobra.MaximumNArgs(1),
162 RunE: func(cmd *cobra.Command, args []string) error {
163 r := openRepo()
164 defer r.Close()
165
166 ss, ok := r.Store.(store.ShelfStore)
167 if !ok {
168 return fmt.Errorf("store does not support shelves")
169 }
170
171 name := "default"
172 if len(args) == 1 {
173 name = args[0]
174 }
175
176 sh, err := ss.GetShelf(name)
177 if err != nil {
178 return err
179 }
180 if sh == nil {
181 return fmt.Errorf("shelf %q not found; list with: arche shelve list", name)
182 }
183
184 head, _, err := r.HeadCommit()
185 if err != nil {
186 return err
187 }
188
189 w := wc.New(r)
190 headChangeID := "ch:" + object.FormatChangeID(head.ChangeID)
191 if err := w.Materialize(sh.TreeID, headChangeID); err != nil {
192 return fmt.Errorf("apply shelf: %w", err)
193 }
194
195 fmt.Printf("Applied shelf %q onto working copy (HEAD stays at ch:%s)\n",
196 name, object.FormatChangeID(head.ChangeID))
197 return nil
198 },
199}
200
201func init() {
202 shelveCmd.AddCommand(shelveListCmd, shelveDropCmd)
203 shelveCmd.Flags().StringVarP(&shelveDesc, "message", "m", "", "Description for the shelf")
204}