Fast implementation of Git in pure Go codeberg.org/lindenii/furgit
git go
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

object/store/packed: Add quarantine

Runxi Yu 1d2912f9 61e4da66

+404
+19
object/store/packed/quarantine.go
··· 1 + package packed 2 + 3 + import ( 4 + "os" 5 + 6 + objectstore "codeberg.org/lindenii/furgit/object/store" 7 + ) 8 + 9 + var _ objectstore.PackQuarantiner = (*Store)(nil) 10 + 11 + type packQuarantine struct { 12 + *Store 13 + 14 + parent *Store 15 + tempName string 16 + tempRoot *os.Root 17 + } 18 + 19 + var _ objectstore.PackQuarantine = (*packQuarantine)(nil)
+63
object/store/packed/quarantine_begin.go
··· 1 + package packed 2 + 3 + import ( 4 + "crypto/rand" 5 + "errors" 6 + "fmt" 7 + "io/fs" 8 + "os" 9 + 10 + objectstore "codeberg.org/lindenii/furgit/object/store" 11 + ) 12 + 13 + // BeginPackQuarantine creates one quarantined packed store rooted privately 14 + // beneath the destination pack root. 15 + // 16 + // Labels: Deps-Borrowed, Life-Parent, Close-No. 17 + func (store *Store) BeginPackQuarantine(_ objectstore.PackQuarantineOptions) (objectstore.PackQuarantine, error) { 18 + tempName, tempRoot, err := createPackQuarantineRoot(store.root) 19 + if err != nil { 20 + return nil, err 21 + } 22 + 23 + quarantineStore, err := New(tempRoot, store.algo, store.opts) 24 + if err != nil { 25 + _ = tempRoot.Close() 26 + _ = store.root.RemoveAll(tempName) 27 + 28 + return nil, err 29 + } 30 + 31 + return &packQuarantine{ 32 + Store: quarantineStore, 33 + parent: store, 34 + tempName: tempName, 35 + tempRoot: tempRoot, 36 + }, nil 37 + } 38 + 39 + func createPackQuarantineRoot(parent *os.Root) (string, *os.Root, error) { 40 + for range 32 { 41 + name := "tmp_packq_" + rand.Text() 42 + 43 + err := parent.Mkdir(name, 0o700) 44 + if err == nil { 45 + root, err := parent.OpenRoot(name) 46 + if err == nil { 47 + return name, root, nil 48 + } 49 + 50 + _ = parent.RemoveAll(name) 51 + 52 + return "", nil, err 53 + } 54 + 55 + if errors.Is(err, fs.ErrExist) { 56 + continue 57 + } 58 + 59 + return "", nil, err 60 + } 61 + 62 + return "", nil, fmt.Errorf("packed: unable to create quarantine directory") 63 + }
+18
object/store/packed/quarantine_discard.go
··· 1 + package packed 2 + 3 + // Discard removes the quarantine and invalidates the receiver. 4 + func (quarantine *packQuarantine) Discard() error { 5 + closeErr := quarantine.Close() 6 + tempRootErr := quarantine.tempRoot.Close() 7 + removeErr := quarantine.parent.root.RemoveAll(quarantine.tempName) 8 + 9 + if closeErr != nil { 10 + return closeErr 11 + } 12 + 13 + if tempRootErr != nil { 14 + return tempRootErr 15 + } 16 + 17 + return removeErr 18 + }
+89
object/store/packed/quarantine_promote.go
··· 1 + package packed 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "io/fs" 7 + "os" 8 + "slices" 9 + "strings" 10 + ) 11 + 12 + // Promote publishes all finalized pack artifacts in the quarantine into the 13 + // parent packed store and invalidates the receiver. 14 + func (quarantine *packQuarantine) Promote() error { 15 + closeErr := quarantine.Close() 16 + promoteErr := promotePackQuarantine(quarantine.parent.root, quarantine.tempName, quarantine.tempRoot) 17 + tempRootErr := quarantine.tempRoot.Close() 18 + removeErr := quarantine.parent.root.RemoveAll(quarantine.tempName) 19 + 20 + if closeErr != nil { 21 + return closeErr 22 + } 23 + 24 + if tempRootErr != nil { 25 + return tempRootErr 26 + } 27 + 28 + if promoteErr != nil { 29 + return promoteErr 30 + } 31 + 32 + return removeErr 33 + } 34 + 35 + func promotePackQuarantine(parent *os.Root, tempName string, tempRoot *os.Root) error { 36 + entries, err := fs.ReadDir(tempRoot.FS(), ".") 37 + if err != nil && !errors.Is(err, fs.ErrNotExist) { 38 + return err 39 + } 40 + 41 + slices.SortFunc(entries, func(left, right fs.DirEntry) int { 42 + return packPromotionPriority(left.Name()) - packPromotionPriority(right.Name()) 43 + }) 44 + 45 + for _, entry := range entries { 46 + if entry.IsDir() { 47 + return fmt.Errorf("packed: quarantine contains unexpected directory %q", entry.Name()) 48 + } 49 + 50 + err := promotePackQuarantineFile(parent, tempName, entry.Name()) 51 + if err != nil { 52 + return err 53 + } 54 + } 55 + 56 + return nil 57 + } 58 + 59 + func promotePackQuarantineFile(parent *os.Root, tempName, name string) error { 60 + src := tempName + "/" + name 61 + 62 + err := parent.Link(src, name) 63 + if err == nil { 64 + _ = parent.Remove(src) 65 + 66 + return nil 67 + } 68 + 69 + if errors.Is(err, fs.ErrExist) { 70 + _ = parent.Remove(src) 71 + 72 + return nil 73 + } 74 + 75 + return fmt.Errorf("packed: promote quarantine %q -> %q: %w", src, name, err) 76 + } 77 + 78 + func packPromotionPriority(name string) int { 79 + switch { 80 + case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".pack"): 81 + return 1 82 + case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".rev"): 83 + return 2 84 + case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".idx"): 85 + return 3 86 + default: 87 + return 0 88 + } 89 + }
+215
object/store/packed/quarantine_test.go
··· 1 + package packed_test 2 + 3 + import ( 4 + "bytes" 5 + "os" 6 + "path/filepath" 7 + "strings" 8 + "testing" 9 + 10 + "codeberg.org/lindenii/furgit/internal/testgit" 11 + objectid "codeberg.org/lindenii/furgit/object/id" 12 + objectstore "codeberg.org/lindenii/furgit/object/store" 13 + "codeberg.org/lindenii/furgit/object/store/packed" 14 + objecttype "codeberg.org/lindenii/furgit/object/type" 15 + ) 16 + 17 + func fixturePath(t *testing.T, algo objectid.Algorithm, name string) string { 18 + t.Helper() 19 + 20 + return filepath.Join("internal", "ingest", "testdata", "fixtures", algo.String(), name) 21 + } 22 + 23 + func fixtureBytes(t *testing.T, algo objectid.Algorithm, name string) []byte { 24 + t.Helper() 25 + 26 + path := fixturePath(t, algo, name) 27 + dir := filepath.Dir(path) 28 + base := filepath.Base(path) 29 + 30 + root, err := os.OpenRoot(dir) 31 + if err != nil { 32 + t.Fatalf("open fixture root %q: %v", dir, err) 33 + } 34 + 35 + defer func() { 36 + err := root.Close() 37 + if err != nil { 38 + t.Fatalf("close fixture root %q: %v", dir, err) 39 + } 40 + }() 41 + 42 + data, err := root.ReadFile(base) 43 + if err != nil { 44 + t.Fatalf("read fixture %q: %v", base, err) 45 + } 46 + 47 + return data 48 + } 49 + 50 + func fixtureMetadata(t *testing.T, algo objectid.Algorithm) map[string]string { 51 + t.Helper() 52 + 53 + data := fixtureBytes(t, algo, "METADATA.txt") 54 + out := make(map[string]string) 55 + 56 + for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") { 57 + line = strings.TrimSpace(line) 58 + if line == "" { 59 + continue 60 + } 61 + 62 + key, value, ok := strings.Cut(line, "=") 63 + if !ok { 64 + t.Fatalf("invalid fixture metadata line %q", line) 65 + } 66 + 67 + out[strings.TrimSpace(key)] = strings.TrimSpace(value) 68 + } 69 + 70 + return out 71 + } 72 + 73 + func fixtureOID(t *testing.T, algo objectid.Algorithm, key string) objectid.ObjectID { 74 + t.Helper() 75 + 76 + meta := fixtureMetadata(t, algo) 77 + 78 + hex, ok := meta[key] 79 + if !ok { 80 + t.Fatalf("missing fixture metadata key %q", key) 81 + } 82 + 83 + id, err := objectid.ParseHex(algo, hex) 84 + if err != nil { 85 + t.Fatalf("parse fixture metadata oid %q: %v", hex, err) 86 + } 87 + 88 + return id 89 + } 90 + 91 + func TestPackQuarantinePromotePublishesWrittenObjects(t *testing.T) { 92 + t.Parallel() 93 + 94 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 95 + head := fixtureOID(t, algo, "head") 96 + packBytes := fixtureBytes(t, algo, "nonthin.pack") 97 + 98 + repo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 99 + packRoot := repo.OpenPackRoot(t) 100 + 101 + store, err := packed.New(packRoot, algo, packed.Options{WriteRev: true}) 102 + if err != nil { 103 + t.Fatalf("packed.New: %v", err) 104 + } 105 + 106 + defer func() { 107 + err := store.Close() 108 + if err != nil { 109 + t.Fatalf("store.Close: %v", err) 110 + } 111 + }() 112 + 113 + quarantiner, ok := any(store).(objectstore.PackQuarantiner) 114 + if !ok { 115 + t.Fatal("packed store does not implement PackQuarantiner") 116 + } 117 + 118 + quarantine, err := quarantiner.BeginPackQuarantine(objectstore.PackQuarantineOptions{}) 119 + if err != nil { 120 + t.Fatalf("BeginPackQuarantine: %v", err) 121 + } 122 + 123 + err = quarantine.WritePack(bytes.NewReader(packBytes), objectstore.PackWriteOptions{RequireTrailingEOF: true}) 124 + if err != nil { 125 + t.Fatalf("quarantine.WritePack: %v", err) 126 + } 127 + 128 + ty, _, err := quarantine.ReadHeader(head) 129 + if err != nil { 130 + t.Fatalf("quarantine.ReadHeader: %v", err) 131 + } 132 + 133 + if ty != objecttype.TypeCommit { 134 + t.Fatalf("quarantine.ReadHeader type = %v, want commit", ty) 135 + } 136 + 137 + _, _, err = store.ReadHeader(head) 138 + if err == nil { 139 + t.Fatal("store.ReadHeader unexpectedly saw quarantined object before promote") 140 + } 141 + 142 + err = quarantine.Promote() 143 + if err != nil { 144 + t.Fatalf("quarantine.Promote: %v", err) 145 + } 146 + 147 + err = store.Refresh() 148 + if err != nil { 149 + t.Fatalf("store.Refresh: %v", err) 150 + } 151 + 152 + ty, _, err = store.ReadHeader(head) 153 + if err != nil { 154 + t.Fatalf("store.ReadHeader after promote: %v", err) 155 + } 156 + 157 + if ty != objecttype.TypeCommit { 158 + t.Fatalf("store.ReadHeader type = %v, want commit", ty) 159 + } 160 + }) 161 + } 162 + 163 + func TestPackQuarantineDiscardDropsWrittenObjects(t *testing.T) { 164 + t.Parallel() 165 + 166 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 167 + head := fixtureOID(t, algo, "head") 168 + packBytes := fixtureBytes(t, algo, "nonthin.pack") 169 + 170 + repo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 171 + packRoot := repo.OpenPackRoot(t) 172 + 173 + store, err := packed.New(packRoot, algo, packed.Options{WriteRev: true}) 174 + if err != nil { 175 + t.Fatalf("packed.New: %v", err) 176 + } 177 + 178 + defer func() { 179 + err := store.Close() 180 + if err != nil { 181 + t.Fatalf("store.Close: %v", err) 182 + } 183 + }() 184 + 185 + quarantiner, ok := any(store).(objectstore.PackQuarantiner) 186 + if !ok { 187 + t.Fatalf("expected objectstore.PackQuarantiner") 188 + } 189 + 190 + quarantine, err := quarantiner.BeginPackQuarantine(objectstore.PackQuarantineOptions{}) 191 + if err != nil { 192 + t.Fatalf("BeginPackQuarantine: %v", err) 193 + } 194 + 195 + err = quarantine.WritePack(bytes.NewReader(packBytes), objectstore.PackWriteOptions{RequireTrailingEOF: true}) 196 + if err != nil { 197 + t.Fatalf("quarantine.WritePack: %v", err) 198 + } 199 + 200 + err = quarantine.Discard() 201 + if err != nil { 202 + t.Fatalf("quarantine.Discard: %v", err) 203 + } 204 + 205 + err = store.Refresh() 206 + if err != nil { 207 + t.Fatalf("store.Refresh: %v", err) 208 + } 209 + 210 + _, _, err = store.ReadHeader(head) 211 + if err == nil { 212 + t.Fatal("store.ReadHeader unexpectedly saw discarded object") 213 + } 214 + }) 215 + }