1package cli
2
3import (
4 "fmt"
5 "time"
6
7 "arche/internal/object"
8 "arche/internal/revset"
9
10 "github.com/spf13/cobra"
11)
12
13var (
14 logLimit int
15 logOps bool
16 logShowSecret bool
17 logWhere string
18)
19
20var logCmd = &cobra.Command{
21 Use: "log",
22 Short: "Show the commit DAG",
23 Long: `Walk the commit graph backwards from HEAD (and all bookmarks) and display
24each commit in reverse chronological order.
25
26With --ops, show the operation log instead (equivalent to 'arche op log').`,
27 RunE: func(cmd *cobra.Command, args []string) error {
28 r := openRepo()
29 defer r.Close()
30
31 if logOps {
32 ops, err := r.Store.ListOperations(logLimit)
33 if err != nil {
34 return err
35 }
36 if len(ops) == 0 {
37 fmt.Println("No operations recorded.")
38 return nil
39 }
40 for _, op := range ops {
41 ts := time.Unix(op.Timestamp, 0).Format("2006-01-02 15:04:05")
42 if op.Metadata != "" {
43 fmt.Printf("#%-4d %-14s %s %s\n", op.Seq, op.Kind, ts, op.Metadata)
44 } else {
45 fmt.Printf("#%-4d %-14s %s\n", op.Seq, op.Kind, ts)
46 }
47 }
48 return nil
49 }
50
51 var whereFilter revset.Func
52 if logWhere != "" {
53 var err error
54 whereFilter, err = revset.Parse(logWhere)
55 if err != nil {
56 return err
57 }
58 }
59
60 tips := make(map[[32]byte]bool)
61 _, headID, err := r.HeadCommit()
62 if err == nil {
63 tips[headID] = true
64 }
65
66 bms, _ := r.Store.ListBookmarks()
67 for _, bm := range bms {
68 tips[bm.CommitID] = true
69 }
70
71 seen := make(map[[32]byte]bool)
72 queue := make([][32]byte, 0, len(tips))
73 for id := range tips {
74 queue = append(queue, id)
75 }
76
77 bmIndex := make(map[[32]byte][]string)
78 for _, bm := range bms {
79 bmIndex[bm.CommitID] = append(bmIndex[bm.CommitID], bm.Name)
80 }
81 var curHeadID [32]byte
82 if _, id, err := r.HeadCommit(); err == nil {
83 curHeadID = id
84 }
85
86 count := 0
87 for len(queue) > 0 && (logLimit <= 0 || count < logLimit) {
88 id := queue[0]
89 queue = queue[1:]
90 if seen[id] {
91 continue
92 }
93 seen[id] = true
94
95 c, err := r.ReadCommit(id)
96 if err != nil {
97 continue
98 }
99
100 if !logShowSecret {
101 phase, _ := r.Store.GetPhase(id)
102 if phase == object.PhaseSecret {
103 for _, p := range c.Parents {
104 if !seen[p] {
105 queue = append(queue, p)
106 }
107 }
108 continue
109 }
110 }
111
112 if whereFilter != nil {
113 phase, _ := r.Store.GetPhase(id)
114 if !whereFilter(id, c, phase) {
115 for _, p := range c.Parents {
116 if !seen[p] {
117 queue = append(queue, p)
118 }
119 }
120 continue
121 }
122 }
123
124 printCommit(id, c, bmIndex[id], curHeadID == id)
125 count++
126
127 for _, p := range c.Parents {
128 if !seen[p] {
129 queue = append(queue, p)
130 }
131 }
132 }
133 return nil
134 },
135}
136
137func init() {
138 logCmd.Flags().IntVarP(&logLimit, "limit", "n", 0, "maximum number of commits to show (0 = all)")
139 logCmd.Flags().BoolVar(&logOps, "ops", false, "show the operation log instead of the commit graph")
140 logCmd.Flags().BoolVarP(&logShowSecret, "secret", "s", false, "include secret commits in output")
141 logCmd.Flags().StringVar(&logWhere, "where", "", `filter commits with a revset expression, e.g. --where 'author(alice) and not public()'`)
142}
143
144func printCommit(id [32]byte, c *object.Commit, bookmarks []string, isHead bool) {
145 prefix := " "
146 if isHead {
147 prefix = "@ "
148 }
149 fmt.Printf("%scommit %x\n", prefix, id)
150 fmt.Printf(" change ch:%s\n", c.ChangeID)
151 if len(bookmarks) > 0 {
152 fmt.Printf(" marks %v\n", bookmarks)
153 }
154 fmt.Printf(" author %s <%s>\n", c.Author.Name, c.Author.Email)
155 fmt.Printf(" date %s\n", c.Author.Timestamp.Format(time.RFC1123))
156 fmt.Printf(" phase %s\n", c.Phase)
157 if c.Message != "" {
158 fmt.Printf("\n %s\n", c.Message)
159 }
160 fmt.Println()
161}