| /* |
| Package mem_git provides convenience functionality for writing test data into a GitStore. |
| */ |
| |
| package mem_git |
| |
| import ( |
| "context" |
| "crypto/sha1" |
| "fmt" |
| "time" |
| |
| "github.com/stretchr/testify/require" |
| "go.skia.org/infra/go/git" |
| "go.skia.org/infra/go/git/git_common" |
| "go.skia.org/infra/go/gitstore" |
| "go.skia.org/infra/go/sktest" |
| "go.skia.org/infra/go/testutils" |
| "go.skia.org/infra/go/util" |
| "go.skia.org/infra/go/vcsinfo" |
| ) |
| |
| const ( |
| // These are the commit hashes generated by CommitN(2) when run as the |
| // first call to MemGit. |
| Commit0 = "407108ae8039ba9dce77e81057721fc7c4dbab87" |
| Commit1 = "a1dcf4a9669ff77919dfca71901ec98145b0af94" |
| ) |
| |
| var ( |
| // BaseTime is an arbitrary timestamp used as the time of the first |
| // commit. Subsequent commits add a fixed duration to the timestamp |
| // of their parent(s). This keeps the commit hashes predictable. |
| BaseTime = time.Unix(1571926390, 0).UTC() |
| ) |
| |
| // FakeCommitAt creates a LongCommit with the given message at the given time, |
| // belonging to the given branch, and with the given parent commits. Its Index |
| // increases monotonically with respect to the parents. |
| func FakeCommitAt(t sktest.TestingT, msg, branch string, ts time.Time, parents ...*vcsinfo.LongCommit) *vcsinfo.LongCommit { |
| index := 0 |
| var parentHashes []string |
| if len(parents) > 0 { |
| parentHashes = make([]string, 0, len(parents)) |
| for _, p := range parents { |
| if p.Index >= index { |
| index = p.Index + 1 |
| } |
| parentHashes = append(parentHashes, p.Hash) |
| } |
| } |
| lc := &vcsinfo.LongCommit{ |
| ShortCommit: &vcsinfo.ShortCommit{ |
| Author: "me@google.com", |
| Subject: msg, |
| }, |
| Parents: parentHashes, |
| Timestamp: ts, |
| Index: index, |
| Branches: map[string]bool{ |
| branch: true, |
| }, |
| } |
| j := testutils.MarshalJSON(t, lc) |
| lc.Hash = fmt.Sprintf("%040x", sha1.Sum([]byte(j))) |
| return lc |
| } |
| |
| // FakeCommit creates a LongCommit with the given message, belonging to the |
| // given branch, and with the given parent commits. Its Timestamp and Index |
| // increase monotonically with respect to the parents. |
| func FakeCommit(t sktest.TestingT, msg, branch string, parents ...*vcsinfo.LongCommit) *vcsinfo.LongCommit { |
| ts := BaseTime |
| if len(parents) > 0 { |
| for _, p := range parents { |
| if !ts.After(p.Timestamp) { |
| ts = p.Timestamp.Add(time.Minute) |
| } |
| } |
| } |
| return FakeCommitAt(t, msg, branch, ts, parents...) |
| } |
| |
| // New returns a MemGit instance which writes to the given GitStore. |
| func New(t sktest.TestingT, gs gitstore.GitStore) *MemGit { |
| return &MemGit{ |
| branch: git.MainBranch, |
| gs: gs, |
| t: t, |
| } |
| } |
| |
| // MemGit is a struct used for writing fake commits into a GitStore. |
| type MemGit struct { |
| branch string |
| gs gitstore.GitStore |
| t sktest.TestingT |
| updaters []Updater |
| } |
| |
| // head returns the current head of the given branch. |
| func (g *MemGit) head(branch string) string { |
| branches, err := g.gs.GetBranches(context.Background()) |
| require.NoError(g.t, err) |
| ptr, ok := branches[branch] |
| if !ok { |
| return "" |
| } |
| return ptr.Head |
| } |
| |
| // makeCommit creates a commit with the given message and parents on the |
| // currently-active branch. |
| func (g *MemGit) makeCommit(msg string, ts time.Time, parentHashes []string) *vcsinfo.LongCommit { |
| ctx := context.Background() |
| parents := []*vcsinfo.LongCommit{} |
| if len(parentHashes) > 0 { |
| var err error |
| parents, err = g.gs.Get(ctx, parentHashes) |
| require.NoError(g.t, err) |
| for _, p := range parents { |
| require.NotNil(g.t, p) |
| } |
| } |
| var lc *vcsinfo.LongCommit |
| if util.TimeIsZero(ts) { |
| lc = FakeCommit(g.t, msg, g.branch, parents...) |
| } else { |
| lc = FakeCommitAt(g.t, msg, g.branch, ts, parents...) |
| } |
| require.NoError(g.t, g.gs.Put(ctx, []*vcsinfo.LongCommit{lc})) |
| require.NoError(g.t, g.gs.PutBranches(ctx, map[string]string{ |
| g.branch: lc.Hash, |
| })) |
| g.update() |
| return lc |
| } |
| |
| // Commit adds a commit to the GitStore with the given commit message and |
| // parents, and returns its hash. If no parents are provided, the head of the |
| // current branch is used as the parent. In either case, the head of the |
| // current branch is set to be the new commit. |
| func (g *MemGit) Commit(msg string, parents ...string) string { |
| return g.CommitAt(msg, time.Time{}, parents...) |
| } |
| |
| // CommitAt adds a commit to the GitStore with the given commit message, |
| // timestamp, and parents, and returns its hash. If no parents are provided, |
| // the head of the current branch is used as the parent. In either case, the |
| // head of the current branch is set to be the new commit. |
| func (g *MemGit) CommitAt(msg string, ts time.Time, parents ...string) string { |
| if len(parents) == 0 { |
| head := g.head(g.branch) |
| if head != "" { |
| parents = []string{head} |
| } |
| } |
| return g.makeCommit(msg, ts, parents).Hash |
| } |
| |
| // CommitN adds N commits to the GitStore on the current branch and returns |
| // their hashes in reverse chronological order. |
| func (g *MemGit) CommitN(n int) []string { |
| hashes := make([]string, 0, n) |
| for i := 0; i < n; i++ { |
| hashes = append(hashes, g.Commit(fmt.Sprintf("Fake #%d/%d", i, n))) |
| } |
| return util.Reverse(hashes) |
| } |
| |
| // CheckoutBranch switches to the given branch. |
| func (g *MemGit) CheckoutBranch(branch string) { |
| branches, err := g.gs.GetBranches(context.Background()) |
| require.NoError(g.t, err) |
| ptr, ok := branches[branch] |
| require.True(g.t, ok) |
| require.NotNil(g.t, ptr) |
| require.NotEqual(g.t, "", ptr.Head) |
| g.branch = branch |
| } |
| |
| // NewBranch creates the given branch at the given commit hash and switches to it. |
| func (g *MemGit) NewBranch(branch, head string) { |
| ctx := context.Background() |
| branches, err := g.gs.GetBranches(ctx) |
| require.NoError(g.t, err) |
| _, ok := branches[branch] |
| require.False(g.t, ok, "Already have %s", branch) |
| require.NoError(g.t, g.gs.PutBranches(ctx, map[string]string{ |
| branch: head, |
| })) |
| g.branch = branch |
| g.update() |
| } |
| |
| // Merge creates a new commit which merges the given branch into the currently |
| // active branch. |
| func (g *MemGit) Merge(branch string) string { |
| return g.makeCommit(fmt.Sprintf("Merge %s", branch), time.Time{}, []string{g.head(g.branch), g.head(branch)}).Hash |
| } |
| |
| // Updater is an interface used for automatically receiving updates when the |
| // MemGit changes. |
| type Updater interface { |
| // Update indicates that the MemGit has changed. The Context passed to |
| // Update is likely to be a placeholder or empty. |
| Update(ctx context.Context) error |
| } |
| |
| // AddUpdater adds the given Updater to the MemGit. Subsequent mutations to the |
| // MemGit will result in a call to Updater.Update(). |
| func (g *MemGit) AddUpdater(u Updater) { |
| g.updaters = append(g.updaters, u) |
| } |
| |
| // update all of the registered Updaters. |
| func (g *MemGit) update() { |
| for _, u := range g.updaters { |
| require.NoError(g.t, u.Update(context.Background())) |
| } |
| } |
| |
| // FillWithBranchingHistory adds commits to the MemGit. |
| // |
| // The repo layout looks like this: |
| // |
| // older newer |
| // c0--c1------c3--c4-- |
| // |
| // \-c2-----/ |
| // |
| // Returns the commit hashes in order from c0-c4. This is analogous to |
| // git_builder.FillWithBranchingHistory. |
| func FillWithBranchingHistory(mg *MemGit) []string { |
| c0 := mg.Commit("c0") |
| c1 := mg.Commit("c1") |
| mg.NewBranch("branch2", c1) |
| c2 := mg.Commit("c2") |
| mg.CheckoutBranch(git_common.MainBranch) |
| c3 := mg.Commit("c3") |
| c4 := mg.Merge("branch2") |
| return []string{c0, c1, c2, c3, c4} |
| } |