arche / internal/cli/cmd_shelve.go

commit a22ffc45
  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}