1package cli
2
3import (
4 "encoding/json"
5 "fmt"
6 "time"
7
8 "arche/internal/repo"
9 "arche/internal/store"
10
11 "github.com/spf13/cobra"
12)
13
14var bookmarkCmd = &cobra.Command{
15 Use: "bookmark",
16 Short: "Bookmark subcommands",
17}
18
19var bookmarkSetCmd = &cobra.Command{
20 Use: "set <name> [commit]",
21 Short: "Create or update a bookmark pointing to HEAD or a given commit",
22 Args: cobra.RangeArgs(1, 2),
23 RunE: func(cmd *cobra.Command, args []string) error {
24 r := openRepo()
25 defer r.Close()
26
27 var commitID [32]byte
28 if len(args) == 2 {
29 resolved, err := resolveRef(r, args[1])
30 if err != nil {
31 return fmt.Errorf("resolve %q: %w", args[1], err)
32 }
33 commitID = resolved
34 } else {
35 _, id, err := r.HeadCommit()
36 if err != nil {
37 return err
38 }
39 commitID = id
40 }
41
42 before, _ := r.CaptureRefState()
43
44 var beforeState repo.RefState
45 _ = json.Unmarshal([]byte(before), &beforeState)
46 afterState := beforeState
47 if afterState.Bookmarks == nil {
48 afterState.Bookmarks = make(map[string]string)
49 }
50 afterState.Bookmarks[args[0]] = fmt.Sprintf("%x", commitID)
51 afterBytes, _ := json.Marshal(afterState)
52 after := string(afterBytes)
53
54 tx, err := r.Store.Begin()
55 if err != nil {
56 return err
57 }
58 if err := r.Store.SetBookmark(tx, store.Bookmark{Name: args[0], CommitID: commitID}); err != nil {
59 r.Store.Rollback(tx)
60 return err
61 }
62 op := store.Operation{
63 Kind: "bookmark-set", Timestamp: time.Now().Unix(), Before: before, After: after,
64 Metadata: fmt.Sprintf("bookmark %q → %x", args[0], commitID[:6]),
65 }
66 if _, err := r.Store.InsertOperation(tx, op); err != nil {
67 r.Store.Rollback(tx)
68 return err
69 }
70 if err := r.Store.Commit(tx); err != nil {
71 return err
72 }
73
74 fmt.Printf("Bookmark '%s' set to %x\n", args[0], commitID[:8])
75 return nil
76 },
77}
78
79var bookmarkDeleteCmd = &cobra.Command{
80 Use: "delete <name>",
81 Aliases: []string{"rm", "remove"},
82 Short: "Delete a bookmark",
83 Args: cobra.ExactArgs(1),
84 RunE: func(cmd *cobra.Command, args []string) error {
85 r := openRepo()
86 defer r.Close()
87
88 before, _ := r.CaptureRefState()
89
90 var beforeState repo.RefState
91 _ = json.Unmarshal([]byte(before), &beforeState)
92 afterState := beforeState
93 if afterState.Bookmarks == nil {
94 afterState.Bookmarks = make(map[string]string)
95 }
96 delete(afterState.Bookmarks, args[0])
97 afterBytes, _ := json.Marshal(afterState)
98 after := string(afterBytes)
99
100 tx, err := r.Store.Begin()
101 if err != nil {
102 return err
103 }
104 if err := r.Store.DeleteBookmark(tx, args[0]); err != nil {
105 r.Store.Rollback(tx)
106 return err
107 }
108 op := store.Operation{
109 Kind: "bookmark-delete", Timestamp: time.Now().Unix(), Before: before, After: after,
110 Metadata: fmt.Sprintf("deleted bookmark %q", args[0]),
111 }
112 if _, err := r.Store.InsertOperation(tx, op); err != nil {
113 r.Store.Rollback(tx)
114 return err
115 }
116 if err := r.Store.Commit(tx); err != nil {
117 return err
118 }
119 fmt.Printf("Bookmark '%s' deleted.\n", args[0])
120 return nil
121 },
122}
123
124var bookmarkListCmd = &cobra.Command{
125 Use: "list",
126 Short: "List all bookmarks",
127 RunE: func(cmd *cobra.Command, args []string) error {
128 r := openRepo()
129 defer r.Close()
130
131 bms, err := r.Store.ListBookmarks()
132 if err != nil {
133 return err
134 }
135 if len(bms) == 0 {
136 fmt.Println("No bookmarks.")
137 return nil
138 }
139 _, headID, _ := r.HeadCommit()
140 for _, bm := range bms {
141 marker := " "
142 if bm.CommitID == headID {
143 marker = "> "
144 }
145 remote := ""
146 if bm.Remote != "" {
147 remote = " (remote: " + bm.Remote + ")"
148 }
149 fmt.Printf("%s%-24s %x%s\n", marker, bm.Name, bm.CommitID[:8], remote)
150 }
151 return nil
152 },
153}
154
155func init() {
156 bookmarkCmd.AddCommand(bookmarkSetCmd, bookmarkDeleteCmd, bookmarkListCmd)
157}