diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-02 23:43:08 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-03 09:54:39 -0300 |
| commit | 5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e (patch) | |
| tree | f12f4b700fff4ff6f7e818d2a542672ba0b8d76e /sidecar | |
| parent | a9263daee9413c5eff3c1f1bebcd224442fd8685 (diff) | |
| download | notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.tar.gz notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.zip | |
rebrand to notevi: one CLI over the jj sidecar
vr and vrsite become a single binary. vrsite/ folds into a web package in
one module (0x4200.cafe/notevi); "notevi web" serves and exports exactly
what vrsite did, and "notevi read/grep/note/query" is unchanged.
The sidecar is renamed with it: notevi_log, notevi-log.jsonl, and the
description "private: notevi log". The pre-rebrand names are still
recognized, so an old repository opens and reads; it is renamed in place
on the first write, or up front with "notevi migrate DIR...".
That rename cannot be a single mv inside jj run. jj only auto-tracks a
*new* file in the run working copy below a size limit it does not take
from the command line, so writing a whole log under a name the change has
never held is silently dropped while jj reports success. ensureLogFile
creates the file empty first and lets every later byte be a modification
of a tracked file, which snapshots at any size; that also fixes the same
latent bug when importing a large legacy vr-log.jsonl.
Adds a bem-te-vi mark (favicon and nav brand) and a README.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'sidecar')
| -rw-r--r-- | sidecar/store.go | 295 | ||||
| -rw-r--r-- | sidecar/store_test.go | 134 |
2 files changed, 362 insertions, 67 deletions
diff --git a/sidecar/store.go b/sidecar/store.go index 76e626f..ce6c56f 100644 --- a/sidecar/store.go +++ b/sidecar/store.go @@ -1,4 +1,4 @@ -// Package sidecar stores vr's append-only JSONL log in a private jj change. +// Package sidecar stores notevi's append-only JSONL log in a private jj change. // // The change is an anonymous direct child of root(), so it is visible and // durable in the jj repository without becoming part of project history or the @@ -18,18 +18,34 @@ import ( ) const ( - Alias = "vr_log" - LogFile = "vr-log.jsonl" - Description = "private: vr log" + Alias = "notevi_log" + LogFile = "notevi-log.jsonl" + Description = "private: notevi log" + lockFile = "notevi.lock" ) -var ErrNoLog = errors.New("vr log has not been initialized") +// naming is the set of repo-visible names one generation of the tool gave the +// sidecar: the revset alias, the file inside the change, and the change's own +// description. +type naming struct{ alias, logFile, description string } -// Store is a repository's vr log. Open is read-only with respect to jj; the +var current = naming{Alias, LogFile, Description} + +// legacy is what the tool called itself before the notevi rebrand. Every read +// path still recognizes it so an un-migrated repository opens; Migrate renames +// it in place so nothing in the repository keeps the old name. +var legacy = naming{"vr_log", "vr-log.jsonl", "private: vr log"} + +const legacyLockFile = "vr-agent-logger.lock" + +var ErrNoLog = errors.New("notevi log has not been initialized") + +// Store is a repository's notevi log. Open is read-only with respect to jj; the // sidecar change and its config are created lazily by Append. type Store struct { - root string - lockPath string + root string + configDir string + lockPath string } // Open finds the jj workspace containing dir. @@ -43,32 +59,40 @@ func Open(dir string) (*Store, error) { if err != nil { return nil, err } + configDir := filepath.Dir(strings.TrimSpace(configPath)) return &Store{ - root: root, - lockPath: filepath.Join(filepath.Dir(strings.TrimSpace(configPath)), "vr-agent-logger.lock"), + root: root, + configDir: configDir, + lockPath: filepath.Join(configDir, lockFile), }, nil } +// Descriptions lists every change subject that has ever named a notevi sidecar, +// current first. Callers that discover sidecars by description need the older +// ones too, so an un-migrated repository is still recognized as having a log. +func Descriptions() []string { return []string{current.description, legacy.description} } + func (s *Store) Root() string { return s.root } func (s *Store) String() string { return fmt.Sprintf("jj sidecar %s:%s", Alias, LogFile) } -// Read returns the current log. Before the first sidecar write it falls back -// to a repo-root legacy log, which makes migration transparent. +// Read returns the current log. It reads a sidecar that still carries the +// pre-rebrand names without migrating it, and before the first sidecar write it +// falls back to a repo-root legacy log, which makes migration transparent. func (s *Store) Read() ([]byte, error) { var data []byte err := s.withLock(syscall.LOCK_SH, func() error { - rev, found, _, err := s.resolveExisting() + rev, n, found, _, err := s.locate() if err != nil { return err } if found { - data, err = s.readRevision(rev) + data, err = s.readRevision(rev, n.logFile) return err } - data, err = os.ReadFile(filepath.Join(s.root, LogFile)) + data, err = os.ReadFile(filepath.Join(s.root, legacy.logFile)) if errors.Is(err, os.ErrNotExist) { return ErrNoLog } @@ -77,12 +101,12 @@ func (s *Store) Read() ([]byte, error) { return data, err } -// Generation is a cheap identity for the current contents, used by vrsite to -// notice appends. A sidecar rewrite gets a new commit id. +// Generation is a cheap identity for the current contents, used by the web app +// to notice appends. A sidecar rewrite gets a new commit id. func (s *Store) Generation() (string, error) { var generation string err := s.withLock(syscall.LOCK_SH, func() error { - rev, found, _, err := s.resolveExisting() + rev, _, found, _, err := s.locate() if err != nil { return err } @@ -90,7 +114,7 @@ func (s *Store) Generation() (string, error) { generation, err = s.logValue(rev, "commit_id") return err } - info, err := os.Stat(filepath.Join(s.root, LogFile)) + info, err := os.Stat(filepath.Join(s.root, legacy.logFile)) if errors.Is(err, os.ErrNotExist) { generation = "empty" return nil @@ -104,7 +128,7 @@ func (s *Store) Generation() (string, error) { return generation, err } -// Append serializes sidecar rewrites across vr processes. makePayload runs +// Append serializes sidecar rewrites across notevi processes. makePayload runs // while holding that lock and receives the complete current log, allowing note // numbering and reply validation to be atomic with the append. func (s *Store) Append(makePayload func(current []byte) ([]byte, error)) error { @@ -113,17 +137,17 @@ func (s *Store) Append(makePayload func(current []byte) ([]byte, error)) error { if err != nil { return err } - sidecarData, err := s.readRevision(rev) + sidecarData, err := s.readRevision(rev, LogFile) if err != nil { return err } current := sidecarData if len(current) == 0 { - legacy, legacyErr := os.ReadFile(filepath.Join(s.root, LogFile)) - if legacyErr == nil { - current = legacy - } else if !errors.Is(legacyErr, os.ErrNotExist) { - return legacyErr + old, oldErr := os.ReadFile(filepath.Join(s.root, legacy.logFile)) + if oldErr == nil { + current = old + } else if !errors.Is(oldErr, os.ErrNotExist) { + return oldErr } } payload, err := makePayload(current) @@ -131,7 +155,7 @@ func (s *Store) Append(makePayload func(current []byte) ([]byte, error)) error { return err } if len(payload) == 0 { - return errors.New("refusing to append an empty vr log payload") + return errors.New("refusing to append an empty notevi log payload") } var appendData []byte @@ -145,10 +169,97 @@ func (s *Store) Append(makePayload func(current []byte) ([]byte, error)) error { if appendData[len(appendData)-1] != '\n' { appendData = append(appendData, '\n') } - return s.runAppend(appendData) + return s.runAppend(rev, appendData) }) } +// Migrate renames a pre-rebrand sidecar in place: the file inside the change, +// the change description, the repo-local revset alias, and the alias mentioned +// in git.private-commits. It reports whether anything was renamed and is a +// no-op on a repository that is already current or has no sidecar at all. +func (s *Store) Migrate() (migrated bool, err error) { + err = s.withLock(syscall.LOCK_EX, func() error { + rev, n, found, configured, err := s.locate() + if err != nil { + return err + } + if !found || n != legacy { + // Still adopt an unconfigured current sidecar, so that a repo whose + // jj config was lost (moving a workspace orphans it) is repaired. + if found && !configured { + return s.configure(rev) + } + return nil + } + if err := s.rename(rev); err != nil { + return err + } + migrated = true + return nil + }) + return migrated, err +} + +// rename performs the legacy -> current renaming for an already-located sidecar. +// The move is two runs, not one `mv`: see ensureLogFile. +func (s *Store) rename(rev string) error { + listed, err := s.jj("file", "list", "-r", rev, "--", legacy.logFile) + if err != nil { + return err + } + if strings.TrimSpace(listed) != "" { + if err := s.ensureLogFile(rev); err != nil { + return err + } + if _, err := s.jjRun(rev, "cat "+legacy.logFile+" >> "+LogFile+" && rm "+legacy.logFile); err != nil { + return err + } + } + if _, err := s.jj("describe", "-r", rev, "-m", Description); err != nil { + return err + } + if err := s.configure(rev); err != nil { + return err + } + if _, err := s.jj("config", "get", "revset-aliases."+legacy.alias); err == nil { + if _, err := s.jj("config", "unset", "--repo", "revset-aliases."+legacy.alias); err != nil { + return err + } + } + os.Remove(filepath.Join(s.configDir, legacyLockFile)) //nolint:errcheck + return nil +} + +// configure points the repo-local alias at rev and refreshes the push guard. +func (s *Store) configure(rev string) error { + if _, err := s.jj("config", "set", "--repo", "revset-aliases."+Alias, strconv.Quote(rev)); err != nil { + return err + } + return s.guard() +} + +// guard keeps git.private-commits naming the current alias, rewriting a legacy +// mention rather than leaving both names in the expression. +func (s *Store) guard() error { + private, err := s.jj("config", "get", "git.private-commits") + if err != nil { + return err + } + expr := strings.TrimSpace(private) + next := expr + if containsRevsetName(next, legacy.alias) { + next = replaceRevsetName(next, legacy.alias, Alias) + } + if !containsRevsetName(next, Alias) { + next = fmt.Sprintf("(%s) | %s", next, Alias) + } + if next == expr { + return nil + } + _, err = s.jj("config", "set", "--repo", "git.private-commits", strconv.Quote(next)) + return err +} + func (s *Store) withLock(kind int, fn func() error) error { if err := os.MkdirAll(filepath.Dir(s.lockPath), 0o755); err != nil { return err @@ -166,57 +277,64 @@ func (s *Store) withLock(kind int, fn func() error) error { } func (s *Store) ensure() (string, error) { - rev, found, configured, err := s.resolveExisting() + rev, n, found, configured, err := s.locate() if err != nil { return "", err } + if found && n == legacy { + // The first append to a pre-rebrand repository renames it, so no + // repository keeps writing under the old name once notevi touches it. + return rev, s.rename(rev) + } if !found { if _, err := s.jj("new", "--no-edit", "root()", "-m", Description); err != nil { return "", err } - ids, err := s.descriptionMatches() + ids, err := s.descriptionMatches(Description) if err != nil { return "", err } if len(ids) != 1 { return "", fmt.Errorf("created %q sidecar but found %d matching root children", Description, len(ids)) } - rev = ids[0] + rev, configured = ids[0], false } if !configured { - if _, err := s.jj("config", "set", "--repo", "revset-aliases."+Alias, strconv.Quote(rev)); err != nil { - return "", err - } + return rev, s.configure(rev) } - private, err := s.jj("config", "get", "git.private-commits") - if err != nil { - return "", err + return rev, s.guard() +} + +// locate finds this repository's sidecar under the current names, falling back +// to the pre-rebrand ones, and reports which generation named it. +func (s *Store) locate() (rev string, n naming, found, configured bool, err error) { + rev, found, configured, err = s.resolveExisting(current) + if err != nil || found { + return rev, current, found, configured, err } - if !containsRevsetName(private, Alias) { - expr := fmt.Sprintf("(%s) | %s", strings.TrimSpace(private), Alias) - if _, err := s.jj("config", "set", "--repo", "git.private-commits", strconv.Quote(expr)); err != nil { - return "", err - } + rev, found, configured, err = s.resolveExisting(legacy) + if !found { + return rev, current, false, false, err } - return rev, nil + return rev, legacy, found, configured, err } -func (s *Store) resolveExisting() (rev string, found, configured bool, err error) { - configuredValue, configErr := s.jj("config", "get", "revset-aliases."+Alias) +func (s *Store) resolveExisting(n naming) (rev string, found, configured bool, err error) { + configuredValue, configErr := s.jj("config", "get", "revset-aliases."+n.alias) if configErr == nil { - rev, err = s.logValue("exactly("+Alias+", 1)", "change_id") + rev, err = s.logValue("exactly("+n.alias+", 1)", "change_id") if err != nil { - return "", false, true, fmt.Errorf("repo-local revset alias %s is invalid: %w", Alias, err) + return "", false, true, fmt.Errorf("repo-local revset alias %s is invalid: %w", n.alias, err) } - valid := fmt.Sprintf("exactly(%s & root()+ & subject(exact:%s), 1)", Alias, strconv.Quote(Description)) + valid := fmt.Sprintf("exactly(%s & root()+ & subject(exact:%s), 1)", n.alias, strconv.Quote(n.description)) if _, err := s.logValue(valid, "change_id"); err != nil { - return "", false, true, fmt.Errorf("repo-local revset alias %s=%q does not name vr's sidecar: %w", - Alias, strings.TrimSpace(configuredValue), err) + return "", false, true, fmt.Errorf("repo-local revset alias %s=%q does not name notevi's sidecar: %w", + n.alias, strings.TrimSpace(configuredValue), err) } return rev, true, true, nil } - ids, err := s.descriptionMatches() + ids, err := s.descriptionMatches(n.description) if err != nil { return "", false, false, err } @@ -226,12 +344,12 @@ func (s *Store) resolveExisting() (rev string, found, configured bool, err error case 1: return ids[0], true, false, nil default: - return "", false, false, fmt.Errorf("found %d root children described %q; cannot choose a vr sidecar", len(ids), Description) + return "", false, false, fmt.Errorf("found %d root children described %q; cannot choose a notevi sidecar", len(ids), n.description) } } -func (s *Store) descriptionMatches() ([]string, error) { - revset := fmt.Sprintf("root()+ & subject(exact:%s)", strconv.Quote(Description)) +func (s *Store) descriptionMatches(description string) ([]string, error) { + revset := fmt.Sprintf("root()+ & subject(exact:%s)", strconv.Quote(description)) out, err := s.jj("log", "-G", "-r", revset, "-T", "change_id ++ \"\\n\"") if err != nil { return nil, err @@ -255,20 +373,44 @@ func (s *Store) logValue(revset, template string) (string, error) { return value, nil } -func (s *Store) readRevision(rev string) ([]byte, error) { - listed, err := s.jj("file", "list", "-r", rev, "--", LogFile) +func (s *Store) readRevision(rev, logFile string) ([]byte, error) { + listed, err := s.jj("file", "list", "-r", rev, "--", logFile) if err != nil { return nil, err } if strings.TrimSpace(listed) == "" { return nil, nil } - out, err := s.jj("file", "show", "-r", rev, "--", LogFile) + out, err := s.jj("file", "show", "-r", rev, "--", logFile) return []byte(out), err } -func (s *Store) runAppend(data []byte) error { - f, err := os.CreateTemp(filepath.Dir(s.lockPath), "vr-log-append-*") +// ensureLogFile creates the log file, empty, if the sidecar does not have it. +// +// This is load-bearing, not tidiness. jj only auto-tracks a *new* file in the +// run working copy while it is under snapshot.max-new-file-size, and the run +// working copy does not take that limit from the command line, so writing a +// whole log under a name the change has never held is silently dropped — the +// commit is rewritten with the file missing and jj reports success. Writing the +// file empty first is always under the limit; from then on every byte is a +// modification of a tracked file, which snapshots at any size. +func (s *Store) ensureLogFile(rev string) error { + listed, err := s.jj("file", "list", "-r", rev, "--", LogFile) + if err != nil { + return err + } + if strings.TrimSpace(listed) != "" { + return nil + } + _, err = s.jjRun(rev, ": > "+LogFile) + return err +} + +func (s *Store) runAppend(rev string, data []byte) error { + if err := s.ensureLogFile(rev); err != nil { + return err + } + f, err := os.CreateTemp(s.configDir, "notevi-log-append-*") if err != nil { return err } @@ -281,13 +423,20 @@ func (s *Store) runAppend(data []byte) error { if err := f.Close(); err != nil { return err } - _, err = s.jj("--config", "snapshot.auto-track=\"all()\"", - "--config", "snapshot.max-new-file-size=\"1GiB\"", - "run", "--clean", "--root", "-r", "exactly("+Alias+", 1)", "--", - "sh", "-c", "cat \"$1\" >> "+LogFile, "sh", tmp) + _, err = s.jjRun(rev, "cat \"$1\" >> "+LogFile, tmp) return err } +// jjRun rewrites the sidecar change by running script against a clean checkout +// of it. rev is a change id or alias; the working copy is never involved. +func (s *Store) jjRun(rev, script string, scriptArgs ...string) (string, error) { + args := []string{"--config", "snapshot.auto-track=\"all()\"", + "--config", "snapshot.max-new-file-size=\"1GiB\"", + "run", "--clean", "--root", "-r", "exactly((" + rev + "), 1)", "--", + "sh", "-c", script, "sh"} + return s.jj(append(args, scriptArgs...)...) +} + func (s *Store) jj(args ...string) (string, error) { return jjOutput(s.root, args...) } @@ -324,6 +473,26 @@ func containsRevsetName(expr, name string) bool { return false } +// replaceRevsetName swaps whole revset names, so that rewriting an expression +// mentioning vr_log cannot corrupt an unrelated name that merely contains it. +func replaceRevsetName(expr, name, replacement string) string { + var b strings.Builder + for i := 0; i < len(expr); { + if expr[i:min(i+len(name), len(expr))] == name { + before := i == 0 || !isNameByte(expr[i-1]) + after := i+len(name) == len(expr) || !isNameByte(expr[i+len(name)]) + if before && after { + b.WriteString(replacement) + i += len(name) + continue + } + } + b.WriteByte(expr[i]) + i++ + } + return b.String() +} + func isNameByte(b byte) bool { return b == '_' || b == '-' || b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b >= '0' && b <= '9' } diff --git a/sidecar/store_test.go b/sidecar/store_test.go index 85f50e1..8ce3192 100644 --- a/sidecar/store_test.go +++ b/sidecar/store_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "sync" "testing" @@ -72,19 +73,19 @@ func TestAppendCreatesPrivateUnrelatedSidecar(t *testing.T) { func TestLegacyLogIsReadThenImportedOnFirstAppend(t *testing.T) { repo := testRepo(t) - legacy := []byte("legacy\n") - if err := os.WriteFile(filepath.Join(repo, LogFile), legacy, 0o644); err != nil { + data := []byte("legacy\n") + if err := os.WriteFile(filepath.Join(repo, legacy.logFile), data, 0o644); err != nil { t.Fatal(err) } store, err := Open(repo) if err != nil { t.Fatal(err) } - if got, err := store.Read(); err != nil || string(got) != string(legacy) { + if got, err := store.Read(); err != nil || string(got) != string(data) { t.Fatalf("pre-migration read = %q, %v", got, err) } if err := store.Append(func(current []byte) ([]byte, error) { - if string(current) != string(legacy) { + if string(current) != string(data) { t.Fatalf("migration callback saw %q", current) } return []byte("sidecar\n"), nil @@ -96,6 +97,131 @@ func TestLegacyLogIsReadThenImportedOnFirstAppend(t *testing.T) { } } +// A repository written by the pre-rebrand tool must keep working, and must stop +// carrying the old name once notevi has touched it. +func TestPreRebrandSidecarIsRenamedInPlace(t *testing.T) { + repo := testRepo(t) + rev := writeLegacySidecar(t, repo, "old\n") + + store, err := Open(repo) + if err != nil { + t.Fatal(err) + } + if got, err := store.Read(); err != nil || string(got) != "old\n" { + t.Fatalf("read of an un-migrated sidecar = %q, %v", got, err) + } + + migrated, err := store.Migrate() + if err != nil { + t.Fatal(err) + } + if !migrated { + t.Fatal("Migrate reported nothing to do for a legacy sidecar") + } + + if got := strings.TrimSpace(testJJ(t, repo, "config", "get", "revset-aliases."+Alias)); got != rev { + t.Fatalf("alias %s = %q, want the original change %q", Alias, got, rev) + } + if out, err := exec.Command("jj", "--ignore-working-copy", "-R", repo, + "config", "get", "revset-aliases."+legacy.alias).CombinedOutput(); err == nil { + t.Fatalf("legacy alias %s survived: %s", legacy.alias, out) + } + private := testJJ(t, repo, "config", "get", "git.private-commits") + if !containsRevsetName(private, Alias) || containsRevsetName(private, legacy.alias) { + t.Fatalf("private commits = %q, want %s and not %s", private, Alias, legacy.alias) + } + files := testJJ(t, repo, "file", "list", "-r", "exactly("+Alias+", 1)") + if !strings.Contains(files, LogFile) || strings.Contains(files, legacy.logFile) { + t.Fatalf("sidecar files = %q, want only %s", files, LogFile) + } + subject := strings.TrimSpace(testJJ(t, repo, "log", "-G", + "-r", "exactly("+Alias+", 1)", "-T", "description")) + if subject != Description { + t.Fatalf("sidecar description = %q, want %q", subject, Description) + } + + // The trace survived the rename and the store keeps appending to it. + if err := store.Append(func(current []byte) ([]byte, error) { + if string(current) != "old\n" { + t.Fatalf("append after migration saw %q", current) + } + return []byte("new\n"), nil + }); err != nil { + t.Fatal(err) + } + if got, err := store.Read(); err != nil || string(got) != "old\nnew\n" { + t.Fatalf("migrated log = %q, %v", got, err) + } + if again, err := store.Migrate(); err != nil || again { + t.Fatalf("second Migrate = %v, %v; want no-op", again, err) + } +} + +// A real trace is hundreds of KB. jj silently declines to auto-track a *new* +// file in the run working copy past a size limit it does not take from the +// command line, so a rename that writes the whole log under a name the change +// has never held loses it — with jj reporting success. Small fixtures do not +// catch that; this one does. +func TestLargeLogSurvivesRenameAndAppend(t *testing.T) { + repo := testRepo(t) + var seed strings.Builder + for i := range 5000 { + fmt.Fprintf(&seed, `{"time":"2026-07-26T10:00:00Z","op":"read","file":"src/%d.rs","start":1,"end":400}`+"\n", i) + } + if seed.Len() < 256<<10 { + t.Fatalf("fixture is only %d bytes; too small to exercise the limit", seed.Len()) + } + writeLegacySidecar(t, repo, seed.String()) + + store, err := Open(repo) + if err != nil { + t.Fatal(err) + } + if _, err := store.Migrate(); err != nil { + t.Fatal(err) + } + got, err := store.Read() + if err != nil { + t.Fatal(err) + } + if string(got) != seed.String() { + t.Fatalf("renamed log is %d bytes, want %d", len(got), seed.Len()) + } + if err := store.Append(func([]byte) ([]byte, error) { return []byte(`{"op":"note"}`), nil }); err != nil { + t.Fatal(err) + } + if got, err = store.Read(); err != nil || len(got) <= seed.Len() { + t.Fatalf("append after a large rename left %d bytes, %v", len(got), err) + } +} + +// writeLegacySidecar builds the sidecar exactly as the pre-rebrand tool did and +// returns its change id. +func writeLegacySidecar(t *testing.T, repo, content string) string { + t.Helper() + testJJ(t, repo, "new", "--no-edit", "root()", "-m", legacy.description) + rev := strings.TrimSpace(testJJ(t, repo, "log", "-G", "-T", "change_id", + "-r", fmt.Sprintf("root()+ & subject(exact:%q)", legacy.description))) + tmp := filepath.Join(t.TempDir(), "seed") + if err := os.WriteFile(tmp, []byte(content), 0o644); err != nil { + t.Fatal(err) + } + // Touch first, then fill: the same two steps the store uses, so a large + // fixture really lands in the sidecar instead of quietly seeding nothing. + testJJ(t, repo, "--config", `snapshot.auto-track="all()"`, + "run", "--clean", "--root", "-r", "exactly("+rev+", 1)", "--", + "sh", "-c", ": > "+legacy.logFile) + testJJ(t, repo, "--config", `snapshot.auto-track="all()"`, + "run", "--clean", "--root", "-r", "exactly("+rev+", 1)", "--", + "sh", "-c", "cat \"$1\" >> "+legacy.logFile, "sh", tmp) + if got := testJJ(t, repo, "file", "show", "-r", "exactly("+rev+", 1)", "--", legacy.logFile); got != content { + t.Fatalf("legacy fixture is %d bytes in the sidecar, want %d", len(got), len(content)) + } + testJJ(t, repo, "config", "set", "--repo", "revset-aliases."+legacy.alias, strconv.Quote(rev)) + testJJ(t, repo, "config", "set", "--repo", "git.private-commits", strconv.Quote("(none()) | "+legacy.alias)) + return rev +} + func TestConcurrentAppendsAreSerialized(t *testing.T) { repo := testRepo(t) store, err := Open(repo) |
