tangled
alpha
login
or
join now
atscan.net
/
plcbundle-go
1
fork
atom
[DEPRECATED] Go implementation of plcbundle
1
fork
atom
overview
issues
pulls
pipelines
rollback cmd
tree.fail
4 months ago
e464bfb0
e9687d50
+216
-1
2 changed files
expand all
collapse all
unified
split
cmd
plcbundle
commands
rollback.go
main.go
+212
cmd/plcbundle/commands/rollback.go
···
1
1
+
package commands
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"flag"
6
6
+
"fmt"
7
7
+
"os"
8
8
+
"path/filepath"
9
9
+
"strings"
10
10
+
11
11
+
"tangled.org/atscan.net/plcbundle/internal/bundleindex"
12
12
+
)
13
13
+
14
14
+
// RollbackCommand handles the rollback subcommand
15
15
+
func RollbackCommand(args []string) error {
16
16
+
fs := flag.NewFlagSet("rollback", flag.ExitOnError)
17
17
+
toBundle := fs.Int("to", 0, "rollback TO this bundle (keeps it, removes everything after)")
18
18
+
last := fs.Int("last", 0, "rollback last N bundles")
19
19
+
force := fs.Bool("force", false, "skip confirmation")
20
20
+
rebuildDIDIndex := fs.Bool("rebuild-did-index", false, "rebuild DID index after rollback")
21
21
+
22
22
+
if err := fs.Parse(args); err != nil {
23
23
+
return err
24
24
+
}
25
25
+
26
26
+
if *toBundle == 0 && *last == 0 {
27
27
+
return fmt.Errorf("usage: plcbundle rollback [options]\n\n" +
28
28
+
"Options:\n" +
29
29
+
" --to <N> Rollback TO bundle N (keeps N, removes after)\n" +
30
30
+
" --last <N> Rollback last N bundles\n" +
31
31
+
" --force Skip confirmation\n" +
32
32
+
" --rebuild-did-index Rebuild DID index after rollback\n\n" +
33
33
+
"Examples:\n" +
34
34
+
" plcbundle rollback --to 100 # Keep bundles 1-100\n" +
35
35
+
" plcbundle rollback --last 5 # Remove last 5 bundles\n" +
36
36
+
" plcbundle rollback --to 50 --force --rebuild-did-index")
37
37
+
}
38
38
+
39
39
+
if *toBundle > 0 && *last > 0 {
40
40
+
return fmt.Errorf("cannot use both --to and --last together")
41
41
+
}
42
42
+
43
43
+
mgr, dir, err := getManager("")
44
44
+
if err != nil {
45
45
+
return err
46
46
+
}
47
47
+
defer mgr.Close()
48
48
+
49
49
+
index := mgr.GetIndex()
50
50
+
bundles := index.GetBundles()
51
51
+
52
52
+
if len(bundles) == 0 {
53
53
+
fmt.Println("No bundles to rollback")
54
54
+
return nil
55
55
+
}
56
56
+
57
57
+
// Determine target bundle
58
58
+
var targetBundle int
59
59
+
if *toBundle > 0 {
60
60
+
targetBundle = *toBundle
61
61
+
} else {
62
62
+
targetBundle = bundles[len(bundles)-1].BundleNumber - *last
63
63
+
}
64
64
+
65
65
+
if targetBundle < 1 {
66
66
+
return fmt.Errorf("invalid rollback: would result in no bundles (target: %d)", targetBundle)
67
67
+
}
68
68
+
69
69
+
// Find bundles to delete (everything AFTER target)
70
70
+
var toDelete []*bundleindex.BundleMetadata
71
71
+
var toKeep []*bundleindex.BundleMetadata
72
72
+
73
73
+
for _, meta := range bundles {
74
74
+
if meta.BundleNumber > targetBundle {
75
75
+
toDelete = append(toDelete, meta)
76
76
+
} else {
77
77
+
toKeep = append(toKeep, meta)
78
78
+
}
79
79
+
}
80
80
+
81
81
+
if len(toDelete) == 0 {
82
82
+
fmt.Printf("Nothing to rollback (already at bundle %d)\n", targetBundle)
83
83
+
return nil
84
84
+
}
85
85
+
86
86
+
// Display rollback plan
87
87
+
fmt.Printf("╔════════════════════════════════════════════════════════╗\n")
88
88
+
fmt.Printf("║ ROLLBACK PLAN ║\n")
89
89
+
fmt.Printf("╚════════════════════════════════════════════════════════╝\n\n")
90
90
+
fmt.Printf(" Directory: %s\n", dir)
91
91
+
fmt.Printf(" Current state: %d bundles (%06d - %06d)\n",
92
92
+
len(bundles), bundles[0].BundleNumber, bundles[len(bundles)-1].BundleNumber)
93
93
+
fmt.Printf(" Target: bundle %06d\n", targetBundle)
94
94
+
fmt.Printf(" Will DELETE: %d bundle(s)\n\n", len(toDelete))
95
95
+
96
96
+
fmt.Printf("Bundles to delete:\n")
97
97
+
for i, meta := range toDelete {
98
98
+
if i < 10 || i >= len(toDelete)-5 {
99
99
+
fmt.Printf(" %06d (%s, %s)\n",
100
100
+
meta.BundleNumber,
101
101
+
meta.CreatedAt.Format("2006-01-02 15:04"),
102
102
+
formatBytes(meta.CompressedSize))
103
103
+
} else if i == 10 {
104
104
+
fmt.Printf(" ... (%d more bundles)\n", len(toDelete)-15)
105
105
+
}
106
106
+
}
107
107
+
fmt.Printf("\n")
108
108
+
109
109
+
// Calculate what will be deleted
110
110
+
var deletedOps int
111
111
+
var deletedSize int64
112
112
+
for _, meta := range toDelete {
113
113
+
deletedOps += meta.OperationCount
114
114
+
deletedSize += meta.CompressedSize
115
115
+
}
116
116
+
117
117
+
fmt.Printf("⚠️ WARNING: This will delete:\n")
118
118
+
fmt.Printf(" • %s operations\n", formatNumber(deletedOps))
119
119
+
fmt.Printf(" • %s of data\n", formatBytes(deletedSize))
120
120
+
fmt.Printf(" • Mempool will be cleared\n")
121
121
+
if didStats := mgr.GetDIDIndexStats(); didStats["exists"].(bool) {
122
122
+
fmt.Printf(" • DID index will be invalidated\n")
123
123
+
}
124
124
+
fmt.Printf("\n")
125
125
+
126
126
+
if !*force {
127
127
+
fmt.Printf("Type 'rollback' to confirm: ")
128
128
+
var response string
129
129
+
fmt.Scanln(&response)
130
130
+
if strings.TrimSpace(response) != "rollback" {
131
131
+
fmt.Println("Cancelled")
132
132
+
return nil
133
133
+
}
134
134
+
}
135
135
+
136
136
+
fmt.Printf("\n")
137
137
+
138
138
+
// Step 1: Delete bundle files
139
139
+
fmt.Printf("[1/4] Deleting bundle files...\n")
140
140
+
deletedCount := 0
141
141
+
for _, meta := range toDelete {
142
142
+
bundlePath := filepath.Join(dir, fmt.Sprintf("%06d.jsonl.zst", meta.BundleNumber))
143
143
+
if err := os.Remove(bundlePath); err != nil {
144
144
+
if !os.IsNotExist(err) {
145
145
+
fmt.Printf(" ⚠️ Failed to delete %06d: %v\n", meta.BundleNumber, err)
146
146
+
continue
147
147
+
}
148
148
+
}
149
149
+
deletedCount++
150
150
+
}
151
151
+
fmt.Printf(" ✓ Deleted %d bundle file(s)\n\n", deletedCount)
152
152
+
153
153
+
// Step 2: Clear mempool
154
154
+
fmt.Printf("[2/4] Clearing mempool...\n")
155
155
+
if err := mgr.ClearMempool(); err != nil {
156
156
+
fmt.Printf(" ⚠️ Warning: failed to clear mempool: %v\n", err)
157
157
+
} else {
158
158
+
fmt.Printf(" ✓ Mempool cleared\n\n")
159
159
+
}
160
160
+
161
161
+
// Step 3: Update bundle index
162
162
+
fmt.Printf("[3/4] Updating bundle index...\n")
163
163
+
index.Rebuild(toKeep)
164
164
+
if err := mgr.SaveIndex(); err != nil {
165
165
+
return fmt.Errorf("failed to save index: %w", err)
166
166
+
}
167
167
+
fmt.Printf(" ✓ Index updated (%d bundles)\n\n", len(toKeep))
168
168
+
169
169
+
// Step 4: Handle DID index
170
170
+
fmt.Printf("[4/4] DID index...\n")
171
171
+
didStats := mgr.GetDIDIndexStats()
172
172
+
if didStats["exists"].(bool) {
173
173
+
if *rebuildDIDIndex {
174
174
+
fmt.Printf(" Rebuilding DID index...\n")
175
175
+
ctx := context.Background()
176
176
+
if err := mgr.BuildDIDIndex(ctx, func(current, total int) {
177
177
+
if current%100 == 0 || current == total {
178
178
+
fmt.Printf(" Progress: %d/%d (%.1f%%) \r",
179
179
+
current, total, float64(current)/float64(total)*100)
180
180
+
}
181
181
+
}); err != nil {
182
182
+
return fmt.Errorf("failed to rebuild DID index: %w", err)
183
183
+
}
184
184
+
fmt.Printf("\n ✓ DID index rebuilt\n")
185
185
+
} else {
186
186
+
// Just mark as needing rebuild
187
187
+
fmt.Printf(" ⚠️ DID index is out of date\n")
188
188
+
fmt.Printf(" Run: plcbundle index build\n")
189
189
+
}
190
190
+
} else {
191
191
+
fmt.Printf(" (no DID index)\n")
192
192
+
}
193
193
+
194
194
+
fmt.Printf("\n")
195
195
+
fmt.Printf("╔════════════════════════════════════════════════════════╗\n")
196
196
+
fmt.Printf("║ ROLLBACK COMPLETE ║\n")
197
197
+
fmt.Printf("╚════════════════════════════════════════════════════════╝\n\n")
198
198
+
199
199
+
if len(toKeep) > 0 {
200
200
+
lastBundle := toKeep[len(toKeep)-1]
201
201
+
fmt.Printf(" New state:\n")
202
202
+
fmt.Printf(" Bundles: %d (%06d - %06d)\n",
203
203
+
len(toKeep), toKeep[0].BundleNumber, lastBundle.BundleNumber)
204
204
+
fmt.Printf(" Chain head: %s\n", lastBundle.Hash[:16]+"...")
205
205
+
fmt.Printf(" Last update: %s\n", lastBundle.EndTime.Format("2006-01-02 15:04:05"))
206
206
+
} else {
207
207
+
fmt.Printf(" State: empty (all bundles removed)\n")
208
208
+
}
209
209
+
210
210
+
fmt.Printf("\n")
211
211
+
return nil
212
212
+
}
+4
-1
cmd/plcbundle/main.go
···
24
24
err = commands.FetchCommand(os.Args[2:])
25
25
case "clone":
26
26
err = commands.CloneCommand(os.Args[2:])
27
27
+
case "rollback":
28
28
+
err = commands.RollbackCommand(os.Args[2:])
27
29
case "rebuild":
28
30
err = commands.RebuildCommand(os.Args[2:])
29
31
case "verify":
···
67
69
plcbundle <command> [options]
68
70
69
71
Commands:
70
70
-
fetch Fetch next bundle from PLC directory
72
72
+
fetch Fetch next bundle(s) from PLC directory
71
73
clone Clone bundles from remote HTTP endpoint
74
74
+
rollback Rollback to previous bundle state
72
75
rebuild Rebuild index from existing bundle files
73
76
verify Verify bundle integrity
74
77
info Show bundle information