From 435be2b693aee89ed34a2d1e7291b3b141b19717 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Mon, 6 Aug 2018 20:31:20 +0200 Subject: bug: add a Lamport logical clock to be able to sort bugs by creation time and edit time without having to rely on a timestamp --- repository/git.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++-- repository/mock_repo.go | 46 +++++++++++++++++++---- repository/repo.go | 12 ++++++ 3 files changed, 144 insertions(+), 11 deletions(-) (limited to 'repository') diff --git a/repository/git.go b/repository/git.go index aa0e72b7..5748d52a 100644 --- a/repository/git.go +++ b/repository/git.go @@ -3,18 +3,27 @@ package repository import ( "bytes" + "errors" "fmt" "io" "os" "os/exec" + "path" "strings" "github.com/MichaelMure/git-bug/util" ) +const createClockFile = "/.git/git-bug/create-clock" +const editClockFile = "/.git/git-bug/edit-clock" + +var ErrNotARepo = errors.New("not a git repository") + // GitRepo represents an instance of a (local) git repository. type GitRepo struct { - Path string + Path string + createClock *util.PersistedLamport + editClock *util.PersistedLamport } // Run the given git command with the given I/O reader/writers, returning an error if it fails. @@ -62,35 +71,62 @@ func (repo *GitRepo) runGitCommandInline(args ...string) error { // NewGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. -func NewGitRepo(path string) (*GitRepo, error) { +func NewGitRepo(path string, witnesser func(repo *GitRepo) error) (*GitRepo, error) { repo := &GitRepo{Path: path} + + // Check the repo and retrieve the root path stdout, err := repo.runGitCommand("rev-parse", "--show-toplevel") if err != nil { - return nil, err + return nil, ErrNotARepo } // Fix the path to be sure we are at the root repo.Path = stdout + err = repo.LoadClocks() + + if err != nil { + // No clock yet, trying to initialize them + repo.createClocks() + + err = witnesser(repo) + if err != nil { + return nil, err + } + + err = repo.WriteClocks() + if err != nil { + return nil, err + } + + return repo, nil + } + return repo, nil } func InitGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{Path: path} + repo.createClocks() + _, err := repo.runGitCommand("init", path) if err != nil { return nil, err } + return repo, nil } func InitBareGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{Path: path} + repo.createClocks() + _, err := repo.runGitCommand("init", "--bare", path) if err != nil { return nil, err } + return repo, nil } @@ -308,8 +344,63 @@ func (repo *GitRepo) GetTreeHash(commit util.Hash) (util.Hash, error) { } // Add a new remote to the repository +// Not in the interface because it's only used for testing func (repo *GitRepo) AddRemote(name string, url string) error { _, err := repo.runGitCommand("remote", "add", name, url) return err } + +func (repo *GitRepo) createClocks() { + createPath := path.Join(repo.Path, createClockFile) + repo.createClock = util.NewPersistedLamport(createPath) + + editPath := path.Join(repo.Path, editClockFile) + repo.editClock = util.NewPersistedLamport(editPath) +} + +func (repo *GitRepo) LoadClocks() error { + createClock, err := util.LoadPersistedLamport(repo.GetPath() + createClockFile) + if err != nil { + return err + } + + editClock, err := util.LoadPersistedLamport(repo.GetPath() + editClockFile) + if err != nil { + return err + } + + repo.createClock = createClock + repo.editClock = editClock + return nil +} + +func (repo *GitRepo) WriteClocks() error { + err := repo.createClock.Write() + if err != nil { + return err + } + + err = repo.editClock.Write() + if err != nil { + return err + } + + return nil +} + +func (repo *GitRepo) CreateTimeIncrement() (util.LamportTime, error) { + return repo.createClock.Increment() +} + +func (repo *GitRepo) EditTimeIncrement() (util.LamportTime, error) { + return repo.editClock.Increment() +} + +func (repo *GitRepo) CreateWitness(time util.LamportTime) error { + return repo.createClock.Witness(time) +} + +func (repo *GitRepo) EditWitness(time util.LamportTime) error { + return repo.editClock.Witness(time) +} diff --git a/repository/mock_repo.go b/repository/mock_repo.go index f8653c64..eb48f85f 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -10,10 +10,12 @@ import ( // mockRepoForTest defines an instance of Repo that can be used for testing. type mockRepoForTest struct { - blobs map[util.Hash][]byte - trees map[util.Hash]string - commits map[util.Hash]commit - refs map[string]util.Hash + blobs map[util.Hash][]byte + trees map[util.Hash]string + commits map[util.Hash]commit + refs map[string]util.Hash + createClock util.LamportClock + editClock util.LamportClock } type commit struct { @@ -23,10 +25,12 @@ type commit struct { func NewMockRepoForTest() Repo { return &mockRepoForTest{ - blobs: make(map[util.Hash][]byte), - trees: make(map[util.Hash]string), - commits: make(map[util.Hash]commit), - refs: make(map[string]util.Hash), + blobs: make(map[util.Hash][]byte), + trees: make(map[util.Hash]string), + commits: make(map[util.Hash]commit), + refs: make(map[string]util.Hash), + createClock: util.NewLamportClock(), + editClock: util.NewLamportClock(), } } @@ -200,3 +204,29 @@ func (r *mockRepoForTest) FindCommonAncestor(hash1 util.Hash, hash2 util.Hash) ( func (r *mockRepoForTest) GetTreeHash(commit util.Hash) (util.Hash, error) { panic("implement me") } + +func (r *mockRepoForTest) LoadClocks() error { + return nil +} + +func (r *mockRepoForTest) WriteClocks() error { + return nil +} + +func (r *mockRepoForTest) CreateTimeIncrement() (util.LamportTime, error) { + return r.createClock.Increment(), nil +} + +func (r *mockRepoForTest) EditTimeIncrement() (util.LamportTime, error) { + return r.editClock.Increment(), nil +} + +func (r *mockRepoForTest) CreateWitness(time util.LamportTime) error { + r.createClock.Witness(time) + return nil +} + +func (r *mockRepoForTest) EditWitness(time util.LamportTime) error { + r.editClock.Witness(time) + return nil +} diff --git a/repository/repo.go b/repository/repo.go index 38d8a6cb..057223ad 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -69,6 +69,18 @@ type Repo interface { // Return the git tree hash referenced in a commit GetTreeHash(commit util.Hash) (util.Hash, error) + + LoadClocks() error + + WriteClocks() error + + CreateTimeIncrement() (util.LamportTime, error) + + EditTimeIncrement() (util.LamportTime, error) + + CreateWitness(time util.LamportTime) error + + EditWitness(time util.LamportTime) error } func prepareTreeEntries(entries []TreeEntry) bytes.Buffer { -- cgit