diff --git a/blob.go b/blob.go index 9bb85466..399171b4 100644 --- a/blob.go +++ b/blob.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "io" ) @@ -16,14 +17,16 @@ type Blob struct { // Bytes reads and returns the content of the blob all at once in bytes. This // can be very slow and memory consuming for huge content. -func (b *Blob) Bytes() ([]byte, error) { +func (b *Blob) Bytes(ctx context.Context) ([]byte, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) // Preallocate memory to save ~50% memory usage on big files. - stdout.Grow(int(b.Size())) + if size := b.Size(ctx); size > 0 && size < int64(^uint(0)>>1) { + stdout.Grow(int(size)) + } - if err := b.Pipeline(stdout, stderr); err != nil { + if err := b.Pipeline(ctx, stdout, stderr); err != nil { return nil, concatenateError(err, stderr.String()) } return stdout.Bytes(), nil @@ -31,6 +34,6 @@ func (b *Blob) Bytes() ([]byte, error) { // Pipeline reads the content of the blob and pipes stdout and stderr to // supplied io.Writer. -func (b *Blob) Pipeline(stdout, stderr io.Writer) error { - return NewCommand("show", b.id.String()).RunInDirPipeline(stdout, stderr, b.parent.repo.path) +func (b *Blob) Pipeline(ctx context.Context, stdout, stderr io.Writer) error { + return NewCommand(ctx, "show", b.id.String()).RunInDirPipeline(stdout, stderr, b.parent.repo.path) } diff --git a/blob_test.go b/blob_test.go index a9273b7d..6ccc46ee 100644 --- a/blob_test.go +++ b/blob_test.go @@ -6,12 +6,14 @@ package git import ( "bytes" + "context" "testing" "github.com/stretchr/testify/assert" ) func TestBlob(t *testing.T) { + ctx := context.Background() expOutput := `This is a sample project students can use during Matthew's Git class. Here is an addition by me @@ -41,14 +43,14 @@ This demo also includes an image with changes on a branch for examination of ima } t.Run("get data all at once", func(t *testing.T) { - p, err := blob.Bytes() + p, err := blob.Bytes(ctx) assert.Nil(t, err) assert.Equal(t, expOutput, string(p)) }) t.Run("get data with pipeline", func(t *testing.T) { stdout := new(bytes.Buffer) - err := blob.Pipeline(stdout, nil) + err := blob.Pipeline(ctx, stdout, nil) assert.Nil(t, err) assert.Equal(t, expOutput, stdout.String()) }) diff --git a/command.go b/command.go index 4dc5f017..54e0094f 100644 --- a/command.go +++ b/command.go @@ -17,22 +17,16 @@ import ( // Command contains the name, arguments and environment variables of a command. type Command struct { - name string - args []string - envs []string - timeout time.Duration - ctx context.Context + name string + args []string + envs []string + ctx context.Context } // CommandOptions contains options for running a command. -// If timeout is zero, DefaultTimeout will be used. -// If timeout is less than zero, no timeout will be set. -// If context is nil, context.Background() will be used. type CommandOptions struct { - Args []string - Envs []string - Timeout time.Duration - Context context.Context + Args []string + Envs []string } // String returns the string representation of the command. @@ -44,13 +38,7 @@ func (c *Command) String() string { } // NewCommand creates and returns a new Command with given arguments for "git". -func NewCommand(args ...string) *Command { - return NewCommandWithContext(context.Background(), args...) -} - -// NewCommandWithContext creates and returns a new Command with given arguments -// and context for "git". -func NewCommandWithContext(ctx context.Context, args ...string) *Command { +func NewCommand(ctx context.Context, args ...string) *Command { return &Command{ name: "git", args: args, @@ -76,23 +64,9 @@ func (c Command) WithContext(ctx context.Context) *Command { return &c } -// WithTimeout returns a new Command with given timeout. -func (c Command) WithTimeout(timeout time.Duration) *Command { - c.timeout = timeout - return &c -} - -// SetTimeout sets the timeout for the command. -func (c *Command) SetTimeout(timeout time.Duration) { - c.timeout = timeout -} - // AddOptions adds options to the command. -// Note: only the last option will take effect if there are duplicated options. func (c *Command) AddOptions(opts ...CommandOptions) *Command { for _, opt := range opts { - c.timeout = opt.Timeout - c.ctx = opt.Context c.AddArgs(opt.Args...) c.AddEnvs(opt.Envs...) } @@ -105,7 +79,8 @@ func (c *Command) AddCommitter(committer *Signature) *Command { return c } -// DefaultTimeout is the default timeout duration for all commands. +// DefaultTimeout is the default timeout duration for all commands. It is +// applied when the context does not already have a deadline. const DefaultTimeout = time.Minute // A limitDualWriter writes to W but limits the amount of data written to just N @@ -144,33 +119,19 @@ type RunInDirOptions struct { Stdout io.Writer // Stderr is the error output from the command. Stderr io.Writer - // Timeout is the duration to wait before timing out. - // - // Deprecated: Use CommandOptions.Timeout or *Command.WithTimeout instead. - Timeout time.Duration } // RunInDirWithOptions executes the command in given directory and options. It // pipes stdin from supplied io.Reader, and pipes stdout and stderr to supplied -// io.Writer. DefaultTimeout will be used if the timeout duration is less than -// time.Nanosecond (i.e. less than or equal to 0). It returns an ErrExecTimeout -// if the execution was timed out. +// io.Writer. If the command's context does not have a deadline, DefaultTimeout +// will be applied automatically. It returns an ErrExecTimeout if the execution +// was timed out. func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err error) { var opt RunInDirOptions if len(opts) > 0 { opt = opts[0] } - timeout := c.timeout - // TODO: remove this in newer version - if opt.Timeout > 0 { - timeout = opt.Timeout - } - - if timeout == 0 { - timeout = DefaultTimeout - } - buf := new(bytes.Buffer) w := opt.Stdout if logOutput != nil { @@ -184,26 +145,22 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err defer func() { if len(dir) == 0 { - log("[timeout: %v] %s\n%s", timeout, c, buf.Bytes()) + log("%s\n%s", c, buf.Bytes()) } else { - log("[timeout: %v] %s: %s\n%s", timeout, dir, c, buf.Bytes()) + log("%s: %s\n%s", dir, c, buf.Bytes()) } }() - ctx := context.Background() - if c.ctx != nil { - ctx = c.ctx + ctx := c.ctx + if ctx == nil { + ctx = context.Background() } - if timeout > 0 { + // Apply default timeout if the context doesn't already have a deadline. + if _, ok := ctx.Deadline(); !ok { var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, timeout) - defer func() { - cancel() - if err == context.DeadlineExceeded { - err = ErrExecTimeout - } - }() + ctx, cancel = context.WithTimeout(ctx, DefaultTimeout) + defer cancel() } cmd := exec.CommandContext(ctx, c.name, c.args...) @@ -215,6 +172,11 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err cmd.Stdout = w cmd.Stderr = opt.Stderr if err = cmd.Start(); err != nil { + if ctx.Err() == context.DeadlineExceeded { + return ErrExecTimeout + } else if ctx.Err() != nil { + return ctx.Err() + } return err } @@ -225,22 +187,33 @@ func (c *Command) RunInDirWithOptions(dir string, opts ...RunInDirOptions) (err select { case <-ctx.Done(): - <-result - if cmd.Process != nil && cmd.ProcessState != nil && !cmd.ProcessState.Exited() { - if err := cmd.Process.Kill(); err != nil { - return fmt.Errorf("kill process: %v", err) - } + // Kill the process before waiting so cancellation is enforced promptly. + if cmd.Process != nil { + _ = cmd.Process.Kill() } + <-result - return ErrExecTimeout + if ctx.Err() == context.DeadlineExceeded { + return ErrExecTimeout + } + return ctx.Err() case err = <-result: + // Normalize errors when the context may have expired around the same time. + if err != nil { + if ctxErr := ctx.Err(); ctxErr != nil { + if ctxErr == context.DeadlineExceeded { + return ErrExecTimeout + } + return ctxErr + } + } return err } } -// RunInDirPipeline executes the command in given directory and default timeout -// duration. It pipes stdout and stderr to supplied io.Writer. +// RunInDirPipeline executes the command in given directory. It pipes stdout and +// stderr to supplied io.Writer. func (c *Command) RunInDirPipeline(stdout, stderr io.Writer, dir string) error { return c.RunInDirWithOptions(dir, RunInDirOptions{ Stdin: nil, @@ -249,35 +222,8 @@ func (c *Command) RunInDirPipeline(stdout, stderr io.Writer, dir string) error { }) } -// RunInDirPipelineWithTimeout executes the command in given directory and -// timeout duration. It pipes stdout and stderr to supplied io.Writer. -// DefaultTimeout will be used if the timeout duration is less than -// time.Nanosecond (i.e. less than or equal to 0). It returns an ErrExecTimeout -// if the execution was timed out. -// -// Deprecated: Use RunInDirPipeline and CommandOptions instead. -// TODO: remove this in the next major version -func (c *Command) RunInDirPipelineWithTimeout(timeout time.Duration, stdout, stderr io.Writer, dir string) (err error) { - if timeout != 0 { - c = c.WithTimeout(timeout) - } - return c.RunInDirPipeline(stdout, stderr, dir) -} - -// RunInDirWithTimeout executes the command in given directory and timeout -// duration. It returns stdout in []byte and error (combined with stderr). -// -// Deprecated: Use RunInDir and CommandOptions instead. -// TODO: remove this in the next major version -func (c *Command) RunInDirWithTimeout(timeout time.Duration, dir string) ([]byte, error) { - if timeout != 0 { - c = c.WithTimeout(timeout) - } - return c.RunInDir(dir) -} - -// RunInDir executes the command in given directory and default timeout -// duration. It returns stdout and error (combined with stderr). +// RunInDir executes the command in given directory. It returns stdout and error +// (combined with stderr). func (c *Command) RunInDir(dir string) ([]byte, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) @@ -287,20 +233,8 @@ func (c *Command) RunInDir(dir string) ([]byte, error) { return stdout.Bytes(), nil } -// RunWithTimeout executes the command in working directory and given timeout -// duration. It returns stdout in string and error (combined with stderr). -// -// Deprecated: Use RunInDir and CommandOptions instead. -// TODO: remove this in the next major version -func (c *Command) RunWithTimeout(timeout time.Duration) ([]byte, error) { - if timeout != 0 { - c = c.WithTimeout(timeout) - } - return c.Run() -} - -// Run executes the command in working directory and default timeout duration. -// It returns stdout in string and error (combined with stderr). +// Run executes the command in working directory. It returns stdout and +// error (combined with stderr). func (c *Command) Run() ([]byte, error) { stdout, err := c.RunInDir("") if err != nil { diff --git a/command_test.go b/command_test.go index 53e2bf21..e2fb26f5 100644 --- a/command_test.go +++ b/command_test.go @@ -5,6 +5,8 @@ package git import ( + "context" + "io" "testing" "time" @@ -12,6 +14,7 @@ import ( ) func TestCommand_String(t *testing.T) { + ctx := context.Background() tests := []struct { name string args []string @@ -35,14 +38,15 @@ func TestCommand_String(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - cmd := NewCommand(test.args...) + cmd := NewCommand(ctx, test.args...) assert.Equal(t, test.expStr, cmd.String()) }) } } func TestCommand_AddArgs(t *testing.T) { - cmd := NewCommand() + ctx := context.Background() + cmd := NewCommand(ctx) assert.Equal(t, []string(nil), cmd.args) cmd.AddArgs("push") @@ -51,7 +55,8 @@ func TestCommand_AddArgs(t *testing.T) { } func TestCommand_AddEnvs(t *testing.T) { - cmd := NewCommand() + ctx := context.Background() + cmd := NewCommand(ctx) assert.Equal(t, []string(nil), cmd.envs) cmd.AddEnvs("GIT_DIR=/tmp") @@ -59,7 +64,71 @@ func TestCommand_AddEnvs(t *testing.T) { assert.Equal(t, []string{"GIT_DIR=/tmp", "HOME=/Users/unknwon", "GIT_EDITOR=code"}, cmd.envs) } -func TestCommand_RunWithTimeout(t *testing.T) { - _, err := NewCommand("version").WithTimeout(time.Nanosecond).Run() - assert.Equal(t, ErrExecTimeout, err) +func TestCommand_RunWithContextTimeout(t *testing.T) { + t.Run("context already expired before start", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + time.Sleep(time.Millisecond) // ensure deadline has passed + _, err := NewCommand(ctx, "version").Run() + assert.Equal(t, ErrExecTimeout, err) + }) + + t.Run("context deadline fires mid-execution", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + // Use a blocking reader so the command starts successfully and blocks + // reading stdin until the context deadline fires. + err := NewCommand(ctx, "hash-object", "--stdin").RunInDirWithOptions("", RunInDirOptions{ + Stdin: blockingReader{cancel: ctx.Done()}, + Stdout: io.Discard, + Stderr: io.Discard, + }) + assert.Equal(t, ErrExecTimeout, err) + }) +} + +// blockingReader is an io.Reader that blocks until its cancel channel is +// closed, simulating a stdin that never provides data. When cancelled it +// returns io.EOF so that exec's stdin copy goroutine can exit cleanly, +// allowing cmd.Wait() to return. +type blockingReader struct { + cancel <-chan struct{} +} + +func (r blockingReader) Read(p []byte) (int, error) { + <-r.cancel + return 0, io.EOF +} + +func TestCommand_RunWithContextCancellation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + // Cancel in the background after a short delay so the command is already + // running when cancellation arrives. Close done to unblock the reader. + done := make(chan struct{}) + go func() { + time.Sleep(50 * time.Millisecond) + cancel() + close(done) + }() + + err := NewCommand(ctx, "hash-object", "--stdin").RunInDirWithOptions("", RunInDirOptions{ + Stdin: blockingReader{cancel: done}, + Stdout: io.Discard, + Stderr: io.Discard, + }) + assert.ErrorIs(t, err, context.Canceled) + // Must NOT be ErrExecTimeout — cancellation is distinct from deadline. + assert.NotEqual(t, ErrExecTimeout, err) +} + +func TestCommand_DefaultTimeoutApplied(t *testing.T) { + // A plain context.Background() has no deadline. The command should still + // succeed because DefaultTimeout (1 min) is applied automatically and + // "git version" completes well within that. + ctx := context.Background() + stdout, err := NewCommand(ctx, "version").Run() + assert.NoError(t, err) + assert.Contains(t, string(stdout), "git version") } diff --git a/commit.go b/commit.go index 301be259..381734e2 100644 --- a/commit.go +++ b/commit.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "io" "net/http" "strings" @@ -26,9 +27,9 @@ type Commit struct { parents []*SHA1 *Tree - submodules Submodules - submodulesOnce sync.Once - submodulesErr error + submodules Submodules + submodulesMu sync.Mutex + submodulesSet bool } // Summary returns first line of commit message. @@ -53,56 +54,56 @@ func (c *Commit) ParentID(n int) (*SHA1, error) { // Parent returns the n-th parent commit (0-based) of this commit. It returns // ErrRevisionNotExist if no such parent exists. -func (c *Commit) Parent(n int, opts ...CatFileCommitOptions) (*Commit, error) { +func (c *Commit) Parent(ctx context.Context, n int, opts ...CatFileCommitOptions) (*Commit, error) { id, err := c.ParentID(n) if err != nil { return nil, err } - return c.repo.CatFileCommit(id.String(), opts...) + return c.repo.CatFileCommit(ctx, id.String(), opts...) } // CommitByPath returns the commit of the path in the state of this commit. -func (c *Commit) CommitByPath(opts ...CommitByRevisionOptions) (*Commit, error) { - return c.repo.CommitByRevision(c.ID.String(), opts...) +func (c *Commit) CommitByPath(ctx context.Context, opts ...CommitByRevisionOptions) (*Commit, error) { + return c.repo.CommitByRevision(ctx, c.ID.String(), opts...) } // CommitsByPage returns a paginated list of commits in the state of this // commit. The returned list is in reverse chronological order. -func (c *Commit) CommitsByPage(page, size int, opts ...CommitsByPageOptions) ([]*Commit, error) { - return c.repo.CommitsByPage(c.ID.String(), page, size, opts...) +func (c *Commit) CommitsByPage(ctx context.Context, page, size int, opts ...CommitsByPageOptions) ([]*Commit, error) { + return c.repo.CommitsByPage(ctx, c.ID.String(), page, size, opts...) } // SearchCommits searches commit message with given pattern. The returned list // is in reverse chronological order. -func (c *Commit) SearchCommits(pattern string, opts ...SearchCommitsOptions) ([]*Commit, error) { - return c.repo.SearchCommits(c.ID.String(), pattern, opts...) +func (c *Commit) SearchCommits(ctx context.Context, pattern string, opts ...SearchCommitsOptions) ([]*Commit, error) { + return c.repo.SearchCommits(ctx, c.ID.String(), pattern, opts...) } // ShowNameStatus returns name status of the commit. -func (c *Commit) ShowNameStatus(opts ...ShowNameStatusOptions) (*NameStatus, error) { - return c.repo.ShowNameStatus(c.ID.String(), opts...) +func (c *Commit) ShowNameStatus(ctx context.Context, opts ...ShowNameStatusOptions) (*NameStatus, error) { + return c.repo.ShowNameStatus(ctx, c.ID.String(), opts...) } // CommitsCount returns number of total commits up to this commit. -func (c *Commit) CommitsCount(opts ...RevListCountOptions) (int64, error) { - return c.repo.RevListCount([]string{c.ID.String()}, opts...) +func (c *Commit) CommitsCount(ctx context.Context, opts ...RevListCountOptions) (int64, error) { + return c.repo.RevListCount(ctx, []string{c.ID.String()}, opts...) } // FilesChangedAfter returns a list of files changed after given commit ID. -func (c *Commit) FilesChangedAfter(after string, opts ...DiffNameOnlyOptions) ([]string, error) { - return c.repo.DiffNameOnly(after, c.ID.String(), opts...) +func (c *Commit) FilesChangedAfter(ctx context.Context, after string, opts ...DiffNameOnlyOptions) ([]string, error) { + return c.repo.DiffNameOnly(ctx, after, c.ID.String(), opts...) } // CommitsAfter returns a list of commits after given commit ID up to this // commit. The returned list is in reverse chronological order. -func (c *Commit) CommitsAfter(after string, opts ...RevListOptions) ([]*Commit, error) { - return c.repo.RevList([]string{after + "..." + c.ID.String()}, opts...) +func (c *Commit) CommitsAfter(ctx context.Context, after string, opts ...RevListOptions) ([]*Commit, error) { + return c.repo.RevList(ctx, []string{after + "..." + c.ID.String()}, opts...) } // Ancestors returns a list of ancestors of this commit in reverse chronological // order. -func (c *Commit) Ancestors(opts ...LogOptions) ([]*Commit, error) { +func (c *Commit) Ancestors(ctx context.Context, opts ...LogOptions) ([]*Commit, error) { if c.ParentsCount() == 0 { return []*Commit{}, nil } @@ -114,7 +115,7 @@ func (c *Commit) Ancestors(opts ...LogOptions) ([]*Commit, error) { opt.Skip++ - return c.repo.Log(c.ID.String(), opt) + return c.repo.Log(ctx, c.ID.String(), opt) } type limitWriter struct { @@ -138,7 +139,7 @@ func (w *limitWriter) Write(p []byte) (int, error) { return len(p), err } -func (c *Commit) isImageFile(blob *Blob, err error) (bool, error) { +func (c *Commit) isImageFile(ctx context.Context, blob *Blob, err error) (bool, error) { if err != nil { if err == ErrNotBlob { return false, nil @@ -153,7 +154,7 @@ func (c *Commit) isImageFile(blob *Blob, err error) (bool, error) { N: int64(buf.Cap()), } - err = blob.Pipeline(stdout, io.Discard) + err = blob.Pipeline(ctx, stdout, io.Discard) if err != nil { return false, err } @@ -162,12 +163,14 @@ func (c *Commit) isImageFile(blob *Blob, err error) (bool, error) { } // IsImageFile returns true if the blob of the commit is an image by subpath. -func (c *Commit) IsImageFile(subpath string) (bool, error) { - return c.isImageFile(c.Blob(subpath)) +func (c *Commit) IsImageFile(ctx context.Context, subpath string) (bool, error) { + blob, err := c.Blob(ctx, subpath) + return c.isImageFile(ctx, blob, err) } // IsImageFileByIndex returns true if the blob of the commit is an image by // index. -func (c *Commit) IsImageFileByIndex(index string) (bool, error) { - return c.isImageFile(c.BlobByIndex(index)) +func (c *Commit) IsImageFileByIndex(ctx context.Context, index string) (bool, error) { + blob, err := c.BlobByIndex(ctx, index) + return c.isImageFile(ctx, blob, err) } diff --git a/commit_archive.go b/commit_archive.go index cbc45750..535f6f82 100644 --- a/commit_archive.go +++ b/commit_archive.go @@ -5,6 +5,7 @@ package git import ( + "context" "path/filepath" "strings" ) @@ -19,9 +20,9 @@ const ( ) // CreateArchive creates given format of archive to the destination. -func (c *Commit) CreateArchive(format ArchiveFormat, dst string) error { +func (c *Commit) CreateArchive(ctx context.Context, format ArchiveFormat, dst string) error { prefix := filepath.Base(strings.TrimSuffix(c.repo.path, ".git")) + "/" - _, err := NewCommand("archive", + _, err := NewCommand(ctx, "archive", "--prefix="+prefix, "--format="+string(format), "-o", dst, diff --git a/commit_archive_test.go b/commit_archive_test.go index f9376821..2523c73c 100644 --- a/commit_archive_test.go +++ b/commit_archive_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "os" "path/filepath" "strconv" @@ -19,12 +20,13 @@ func tempPath() string { } func TestCommit_CreateArchive(t *testing.T) { + ctx := context.Background() for _, format := range []ArchiveFormat{ ArchiveZip, ArchiveTarGz, } { t.Run(string(format), func(t *testing.T) { - c, err := testrepo.CatFileCommit("755fd577edcfd9209d0ac072eed3b022cbe4d39b") + c, err := testrepo.CatFileCommit(ctx, "755fd577edcfd9209d0ac072eed3b022cbe4d39b") if err != nil { t.Fatal(err) } @@ -34,7 +36,7 @@ func TestCommit_CreateArchive(t *testing.T) { _ = os.Remove(dst) }() - assert.Nil(t, c.CreateArchive(format, dst)) + assert.Nil(t, c.CreateArchive(ctx, format, dst)) }) } } diff --git a/commit_submodule.go b/commit_submodule.go index da63231e..3c4da05c 100644 --- a/commit_submodule.go +++ b/commit_submodule.go @@ -7,6 +7,7 @@ package git import ( "bufio" "bytes" + "context" "strings" ) @@ -23,68 +24,82 @@ type Submodule struct { // Submodules contains information of submodules. type Submodules = *objectCache -// Submodules returns submodules found in this commit. -func (c *Commit) Submodules() (Submodules, error) { - c.submodulesOnce.Do(func() { - var e *TreeEntry - e, c.submodulesErr = c.TreeEntry(".gitmodules") - if c.submodulesErr != nil { - return +// Submodules returns submodules found in this commit. Successful results are +// cached; failed attempts are not cached, allowing retries with a fresh context. +func (c *Commit) Submodules(ctx context.Context) (Submodules, error) { + c.submodulesMu.Lock() + defer c.submodulesMu.Unlock() + + if c.submodulesSet { + return c.submodules, nil + } + + e, err := c.TreeEntry(ctx, ".gitmodules") + if err != nil { + return nil, err + } + + p, err := e.Blob().Bytes(ctx) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(bytes.NewReader(p)) + submodules := newObjectCache() + var inSection bool + var path string + var url string + for scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "[submodule") { + inSection = true + path = "" + url = "" + continue + } else if !inSection { + continue } - var p []byte - p, c.submodulesErr = e.Blob().Bytes() - if c.submodulesErr != nil { - return + key, value, ok := strings.Cut(scanner.Text(), "=") + if !ok { + continue } - scanner := bufio.NewScanner(bytes.NewReader(p)) - c.submodules = newObjectCache() - var inSection bool - var path string - var url string - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), "[submodule") { - inSection = true - path = "" - url = "" - continue - } else if !inSection { - continue - } + switch strings.TrimSpace(key) { + case "path": + path = strings.TrimSpace(value) + case "url": + url = strings.TrimSpace(value) + } - fields := strings.Split(scanner.Text(), "=") - switch strings.TrimSpace(fields[0]) { - case "path": - path = strings.TrimSpace(fields[1]) - case "url": - url = strings.TrimSpace(fields[1]) + if len(path) > 0 && len(url) > 0 { + mod := &Submodule{ + Name: path, + URL: url, } - if len(path) > 0 && len(url) > 0 { - mod := &Submodule{ - Name: path, - URL: url, - } - - mod.Commit, c.submodulesErr = c.repo.RevParse(c.id.String() + ":" + mod.Name) - if c.submodulesErr != nil { - return - } - - c.submodules.Set(path, mod) - inSection = false + mod.Commit, err = c.repo.RevParse(ctx, c.id.String()+":"+mod.Name) + if err != nil { + return nil, err } + + submodules.Set(path, mod) + inSection = false } - }) + } + + if err := scanner.Err(); err != nil { + return nil, err + } - return c.submodules, c.submodulesErr + c.submodules = submodules + c.submodulesSet = true + return c.submodules, nil } // Submodule returns submodule by given name. It returns an ErrSubmoduleNotExist // if the path does not exist as a submodule. -func (c *Commit) Submodule(path string) (*Submodule, error) { - mods, err := c.Submodules() +func (c *Commit) Submodule(ctx context.Context, path string) (*Submodule, error) { + mods, err := c.Submodules(ctx) if err != nil { return nil, err } diff --git a/commit_submodule_test.go b/commit_submodule_test.go index cecc661d..2861fc0f 100644 --- a/commit_submodule_test.go +++ b/commit_submodule_test.go @@ -5,18 +5,21 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestCommit_Submodule(t *testing.T) { - c, err := testrepo.CatFileCommit("4e59b72440188e7c2578299fc28ea425fbe9aece") + ctx := context.Background() + + c, err := testrepo.CatFileCommit(ctx, "4e59b72440188e7c2578299fc28ea425fbe9aece") if err != nil { t.Fatal(err) } - mod, err := c.Submodule("gogs/docs-api") + mod, err := c.Submodule(ctx, "gogs/docs-api") if err != nil { t.Fatal(err) } @@ -24,6 +27,6 @@ func TestCommit_Submodule(t *testing.T) { assert.Equal(t, "https://github.com/gogs/docs-api.git", mod.URL) assert.Equal(t, "6b08f76a5313fa3d26859515b30aa17a5faa2807", mod.Commit) - _, err = c.Submodule("404") + _, err = c.Submodule(ctx, "404") assert.Equal(t, ErrSubmoduleNotExist, err) } diff --git a/commit_test.go b/commit_test.go index a9d9cbfb..ca256804 100644 --- a/commit_test.go +++ b/commit_test.go @@ -5,13 +5,15 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestCommit(t *testing.T) { - c, err := testrepo.CatFileCommit("435ffceb7ba576c937e922766e37d4f7abdcc122") + ctx := context.Background() + c, err := testrepo.CatFileCommit(ctx, "435ffceb7ba576c937e922766e37d4f7abdcc122") if err != nil { t.Fatal(err) } @@ -25,7 +27,8 @@ func TestCommit(t *testing.T) { } func TestCommit_Parent(t *testing.T) { - c, err := testrepo.CatFileCommit("435ffceb7ba576c937e922766e37d4f7abdcc122") + ctx := context.Background() + c, err := testrepo.CatFileCommit(ctx, "435ffceb7ba576c937e922766e37d4f7abdcc122") if err != nil { t.Fatal(err) } @@ -36,7 +39,7 @@ func TestCommit_Parent(t *testing.T) { t.Run("Parent", func(t *testing.T) { t.Run("no such parent", func(t *testing.T) { - _, err := c.Parent(c.ParentsCount() + 1) + _, err := c.Parent(ctx, c.ParentsCount()+1) assert.Equal(t, ErrParentNotExist, err) }) @@ -55,7 +58,7 @@ func TestCommit_Parent(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - p, err := c.Parent(test.n) + p, err := c.Parent(ctx, test.n) if err != nil { t.Fatal(err) } @@ -66,6 +69,7 @@ func TestCommit_Parent(t *testing.T) { } func TestCommit_CommitByPath(t *testing.T) { + ctx := context.Background() tests := []struct { id string opt CommitByRevisionOptions @@ -88,12 +92,12 @@ func TestCommit_CommitByPath(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - cc, err := c.CommitByPath(test.opt) + cc, err := c.CommitByPath(ctx, test.opt) if err != nil { t.Fatal(err) } @@ -113,8 +117,9 @@ func commitsToIDs(commits []*Commit) []string { } func TestCommit_CommitsByPage(t *testing.T) { + ctx := context.Background() // There are at most 5 commits can be used for pagination before this commit. - c, err := testrepo.CatFileCommit("f5ed01959cffa4758ca0a49bf4c34b138d7eab0a") + c, err := testrepo.CatFileCommit(ctx, "f5ed01959cffa4758ca0a49bf4c34b138d7eab0a") if err != nil { t.Fatal(err) } @@ -175,7 +180,7 @@ func TestCommit_CommitsByPage(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - commits, err := c.CommitsByPage(test.page, test.size, test.opt) + commits, err := c.CommitsByPage(ctx, test.page, test.size, test.opt) if err != nil { t.Fatal(err) } @@ -186,6 +191,7 @@ func TestCommit_CommitsByPage(t *testing.T) { } func TestCommit_SearchCommits(t *testing.T) { + ctx := context.Background() tests := []struct { id string pattern string @@ -271,12 +277,12 @@ func TestCommit_SearchCommits(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - commits, err := c.SearchCommits(test.pattern, test.opt) + commits, err := c.SearchCommits(ctx, test.pattern, test.opt) if err != nil { t.Fatal(err) } @@ -287,6 +293,7 @@ func TestCommit_SearchCommits(t *testing.T) { } func TestCommit_ShowNameStatus(t *testing.T) { + ctx := context.Background() tests := []struct { id string opt ShowNameStatusOptions @@ -332,12 +339,12 @@ func TestCommit_ShowNameStatus(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - status, err := c.ShowNameStatus(test.opt) + status, err := c.ShowNameStatus(ctx, test.opt) if err != nil { t.Fatal(err) } @@ -348,6 +355,7 @@ func TestCommit_ShowNameStatus(t *testing.T) { } func TestCommit_CommitsCount(t *testing.T) { + ctx := context.Background() tests := []struct { id string opt RevListCountOptions @@ -383,12 +391,12 @@ func TestCommit_CommitsCount(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - count, err := c.CommitsCount(test.opt) + count, err := c.CommitsCount(ctx, test.opt) if err != nil { t.Fatal(err) } @@ -399,6 +407,7 @@ func TestCommit_CommitsCount(t *testing.T) { } func TestCommit_FilesChangedAfter(t *testing.T) { + ctx := context.Background() tests := []struct { id string after string @@ -435,12 +444,12 @@ func TestCommit_FilesChangedAfter(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - files, err := c.FilesChangedAfter(test.after, test.opt) + files, err := c.FilesChangedAfter(ctx, test.after, test.opt) if err != nil { t.Fatal(err) } @@ -451,6 +460,7 @@ func TestCommit_FilesChangedAfter(t *testing.T) { } func TestCommit_CommitsAfter(t *testing.T) { + ctx := context.Background() tests := []struct { id string after string @@ -479,12 +489,12 @@ func TestCommit_CommitsAfter(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - commits, err := c.CommitsAfter(test.after, test.opt) + commits, err := c.CommitsAfter(ctx, test.after, test.opt) if err != nil { t.Fatal(err) } @@ -495,6 +505,7 @@ func TestCommit_CommitsAfter(t *testing.T) { } func TestCommit_Ancestors(t *testing.T) { + ctx := context.Background() tests := []struct { id string opt LogOptions @@ -518,12 +529,12 @@ func TestCommit_Ancestors(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - commits, err := c.Ancestors(test.opt) + commits, err := c.Ancestors(ctx, test.opt) if err != nil { t.Fatal(err) } @@ -534,13 +545,15 @@ func TestCommit_Ancestors(t *testing.T) { } func TestCommit_IsImageFile(t *testing.T) { + ctx := context.Background() + t.Run("not a blob", func(t *testing.T) { - c, err := testrepo.CatFileCommit("4e59b72440188e7c2578299fc28ea425fbe9aece") + c, err := testrepo.CatFileCommit(ctx, "4e59b72440188e7c2578299fc28ea425fbe9aece") if err != nil { t.Fatal(err) } - isImage, err := c.IsImageFile("gogs/docs-api") + isImage, err := c.IsImageFile(ctx, "gogs/docs-api") if err != nil { t.Fatal(err) } @@ -565,12 +578,12 @@ func TestCommit_IsImageFile(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - isImage, err := c.IsImageFile(test.name) + isImage, err := c.IsImageFile(ctx, test.name) if err != nil { t.Fatal(err) } @@ -581,13 +594,15 @@ func TestCommit_IsImageFile(t *testing.T) { } func TestCommit_IsImageFileByIndex(t *testing.T) { + ctx := context.Background() + t.Run("not a blob", func(t *testing.T) { - c, err := testrepo.CatFileCommit("4e59b72440188e7c2578299fc28ea425fbe9aece") + c, err := testrepo.CatFileCommit(ctx, "4e59b72440188e7c2578299fc28ea425fbe9aece") if err != nil { t.Fatal(err) } - isImage, err := c.IsImageFileByIndex("fcf7087e732bfe3c25328248a9bf8c3ccd85bed4") // "gogs" + isImage, err := c.IsImageFileByIndex(ctx, "fcf7087e732bfe3c25328248a9bf8c3ccd85bed4") // "gogs" if err != nil { t.Fatal(err) } @@ -612,12 +627,12 @@ func TestCommit_IsImageFileByIndex(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CatFileCommit(test.id) + c, err := testrepo.CatFileCommit(ctx, test.id) if err != nil { t.Fatal(err) } - isImage, err := c.IsImageFileByIndex(test.index) + isImage, err := c.IsImageFileByIndex(ctx, test.index) if err != nil { t.Fatal(err) } diff --git a/git.go b/git.go index 31955b3c..ebbfdfd7 100644 --- a/git.go +++ b/git.go @@ -5,6 +5,7 @@ package git import ( + "context" "fmt" "io" "strings" @@ -41,35 +42,40 @@ func log(format string, args ...interface{}) { var ( // gitVersion stores the Git binary version. // NOTE: To check Git version should call BinVersion not this global variable. - gitVersion string - gitVersionOnce sync.Once - gitVersionErr error + gitVersion string + gitVersionMu sync.Mutex + gitVersionSet bool ) // BinVersion returns current Git binary version that is used by this module. -func BinVersion() (string, error) { - gitVersionOnce.Do(func() { - var stdout []byte - stdout, gitVersionErr = NewCommand("version").Run() - if gitVersionErr != nil { - return - } +// Successful results are cached; failed attempts are not cached, allowing +// retries with a fresh context. +func BinVersion(ctx context.Context) (string, error) { + gitVersionMu.Lock() + defer gitVersionMu.Unlock() - fields := strings.Fields(string(stdout)) - if len(fields) < 3 { - gitVersionErr = fmt.Errorf("not enough output: %s", stdout) - return - } + if gitVersionSet { + return gitVersion, nil + } - // Handle special case on Windows. - i := strings.Index(fields[2], "windows") - if i >= 1 { - gitVersion = fields[2][:i-1] - return - } + stdout, err := NewCommand(ctx, "version").Run() + if err != nil { + return "", err + } + fields := strings.Fields(string(stdout)) + if len(fields) < 3 { + return "", fmt.Errorf("not enough output: %s", stdout) + } + + // Handle special case on Windows. + i := strings.Index(fields[2], "windows") + if i >= 1 { + gitVersion = fields[2][:i-1] + } else { gitVersion = fields[2] - }) + } - return gitVersion, gitVersionErr + gitVersionSet = true + return gitVersion, nil } diff --git a/git_test.go b/git_test.go index 10289006..5c0e0df9 100644 --- a/git_test.go +++ b/git_test.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "flag" stdlog "log" "os" @@ -25,9 +26,11 @@ func TestMain(m *testing.M) { SetOutput(os.Stdout) } + ctx := context.Background() + // Set up the test repository if !isExist(repoPath) { - if err := Clone("https://github.com/gogs/git-module-testrepo.git", repoPath, CloneOptions{ + if err := Clone(ctx, "https://github.com/gogs/git-module-testrepo.git", repoPath, CloneOptions{ Bare: true, }); err != nil { stdlog.Fatal(err) diff --git a/repo.go b/repo.go index 1eee7b2d..4b82c477 100644 --- a/repo.go +++ b/repo.go @@ -7,6 +7,7 @@ package git import ( "bufio" "bytes" + "context" "fmt" "io" "os" @@ -14,7 +15,6 @@ import ( "path/filepath" "strconv" "strings" - "time" ) // Repository contains information of a Git repository. @@ -35,7 +35,7 @@ const LogFormatHashOnly = `format:%H` // parsePrettyFormatLogToList returns a list of commits parsed from given logs // that are formatted in LogFormatHashOnly. -func (r *Repository) parsePrettyFormatLogToList(timeout time.Duration, logs []byte) ([]*Commit, error) { +func (r *Repository) parsePrettyFormatLogToList(ctx context.Context, logs []byte) ([]*Commit, error) { if len(logs) == 0 { return []*Commit{}, nil } @@ -44,7 +44,7 @@ func (r *Repository) parsePrettyFormatLogToList(timeout time.Duration, logs []by ids := bytes.Split(logs, []byte{'\n'}) commits := make([]*Commit, len(ids)) for i, id := range ids { - commits[i], err = r.CatFileCommit(string(id), CatFileCommitOptions{Timeout: timeout}) + commits[i], err = r.CatFileCommit(ctx, string(id)) if err != nil { return nil, err } @@ -58,17 +58,12 @@ func (r *Repository) parsePrettyFormatLogToList(timeout time.Duration, logs []by type InitOptions struct { // Indicates whether the repository should be initialized in bare format. Bare bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Init initializes a new Git repository. -func Init(path string, opts ...InitOptions) error { +func Init(ctx context.Context, path string, opts ...InitOptions) error { var opt InitOptions if len(opts) > 0 { opt = opts[0] @@ -79,12 +74,12 @@ func Init(path string, opts ...InitOptions) error { return err } - cmd := NewCommand("init").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "init").AddOptions(opt.CommandOptions) if opt.Bare { cmd.AddArgs("--bare") } cmd.AddArgs("--end-of-options") - _, err = cmd.RunInDirWithTimeout(opt.Timeout, path) + _, err = cmd.RunInDir(path) return err } @@ -120,17 +115,12 @@ type CloneOptions struct { Branch string // The number of revisions to clone. Depth uint64 - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Clone clones the repository from remote URL to the destination. -func Clone(url, dst string, opts ...CloneOptions) error { +func Clone(ctx context.Context, url, dst string, opts ...CloneOptions) error { var opt CloneOptions if len(opts) > 0 { opt = opts[0] @@ -141,7 +131,7 @@ func Clone(url, dst string, opts ...CloneOptions) error { return err } - cmd := NewCommand("clone").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "clone").AddOptions(opt.CommandOptions) if opt.Mirror { cmd.AddArgs("--mirror") } @@ -159,7 +149,7 @@ func Clone(url, dst string, opts ...CloneOptions) error { } cmd.AddArgs("--end-of-options") - _, err = cmd.AddArgs(url, dst).RunWithTimeout(opt.Timeout) + _, err = cmd.AddArgs(url, dst).Run() return err } @@ -169,28 +159,23 @@ func Clone(url, dst string, opts ...CloneOptions) error { type FetchOptions struct { // Indicates whether to prune during fetching. Prune bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Fetch fetches updates for the repository. -func (r *Repository) Fetch(opts ...FetchOptions) error { +func (r *Repository) Fetch(ctx context.Context, opts ...FetchOptions) error { var opt FetchOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("fetch").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "fetch").AddOptions(opt.CommandOptions) if opt.Prune { cmd.AddArgs("--prune") } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) return err } @@ -206,23 +191,18 @@ type PullOptions struct { Remote string // The branch to pull updates from when All=false and Remote is supplied. Branch string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Pull pulls updates for the repository. -func (r *Repository) Pull(opts ...PullOptions) error { +func (r *Repository) Pull(ctx context.Context, opts ...PullOptions) error { var opt PullOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("pull").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "pull").AddOptions(opt.CommandOptions) if opt.Rebase { cmd.AddArgs("--rebase") } @@ -236,7 +216,7 @@ func (r *Repository) Pull(opts ...PullOptions) error { } } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) return err } @@ -244,24 +224,19 @@ func (r *Repository) Pull(opts ...PullOptions) error { // // Docs: https://git-scm.com/docs/git-push type PushOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Push pushes local changes to given remote and branch for the repository. -func (r *Repository) Push(remote, branch string, opts ...PushOptions) error { +func (r *Repository) Push(ctx context.Context, remote, branch string, opts ...PushOptions) error { var opt PushOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("push").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", remote, branch) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + cmd := NewCommand(ctx, "push").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", remote, branch) + _, err := cmd.RunInDir(r.path) return err } @@ -271,23 +246,18 @@ func (r *Repository) Push(remote, branch string, opts ...PushOptions) error { type CheckoutOptions struct { // The base branch if checks out to a new branch. BaseBranch string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Checkout checks out to given branch for the repository. -func (r *Repository) Checkout(branch string, opts ...CheckoutOptions) error { +func (r *Repository) Checkout(ctx context.Context, branch string, opts ...CheckoutOptions) error { var opt CheckoutOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("checkout").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "checkout").AddOptions(opt.CommandOptions) if opt.BaseBranch != "" { cmd.AddArgs("-b") } @@ -296,7 +266,7 @@ func (r *Repository) Checkout(branch string, opts ...CheckoutOptions) error { cmd.AddArgs(opt.BaseBranch) } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) return err } @@ -306,23 +276,18 @@ func (r *Repository) Checkout(branch string, opts ...CheckoutOptions) error { type ResetOptions struct { // Indicates whether to perform a hard reset. Hard bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Reset resets working tree to given revision for the repository. -func (r *Repository) Reset(rev string, opts ...ResetOptions) error { +func (r *Repository) Reset(ctx context.Context, rev string, opts ...ResetOptions) error { var opt ResetOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("reset") + cmd := NewCommand(ctx, "reset") if opt.Hard { cmd.AddArgs("--hard") } @@ -336,24 +301,19 @@ func (r *Repository) Reset(rev string, opts ...ResetOptions) error { // // Docs: https://git-scm.com/docs/git-mv type MoveOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Move moves a file, a directory, or a symlink file or directory from source to // destination for the repository. -func (r *Repository) Move(src, dst string, opts ...MoveOptions) error { +func (r *Repository) Move(ctx context.Context, src, dst string, opts ...MoveOptions) error { var opt MoveOptions if len(opts) > 0 { opt = opts[0] } - _, err := NewCommand("mv").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", src, dst).RunInDirWithTimeout(opt.Timeout, r.path) + _, err := NewCommand(ctx, "mv").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", src, dst).RunInDir(r.path) return err } @@ -365,23 +325,18 @@ type AddOptions struct { All bool // The specific pathspecs to be added to index. Pathspecs []string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Add adds local changes to index for the repository. -func (r *Repository) Add(opts ...AddOptions) error { +func (r *Repository) Add(ctx context.Context, opts ...AddOptions) error { var opt AddOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("add").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "add").AddOptions(opt.CommandOptions) if opt.All { cmd.AddArgs("--all") } @@ -389,7 +344,7 @@ func (r *Repository) Add(opts ...AddOptions) error { cmd.AddArgs("--") cmd.AddArgs(opt.Pathspecs...) } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) return err } @@ -399,24 +354,19 @@ func (r *Repository) Add(opts ...AddOptions) error { type CommitOptions struct { // Author is the author of the changes if that's not the same as committer. Author *Signature - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Commit commits local changes with given author, committer and message for the // repository. -func (r *Repository) Commit(committer *Signature, message string, opts ...CommitOptions) error { +func (r *Repository) Commit(ctx context.Context, committer *Signature, message string, opts ...CommitOptions) error { var opt CommitOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("commit") + cmd := NewCommand(ctx, "commit") cmd.AddCommitter(committer) if opt.Author == nil { @@ -426,7 +376,7 @@ func (r *Repository) Commit(committer *Signature, message string, opts ...Commit AddArgs("-m", message). AddOptions(opt.CommandOptions) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) // No stderr but exit status 1 means nothing to commit. if err != nil && err.Error() == "exit status 1" { return nil @@ -445,17 +395,12 @@ type NameStatus struct { // // Docs: https://git-scm.com/docs/git-show#Documentation/git-show.txt---name-status type ShowNameStatusOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // ShowNameStatus returns name status of given revision of the repository. -func (r *Repository) ShowNameStatus(rev string, opts ...ShowNameStatusOptions) (*NameStatus, error) { +func (r *Repository) ShowNameStatus(ctx context.Context, rev string, opts ...ShowNameStatusOptions) (*NameStatus, error) { var opt ShowNameStatusOptions if len(opts) > 0 { opt = opts[0] @@ -485,10 +430,10 @@ func (r *Repository) ShowNameStatus(rev string, opts ...ShowNameStatusOptions) ( }() stderr := new(bytes.Buffer) - cmd := NewCommand("show", "--name-status", "--pretty=format:''"). + cmd := NewCommand(ctx, "show", "--name-status", "--pretty=format:''"). AddOptions(opt.CommandOptions). AddArgs("--end-of-options", rev) - err := cmd.RunInDirPipelineWithTimeout(opt.Timeout, w, stderr, r.path) + err := cmd.RunInDirPipeline(w, stderr, r.path) _ = w.Close() // Close writer to exit parsing goroutine if err != nil { return nil, concatenateError(err, stderr.String()) @@ -502,27 +447,22 @@ func (r *Repository) ShowNameStatus(rev string, opts ...ShowNameStatusOptions) ( // // Docs: https://git-scm.com/docs/git-rev-parse type RevParseOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RevParse returns full length (40) commit ID by given revision in the // repository. -func (r *Repository) RevParse(rev string, opts ...RevParseOptions) (string, error) { +func (r *Repository) RevParse(ctx context.Context, rev string, opts ...RevParseOptions) (string, error) { var opt RevParseOptions if len(opts) > 0 { opt = opts[0] } - commitID, err := NewCommand("rev-parse"). + commitID, err := NewCommand(ctx, "rev-parse"). AddOptions(opt.CommandOptions). AddArgs(rev). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { if strings.Contains(err.Error(), "exit status 128") { return "", ErrRevisionNotExist @@ -548,25 +488,20 @@ type CountObject struct { // // Docs: https://git-scm.com/docs/git-count-objects type CountObjectsOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CountObjects returns disk usage report of the repository. -func (r *Repository) CountObjects(opts ...CountObjectsOptions) (*CountObject, error) { +func (r *Repository) CountObjects(ctx context.Context, opts ...CountObjectsOptions) (*CountObject, error) { var opt CountObjectsOptions if len(opts) > 0 { opt = opts[0] } - stdout, err := NewCommand("count-objects", "-v"). + stdout, err := NewCommand(ctx, "count-objects", "-v"). AddOptions(opt.CommandOptions). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return nil, err } @@ -605,24 +540,19 @@ func (r *Repository) CountObjects(opts ...CountObjectsOptions) (*CountObject, er // // Docs: https://git-scm.com/docs/git-fsck type FsckOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Fsck verifies the connectivity and validity of the objects in the database // for the repository. -func (r *Repository) Fsck(opts ...FsckOptions) error { +func (r *Repository) Fsck(ctx context.Context, opts ...FsckOptions) error { var opt FsckOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("fsck").AddOptions(opt.CommandOptions) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + cmd := NewCommand(ctx, "fsck").AddOptions(opt.CommandOptions) + _, err := cmd.RunInDir(r.path) return err } diff --git a/repo_blame.go b/repo_blame.go index 047fa950..d2ffc062 100644 --- a/repo_blame.go +++ b/repo_blame.go @@ -6,33 +6,28 @@ package git import ( "bytes" - "time" + "context" ) // BlameOptions contains optional arguments for blaming a file. // Docs: https://git-scm.com/docs/git-blame type BlameOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Blame returns blame results of the file with the given revision of the // repository. -func (r *Repository) Blame(rev, file string, opts ...BlameOptions) (*Blame, error) { +func (r *Repository) Blame(ctx context.Context, rev, file string, opts ...BlameOptions) (*Blame, error) { var opt BlameOptions if len(opts) > 0 { opt = opts[0] } - stdout, err := NewCommand("blame"). + stdout, err := NewCommand(ctx, "blame"). AddOptions(opt.CommandOptions). AddArgs("-l", "-s", rev, "--", file). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return nil, err } @@ -51,7 +46,7 @@ func (r *Repository) Blame(rev, file string, opts ...BlameOptions) (*Blame, erro if id[0] == '^' { id = id[1:] } - commit, err := r.CatFileCommit(string(id), CatFileCommitOptions{Timeout: opt.Timeout}) //nolint + commit, err := r.CatFileCommit(ctx, string(id)) //nolint if err != nil { return nil, err } diff --git a/repo_blame_test.go b/repo_blame_test.go index 6073b78d..85fe38b7 100644 --- a/repo_blame_test.go +++ b/repo_blame_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "fmt" "testing" @@ -12,12 +13,14 @@ import ( ) func TestRepository_Blame(t *testing.T) { + ctx := context.Background() + t.Run("bad file", func(t *testing.T) { - _, err := testrepo.Blame("", "404.txt") + _, err := testrepo.Blame(ctx, "", "404.txt") assert.Error(t, err) }) - blame, err := testrepo.Blame("cfc3b2993f74726356887a5ec093de50486dc617", "README.txt") + blame, err := testrepo.Blame(ctx, "cfc3b2993f74726356887a5ec093de50486dc617", "README.txt") assert.Nil(t, err) // Assert representative commits diff --git a/repo_blob.go b/repo_blob.go index 4d9bfc24..906d18d5 100644 --- a/repo_blob.go +++ b/repo_blob.go @@ -4,34 +4,31 @@ package git -import "time" +import "context" // CatFileBlobOptions contains optional arguments for verifying the objects. // // Docs: https://git-scm.com/docs/git-cat-file#Documentation/git-cat-file.txt type CatFileBlobOptions struct { - // The timeout duration before giving up for each shell command execution. - // The default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CatFileBlob returns the blob corresponding to the given revision of the repository. -func (r *Repository) CatFileBlob(rev string, opts ...CatFileBlobOptions) (*Blob, error) { +func (r *Repository) CatFileBlob(ctx context.Context, rev string, opts ...CatFileBlobOptions) (*Blob, error) { var opt CatFileBlobOptions if len(opts) > 0 { opt = opts[0] } - rev, err := r.RevParse(rev, RevParseOptions{Timeout: opt.Timeout}) //nolint + // Type conversions work because all three option types share the same + // underlying structure (CommandOptions only). + rev, err := r.RevParse(ctx, rev, RevParseOptions(opt)) //nolint if err != nil { return nil, err } - typ, err := r.CatFileType(rev) + typ, err := r.CatFileType(ctx, rev, CatFileTypeOptions(opt)) if err != nil { return nil, err } diff --git a/repo_blob_test.go b/repo_blob_test.go index 7def8039..434a1969 100644 --- a/repo_blob_test.go +++ b/repo_blob_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -12,19 +13,21 @@ import ( ) func TestRepository_CatFileBlob(t *testing.T) { + ctx := context.Background() + t.Run("not a blob", func(t *testing.T) { - _, err := testrepo.CatFileBlob("007cb92318c7bd3b56908ea8c2e54370245562f8") + _, err := testrepo.CatFileBlob(ctx, "007cb92318c7bd3b56908ea8c2e54370245562f8") assert.Equal(t, ErrNotBlob, err) }) t.Run("get a blob, no full rev hash", func(t *testing.T) { - b, err := testrepo.CatFileBlob("021a") + b, err := testrepo.CatFileBlob(ctx, "021a") require.NoError(t, err) assert.True(t, b.IsBlob()) }) t.Run("get a blob", func(t *testing.T) { - b, err := testrepo.CatFileBlob("021a721a61a1de65865542c405796d1eb985f784") + b, err := testrepo.CatFileBlob(ctx, "021a721a61a1de65865542c405796d1eb985f784") require.NoError(t, err) assert.True(t, b.IsBlob()) }) diff --git a/repo_commit.go b/repo_commit.go index a8ee370c..b2425200 100644 --- a/repo_commit.go +++ b/repo_commit.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "errors" "strconv" "strings" @@ -69,11 +70,6 @@ loop: // // Docs: https://git-scm.com/docs/git-cat-file#Documentation/git-cat-file.txt-lttypegt type CatFileCommitOptions struct { - // The timeout duration before giving up for each shell command execution. - // The default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } @@ -81,7 +77,7 @@ type CatFileCommitOptions struct { // CatFileCommit returns the commit corresponding to the given revision of the // repository. The revision could be a commit ID or full refspec (e.g. // "refs/heads/master"). -func (r *Repository) CatFileCommit(rev string, opts ...CatFileCommitOptions) (*Commit, error) { +func (r *Repository) CatFileCommit(ctx context.Context, rev string, opts ...CatFileCommitOptions) (*Commit, error) { var opt CatFileCommitOptions if len(opts) > 0 { opt = opts[0] @@ -93,15 +89,15 @@ func (r *Repository) CatFileCommit(rev string, opts ...CatFileCommitOptions) (*C return cache.(*Commit), nil } - commitID, err := r.RevParse(rev, RevParseOptions{Timeout: opt.Timeout}) //nolint + commitID, err := r.RevParse(ctx, rev) //nolint if err != nil { return nil, err } - stdout, err := NewCommand("cat-file"). + stdout, err := NewCommand(ctx, "cat-file"). AddOptions(opt.CommandOptions). AddArgs("commit", commitID). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return nil, err } @@ -121,24 +117,21 @@ func (r *Repository) CatFileCommit(rev string, opts ...CatFileCommitOptions) (*C // // Docs: https://git-scm.com/docs/git-cat-file#Documentation/git-cat-file.txt--t type CatFileTypeOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CatFileType returns the object type of given revision of the repository. -func (r *Repository) CatFileType(rev string, opts ...CatFileTypeOptions) (ObjectType, error) { +func (r *Repository) CatFileType(ctx context.Context, rev string, opts ...CatFileTypeOptions) (ObjectType, error) { var opt CatFileTypeOptions if len(opts) > 0 { opt = opts[0] } - typ, err := NewCommand("cat-file"). + typ, err := NewCommand(ctx, "cat-file"). AddOptions(opt.CommandOptions). AddArgs("-t", rev). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return "", err } @@ -148,14 +141,14 @@ func (r *Repository) CatFileType(rev string, opts ...CatFileTypeOptions) (Object // BranchCommit returns the latest commit of given branch of the repository. The // branch must be given in short name e.g. "master". -func (r *Repository) BranchCommit(branch string, opts ...CatFileCommitOptions) (*Commit, error) { - return r.CatFileCommit(RefsHeads+branch, opts...) +func (r *Repository) BranchCommit(ctx context.Context, branch string, opts ...CatFileCommitOptions) (*Commit, error) { + return r.CatFileCommit(ctx, RefsHeads+branch, opts...) } // TagCommit returns the latest commit of given tag of the repository. The tag // must be given in short name e.g. "v1.0.0". -func (r *Repository) TagCommit(tag string, opts ...CatFileCommitOptions) (*Commit, error) { - return r.CatFileCommit(RefsTags+tag, opts...) +func (r *Repository) TagCommit(ctx context.Context, tag string, opts ...CatFileCommitOptions) (*Commit, error) { + return r.CatFileCommit(ctx, RefsTags+tag, opts...) } // LogOptions contains optional arguments for listing commits. @@ -174,9 +167,6 @@ type LogOptions struct { RegexpIgnoreCase bool // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } @@ -195,13 +185,13 @@ func escapePath(path string) string { // Log returns a list of commits in the state of given revision of the repository. // The returned list is in reverse chronological order. -func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { +func (r *Repository) Log(ctx context.Context, rev string, opts ...LogOptions) ([]*Commit, error) { var opt LogOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("log"). + cmd := NewCommand(ctx, "log"). AddOptions(opt.CommandOptions). AddArgs("--pretty=" + LogFormatHashOnly) if opt.MaxCount > 0 { @@ -224,11 +214,11 @@ func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { cmd.AddArgs(escapePath(opt.Path)) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil, err } - return r.parsePrettyFormatLogToList(opt.Timeout, stdout) + return r.parsePrettyFormatLogToList(ctx, stdout) } // CommitByRevisionOptions contains optional arguments for getting a commit. @@ -237,24 +227,20 @@ func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { type CommitByRevisionOptions struct { // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CommitByRevision returns a commit by given revision. -func (r *Repository) CommitByRevision(rev string, opts ...CommitByRevisionOptions) (*Commit, error) { +func (r *Repository) CommitByRevision(ctx context.Context, rev string, opts ...CommitByRevisionOptions) (*Commit, error) { var opt CommitByRevisionOptions if len(opts) > 0 { opt = opts[0] } - commits, err := r.Log(rev, LogOptions{ + commits, err := r.Log(ctx, rev, LogOptions{ MaxCount: 1, Path: opt.Path, - Timeout: opt.Timeout, CommandOptions: opt.CommandOptions, }) if err != nil { @@ -275,26 +261,22 @@ func (r *Repository) CommitByRevision(rev string, opts ...CommitByRevisionOption type CommitsByPageOptions struct { // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CommitsByPage returns a paginated list of commits in the state of given // revision. The pagination starts from the newest to the oldest commit. -func (r *Repository) CommitsByPage(rev string, page, size int, opts ...CommitsByPageOptions) ([]*Commit, error) { +func (r *Repository) CommitsByPage(ctx context.Context, rev string, page, size int, opts ...CommitsByPageOptions) ([]*Commit, error) { var opt CommitsByPageOptions if len(opts) > 0 { opt = opts[0] } - return r.Log(rev, LogOptions{ + return r.Log(ctx, rev, LogOptions{ MaxCount: size, Skip: (page - 1) * size, Path: opt.Path, - Timeout: opt.Timeout, CommandOptions: opt.CommandOptions, }) } @@ -307,27 +289,23 @@ type SearchCommitsOptions struct { MaxCount int // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // SearchCommits searches commit message with given pattern in the state of // given revision. The returned list is in reverse chronological order. -func (r *Repository) SearchCommits(rev, pattern string, opts ...SearchCommitsOptions) ([]*Commit, error) { +func (r *Repository) SearchCommits(ctx context.Context, rev, pattern string, opts ...SearchCommitsOptions) ([]*Commit, error) { var opt SearchCommitsOptions if len(opts) > 0 { opt = opts[0] } - return r.Log(rev, LogOptions{ + return r.Log(ctx, rev, LogOptions{ MaxCount: opt.MaxCount, GrepPattern: pattern, RegexpIgnoreCase: true, Path: opt.Path, - Timeout: opt.Timeout, CommandOptions: opt.CommandOptions, }) } @@ -339,25 +317,21 @@ func (r *Repository) SearchCommits(rev, pattern string, opts ...SearchCommitsOpt type CommitsSinceOptions struct { // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CommitsSince returns a list of commits since given time. The returned list is // in reverse chronological order. -func (r *Repository) CommitsSince(rev string, since time.Time, opts ...CommitsSinceOptions) ([]*Commit, error) { +func (r *Repository) CommitsSince(ctx context.Context, rev string, since time.Time, opts ...CommitsSinceOptions) ([]*Commit, error) { var opt CommitsSinceOptions if len(opts) > 0 { opt = opts[0] } - return r.Log(rev, LogOptions{ + return r.Log(ctx, rev, LogOptions{ Since: since, Path: opt.Path, - Timeout: opt.Timeout, CommandOptions: opt.CommandOptions, }) } @@ -370,22 +344,19 @@ type DiffNameOnlyOptions struct { NeedsMergeBase bool // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // DiffNameOnly returns a list of changed files between base and head revisions of the // repository. -func (r *Repository) DiffNameOnly(base, head string, opts ...DiffNameOnlyOptions) ([]string, error) { +func (r *Repository) DiffNameOnly(ctx context.Context, base, head string, opts ...DiffNameOnlyOptions) ([]string, error) { var opt DiffNameOnlyOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("diff"). + cmd := NewCommand(ctx, "diff"). AddOptions(opt.CommandOptions). AddArgs("--name-only") cmd.AddArgs("--end-of-options") @@ -399,7 +370,7 @@ func (r *Repository) DiffNameOnly(base, head string, opts ...DiffNameOnlyOptions cmd.AddArgs(escapePath(opt.Path)) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil, err } @@ -422,16 +393,13 @@ func (r *Repository) DiffNameOnly(base, head string, opts ...DiffNameOnlyOptions type RevListCountOptions struct { // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RevListCount returns number of total commits up to given refspec of the // repository. -func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions) (int64, error) { +func (r *Repository) RevListCount(ctx context.Context, refspecs []string, opts ...RevListCountOptions) (int64, error) { var opt RevListCountOptions if len(opts) > 0 { opt = opts[0] @@ -441,7 +409,7 @@ func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions return 0, errors.New("must have at least one refspec") } - cmd := NewCommand("rev-list"). + cmd := NewCommand(ctx, "rev-list"). AddOptions(opt.CommandOptions). AddArgs( "--count", @@ -453,7 +421,7 @@ func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions cmd.AddArgs(escapePath(opt.Path)) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return 0, err } @@ -467,16 +435,13 @@ func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions type RevListOptions struct { // The relative path of the repository. Path string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RevList returns a list of commits based on given refspecs in reverse // chronological order. -func (r *Repository) RevList(refspecs []string, opts ...RevListOptions) ([]*Commit, error) { +func (r *Repository) RevList(ctx context.Context, refspecs []string, opts ...RevListOptions) ([]*Commit, error) { var opt RevListOptions if len(opts) > 0 { opt = opts[0] @@ -486,7 +451,7 @@ func (r *Repository) RevList(refspecs []string, opts ...RevListOptions) ([]*Comm return nil, errors.New("must have at least one refspec") } - cmd := NewCommand("rev-list").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "rev-list").AddOptions(opt.CommandOptions) cmd.AddArgs("--end-of-options") cmd.AddArgs(refspecs...) cmd.AddArgs("--") @@ -494,11 +459,11 @@ func (r *Repository) RevList(refspecs []string, opts ...RevListOptions) ([]*Comm cmd.AddArgs(escapePath(opt.Path)) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil, err } - return r.parsePrettyFormatLogToList(opt.Timeout, bytes.TrimSpace(stdout)) + return r.parsePrettyFormatLogToList(ctx, bytes.TrimSpace(stdout)) } // LatestCommitTimeOptions contains optional arguments for getting the latest @@ -506,21 +471,18 @@ func (r *Repository) RevList(refspecs []string, opts ...RevListOptions) ([]*Comm type LatestCommitTimeOptions struct { // To get the latest commit time of the branch. When not set, it checks all branches. Branch string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // LatestCommitTime returns the time of latest commit of the repository. -func (r *Repository) LatestCommitTime(opts ...LatestCommitTimeOptions) (time.Time, error) { +func (r *Repository) LatestCommitTime(ctx context.Context, opts ...LatestCommitTimeOptions) (time.Time, error) { var opt LatestCommitTimeOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("for-each-ref"). + cmd := NewCommand(ctx, "for-each-ref"). AddOptions(opt.CommandOptions). AddArgs( "--count=1", @@ -531,7 +493,7 @@ func (r *Repository) LatestCommitTime(opts ...LatestCommitTimeOptions) (time.Tim cmd.AddArgs(RefsHeads + opt.Branch) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return time.Time{}, err } diff --git a/repo_commit_test.go b/repo_commit_test.go index 809b6ffc..c7108993 100644 --- a/repo_commit_test.go +++ b/repo_commit_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "errors" "testing" "time" @@ -38,13 +39,15 @@ func Test_escapePath(t *testing.T) { } func TestRepository_CatFileCommit(t *testing.T) { + ctx := context.Background() + t.Run("invalid revision", func(t *testing.T) { - c, err := testrepo.CatFileCommit("bad_revision") + c, err := testrepo.CatFileCommit(ctx, "bad_revision") assert.Equal(t, ErrRevisionNotExist, err) assert.Nil(t, c) }) - c, err := testrepo.CatFileCommit("d58e3ef9f123eea6857161c79275ee22b228f659") + c, err := testrepo.CatFileCommit(ctx, "d58e3ef9f123eea6857161c79275ee22b228f659") if err != nil { t.Fatal(err) } @@ -54,13 +57,15 @@ func TestRepository_CatFileCommit(t *testing.T) { } func TestRepository_BranchCommit(t *testing.T) { + ctx := context.Background() + t.Run("invalid branch", func(t *testing.T) { - c, err := testrepo.BranchCommit("refs/heads/release-1.0") + c, err := testrepo.BranchCommit(ctx, "refs/heads/release-1.0") assert.Equal(t, ErrRevisionNotExist, err) assert.Nil(t, c) }) - c, err := testrepo.BranchCommit("release-1.0") + c, err := testrepo.BranchCommit(ctx, "release-1.0") if err != nil { t.Fatal(err) } @@ -70,13 +75,15 @@ func TestRepository_BranchCommit(t *testing.T) { } func TestRepository_TagCommit(t *testing.T) { + ctx := context.Background() + t.Run("invalid branch", func(t *testing.T) { - c, err := testrepo.BranchCommit("refs/tags/v1.0.0") + c, err := testrepo.BranchCommit(ctx, "refs/tags/v1.0.0") assert.Equal(t, ErrRevisionNotExist, err) assert.Nil(t, c) }) - c, err := testrepo.BranchCommit("release-1.0") + c, err := testrepo.BranchCommit(ctx, "release-1.0") if err != nil { t.Fatal(err) } @@ -86,6 +93,7 @@ func TestRepository_TagCommit(t *testing.T) { } func TestRepository_Log(t *testing.T) { + ctx := context.Background() tests := []struct { rev string opt LogOptions @@ -111,7 +119,7 @@ func TestRepository_Log(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - commits, err := testrepo.Log(test.rev, test.opt) + commits, err := testrepo.Log(ctx, test.rev, test.opt) if err != nil { t.Fatal(err) } @@ -122,8 +130,10 @@ func TestRepository_Log(t *testing.T) { } func TestRepository_CommitByRevision(t *testing.T) { + ctx := context.Background() + t.Run("invalid revision", func(t *testing.T) { - c, err := testrepo.CommitByRevision("bad_revision") + c, err := testrepo.CommitByRevision(ctx, "bad_revision") assert.Equal(t, ErrRevisionNotExist, err) assert.Nil(t, c) }) @@ -140,7 +150,7 @@ func TestRepository_CommitByRevision(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - c, err := testrepo.CommitByRevision(test.rev, test.opt) + c, err := testrepo.CommitByRevision(ctx, test.rev, test.opt) if err != nil { t.Fatal(err) } @@ -151,6 +161,7 @@ func TestRepository_CommitByRevision(t *testing.T) { } func TestRepository_CommitsSince(t *testing.T) { + ctx := context.Background() tests := []struct { rev string since time.Time @@ -173,7 +184,7 @@ func TestRepository_CommitsSince(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - commits, err := testrepo.CommitsSince(test.rev, test.since, test.opt) + commits, err := testrepo.CommitsSince(ctx, test.rev, test.since, test.opt) if err != nil { t.Fatal(err) } @@ -184,6 +195,7 @@ func TestRepository_CommitsSince(t *testing.T) { } func TestRepository_DiffNameOnly(t *testing.T) { + ctx := context.Background() tests := []struct { base string head string @@ -223,7 +235,7 @@ func TestRepository_DiffNameOnly(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - files, err := testrepo.DiffNameOnly(test.base, test.head, test.opt) + files, err := testrepo.DiffNameOnly(ctx, test.base, test.head, test.opt) if err != nil { t.Fatal(err) } @@ -234,8 +246,10 @@ func TestRepository_DiffNameOnly(t *testing.T) { } func TestRepository_RevListCount(t *testing.T) { + ctx := context.Background() + t.Run("no refspecs", func(t *testing.T) { - count, err := testrepo.RevListCount([]string{}) + count, err := testrepo.RevListCount(ctx, []string{}) assert.Equal(t, errors.New("must have at least one refspec"), err) assert.Zero(t, count) }) @@ -275,7 +289,7 @@ func TestRepository_RevListCount(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - count, err := testrepo.RevListCount(test.refspecs, test.opt) + count, err := testrepo.RevListCount(ctx, test.refspecs, test.opt) if err != nil { t.Fatal(err) } @@ -286,8 +300,10 @@ func TestRepository_RevListCount(t *testing.T) { } func TestRepository_RevList(t *testing.T) { + ctx := context.Background() + t.Run("no refspecs", func(t *testing.T) { - commits, err := testrepo.RevList([]string{}) + commits, err := testrepo.RevList(ctx, []string{}) assert.Equal(t, errors.New("must have at least one refspec"), err) assert.Nil(t, commits) }) @@ -317,7 +333,7 @@ func TestRepository_RevList(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - commits, err := testrepo.RevList(test.refspecs, test.opt) + commits, err := testrepo.RevList(ctx, test.refspecs, test.opt) if err != nil { t.Fatal(err) } @@ -328,6 +344,7 @@ func TestRepository_RevList(t *testing.T) { } func TestRepository_LatestCommitTime(t *testing.T) { + ctx := context.Background() tests := []struct { opt LatestCommitTimeOptions expTime time.Time @@ -341,7 +358,7 @@ func TestRepository_LatestCommitTime(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - got, err := testrepo.LatestCommitTime(test.opt) + got, err := testrepo.LatestCommitTime(ctx, test.opt) if err != nil { t.Fatal(err) } diff --git a/repo_diff.go b/repo_diff.go index ffe9d6ca..0ea4fdf5 100644 --- a/repo_diff.go +++ b/repo_diff.go @@ -6,9 +6,9 @@ package git import ( "bytes" + "context" "fmt" "io" - "time" ) // DiffOptions contains optional arguments for parsing diff. @@ -18,28 +18,23 @@ type DiffOptions struct { // The commit ID to used for computing diff between a range of commits (base, // revision]. When not set, only computes diff for a single commit at revision. Base string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Diff returns a parsed diff object between given commits of the repository. -func (r *Repository) Diff(rev string, maxFiles, maxFileLines, maxLineChars int, opts ...DiffOptions) (*Diff, error) { +func (r *Repository) Diff(ctx context.Context, rev string, maxFiles, maxFileLines, maxLineChars int, opts ...DiffOptions) (*Diff, error) { var opt DiffOptions if len(opts) > 0 { opt = opts[0] } - commit, err := r.CatFileCommit(rev, CatFileCommitOptions{Timeout: opt.Timeout}) + commit, err := r.CatFileCommit(ctx, rev) if err != nil { return nil, err } - cmd := NewCommand() + cmd := NewCommand(ctx) if opt.Base == "" { // First commit of repository if commit.ParentsCount() == 0 { @@ -47,7 +42,7 @@ func (r *Repository) Diff(rev string, maxFiles, maxFileLines, maxLineChars int, AddOptions(opt.CommandOptions). AddArgs("--full-index", "--end-of-options", rev) } else { - c, err := commit.Parent(0) + c, err := commit.Parent(ctx, 0) if err != nil { return nil, err } @@ -66,7 +61,7 @@ func (r *Repository) Diff(rev string, maxFiles, maxFileLines, maxLineChars int, go StreamParseDiff(stdout, done, maxFiles, maxFileLines, maxLineChars) stderr := new(bytes.Buffer) - err = cmd.RunInDirPipelineWithTimeout(opt.Timeout, w, stderr, r.path) + err = cmd.RunInDirPipeline(w, stderr, r.path) _ = w.Close() // Close writer to exit parsing goroutine if err != nil { return nil, concatenateError(err, stderr.String()) @@ -88,27 +83,24 @@ const ( // // Docs: https://git-scm.com/docs/git-format-patch type RawDiffOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RawDiff dumps diff of repository in given revision directly to given // io.Writer. -func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, opts ...RawDiffOptions) error { +func (r *Repository) RawDiff(ctx context.Context, rev string, diffType RawDiffFormat, w io.Writer, opts ...RawDiffOptions) error { var opt RawDiffOptions if len(opts) > 0 { opt = opts[0] } - commit, err := r.CatFileCommit(rev, CatFileCommitOptions{Timeout: opt.Timeout}) //nolint + commit, err := r.CatFileCommit(ctx, rev) //nolint if err != nil { return err } - cmd := NewCommand() + cmd := NewCommand(ctx) switch diffType { case RawDiffNormal: if commit.ParentsCount() == 0 { @@ -116,7 +108,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op AddOptions(opt.CommandOptions). AddArgs("--full-index", "--end-of-options", rev) } else { - c, err := commit.Parent(0) + c, err := commit.Parent(ctx, 0) if err != nil { return err } @@ -130,7 +122,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op AddOptions(opt.CommandOptions). AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", "--root", "--end-of-options", rev) } else { - c, err := commit.Parent(0) + c, err := commit.Parent(ctx, 0) if err != nil { return err } @@ -143,7 +135,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op } stderr := new(bytes.Buffer) - if err = cmd.RunInDirPipelineWithTimeout(opt.Timeout, w, stderr, r.path); err != nil { + if err = cmd.RunInDirPipeline(w, stderr, r.path); err != nil { return concatenateError(err, stderr.String()) } return nil @@ -151,23 +143,20 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op // DiffBinaryOptions contains optional arguments for producing binary patch. type DiffBinaryOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // DiffBinary returns binary patch between base and head revisions that could be // used for git-apply. -func (r *Repository) DiffBinary(base, head string, opts ...DiffBinaryOptions) ([]byte, error) { +func (r *Repository) DiffBinary(ctx context.Context, base, head string, opts ...DiffBinaryOptions) ([]byte, error) { var opt DiffBinaryOptions if len(opts) > 0 { opt = opts[0] } - return NewCommand("diff"). + return NewCommand(ctx, "diff"). AddOptions(opt.CommandOptions). AddArgs("--full-index", "--binary", base, head). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) } diff --git a/repo_diff_test.go b/repo_diff_test.go index ec15c133..96a619e9 100644 --- a/repo_diff_test.go +++ b/repo_diff_test.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "errors" "strings" "testing" @@ -14,6 +15,7 @@ import ( ) func TestRepository_Diff(t *testing.T) { + ctx := context.Background() tests := []struct { rev string maxFiles int @@ -289,7 +291,7 @@ func TestRepository_Diff(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - diff, err := testrepo.Diff(test.rev, test.maxFiles, test.maxFileLines, test.maxLineChars, test.opt) + diff, err := testrepo.Diff(ctx, test.rev, test.maxFiles, test.maxFileLines, test.maxLineChars, test.opt) if err != nil { t.Fatal(err) } @@ -300,13 +302,15 @@ func TestRepository_Diff(t *testing.T) { } func TestRepository_RawDiff(t *testing.T) { + ctx := context.Background() + t.Run("invalid revision", func(t *testing.T) { - err := testrepo.RawDiff("bad_revision", "bad_diff_type", nil) + err := testrepo.RawDiff(ctx, "bad_revision", "bad_diff_type", nil) assert.Equal(t, ErrRevisionNotExist, err) }) t.Run("invalid diffType", func(t *testing.T) { - err := testrepo.RawDiff("978fb7f6388b49b532fbef8b856681cfa6fcaa0a", "bad_diff_type", nil) + err := testrepo.RawDiff(ctx, "978fb7f6388b49b532fbef8b856681cfa6fcaa0a", "bad_diff_type", nil) assert.Equal(t, errors.New("invalid diffType: bad_diff_type"), err) }) @@ -461,7 +465,7 @@ index 0000000000000000000000000000000000000000..51680791956b43effdb2f16bccd2b475 for _, test := range tests { t.Run("", func(t *testing.T) { buf := new(bytes.Buffer) - err := testrepo.RawDiff(test.rev, test.diffType, buf, test.opt) + err := testrepo.RawDiff(ctx, test.rev, test.diffType, buf, test.opt) if err != nil { t.Fatal(err) } @@ -479,6 +483,7 @@ index 0000000000000000000000000000000000000000..51680791956b43effdb2f16bccd2b475 } func TestRepository_DiffBinary(t *testing.T) { + ctx := context.Background() tests := []struct { base string head string @@ -509,7 +514,7 @@ index 0000000000000000000000000000000000000000..6b08f76a5313fa3d26859515b30aa17a } for _, test := range tests { t.Run("", func(t *testing.T) { - p, err := testrepo.DiffBinary(test.base, test.head, test.opt) + p, err := testrepo.DiffBinary(ctx, test.base, test.head, test.opt) if err != nil { t.Fatal(err) } diff --git a/repo_grep.go b/repo_grep.go index f6394bce..dd05a64f 100644 --- a/repo_grep.go +++ b/repo_grep.go @@ -5,10 +5,10 @@ package git import ( + "context" "fmt" "strconv" "strings" - "time" ) // GrepOptions contains optional arguments for grep search over repository files. @@ -25,11 +25,6 @@ type GrepOptions struct { WordRegexp bool // Whether use extended regular expressions. ExtendedRegexp bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } @@ -74,7 +69,7 @@ func parseGrepLine(line string) (*GrepResult, error) { } // Grep returns the results of a grep search in the repository. -func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { +func (r *Repository) Grep(ctx context.Context, pattern string, opts ...GrepOptions) []*GrepResult { var opt GrepOptions if len(opts) > 0 { opt = opts[0] @@ -83,7 +78,7 @@ func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { opt.Tree = "HEAD" } - cmd := NewCommand("grep"). + cmd := NewCommand(ctx, "grep"). AddOptions(opt.CommandOptions). // Display full-name, line number and column number AddArgs("--full-name", "--line-number", "--column") @@ -105,7 +100,7 @@ func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { cmd.AddArgs("--", opt.Pathspec) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil } diff --git a/repo_grep_test.go b/repo_grep_test.go index 2c0be390..1402c71d 100644 --- a/repo_grep_test.go +++ b/repo_grep_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "runtime" "testing" @@ -12,6 +13,7 @@ import ( ) func TestRepository_Grep_Simple(t *testing.T) { + ctx := context.Background() want := []*GrepResult{ { Tree: "HEAD", @@ -51,11 +53,12 @@ func TestRepository_Grep_Simple(t *testing.T) { Text: `println "${programmingPoints} plus 3 bonus points is ${sum(programmingPoints, 3)}"`, }, } - got := testrepo.Grep("programmingPoints") + got := testrepo.Grep(ctx, "programmingPoints") assert.Equal(t, want, got) } func TestRepository_Grep_IgnoreCase(t *testing.T) { + ctx := context.Background() want := []*GrepResult{ { Tree: "HEAD", @@ -107,11 +110,12 @@ func TestRepository_Grep_IgnoreCase(t *testing.T) { Text: ` System.out.println( "Hello World!" );`, }, } - got := testrepo.Grep("Hello", GrepOptions{IgnoreCase: true}) + got := testrepo.Grep(ctx, "Hello", GrepOptions{IgnoreCase: true}) assert.Equal(t, want, got) } func TestRepository_Grep_ExtendedRegexp(t *testing.T) { + ctx := context.Background() if runtime.GOOS == "darwin" { t.Skip("Skipping testing on macOS") return @@ -125,11 +129,12 @@ func TestRepository_Grep_ExtendedRegexp(t *testing.T) { Text: ` System.out.println( "Hello World!" );`, }, } - got := testrepo.Grep(`Hello\sW\w+`, GrepOptions{ExtendedRegexp: true}) + got := testrepo.Grep(ctx, `Hello\sW\w+`, GrepOptions{ExtendedRegexp: true}) assert.Equal(t, want, got) } func TestRepository_Grep_WordRegexp(t *testing.T) { + ctx := context.Background() want := []*GrepResult{ { Tree: "HEAD", @@ -139,6 +144,6 @@ func TestRepository_Grep_WordRegexp(t *testing.T) { Text: ` * Hello world!`, }, } - got := testrepo.Grep("world", GrepOptions{WordRegexp: true}) + got := testrepo.Grep(ctx, "world", GrepOptions{WordRegexp: true}) assert.Equal(t, want, got) } diff --git a/repo_pull.go b/repo_pull.go index 2b42229e..0c9e7775 100644 --- a/repo_pull.go +++ b/repo_pull.go @@ -5,38 +5,33 @@ package git import ( + "context" "strings" - "time" ) // MergeBaseOptions contains optional arguments for getting merge base. // // Docs: https://git-scm.com/docs/git-merge-base type MergeBaseOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // MergeBase returns merge base between base and head revisions of the // repository. -func (r *Repository) MergeBase(base, head string, opts ...MergeBaseOptions) (string, error) { +func (r *Repository) MergeBase(ctx context.Context, base, head string, opts ...MergeBaseOptions) (string, error) { var opt MergeBaseOptions if len(opts) > 0 { opt = opts[0] } - stdout, err := NewCommand("merge-base"). + stdout, err := NewCommand(ctx, "merge-base"). AddOptions(opt.CommandOptions). AddArgs( "--end-of-options", base, head, - ).RunInDirWithTimeout(opt.Timeout, r.path) + ).RunInDir(r.path) if err != nil { if strings.Contains(err.Error(), "exit status 1") { return "", ErrNoMergeBase diff --git a/repo_pull_test.go b/repo_pull_test.go index 3ec9dcb7..ff231863 100644 --- a/repo_pull_test.go +++ b/repo_pull_test.go @@ -5,14 +5,17 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestRepository_MergeBase(t *testing.T) { + ctx := context.Background() + t.Run("no merge base", func(t *testing.T) { - mb, err := testrepo.MergeBase("0eedd79eba4394bbef888c804e899731644367fe", "bad_revision") + mb, err := testrepo.MergeBase(ctx, "0eedd79eba4394bbef888c804e899731644367fe", "bad_revision") assert.Equal(t, ErrNoMergeBase, err) assert.Empty(t, mb) }) @@ -36,7 +39,7 @@ func TestRepository_MergeBase(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - mb, err := testrepo.MergeBase(test.base, test.head, test.opt) + mb, err := testrepo.MergeBase(ctx, test.base, test.head, test.opt) if err != nil { t.Fatal(err) } diff --git a/repo_reference.go b/repo_reference.go index 1b90e7b8..eec27159 100644 --- a/repo_reference.go +++ b/repo_reference.go @@ -5,9 +5,9 @@ package git import ( + "context" "errors" "strings" - "time" ) const ( @@ -37,11 +37,6 @@ type Reference struct { // // Docs: https://git-scm.com/docs/git-show-ref#Documentation/git-show-ref.txt---verify type ShowRefVerifyOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } @@ -50,14 +45,14 @@ var ErrReferenceNotExist = errors.New("reference does not exist") // ShowRefVerify returns the commit ID of given reference (e.g. // "refs/heads/master") if it exists in the repository. -func (r *Repository) ShowRefVerify(ref string, opts ...ShowRefVerifyOptions) (string, error) { +func (r *Repository) ShowRefVerify(ctx context.Context, ref string, opts ...ShowRefVerifyOptions) (string, error) { var opt ShowRefVerifyOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("show-ref", "--verify", "--end-of-options", ref).AddOptions(opt.CommandOptions) - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + cmd := NewCommand(ctx, "show-ref", "--verify", "--end-of-options", ref).AddOptions(opt.CommandOptions) + stdout, err := cmd.RunInDir(r.path) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { return "", ErrReferenceNotExist @@ -69,33 +64,33 @@ func (r *Repository) ShowRefVerify(ref string, opts ...ShowRefVerifyOptions) (st // BranchCommitID returns the commit ID of given branch if it exists in the // repository. The branch must be given in short name e.g. "master". -func (r *Repository) BranchCommitID(branch string, opts ...ShowRefVerifyOptions) (string, error) { - return r.ShowRefVerify(RefsHeads+branch, opts...) +func (r *Repository) BranchCommitID(ctx context.Context, branch string, opts ...ShowRefVerifyOptions) (string, error) { + return r.ShowRefVerify(ctx, RefsHeads+branch, opts...) } // TagCommitID returns the commit ID of given tag if it exists in the // repository. The tag must be given in short name e.g. "v1.0.0". -func (r *Repository) TagCommitID(tag string, opts ...ShowRefVerifyOptions) (string, error) { - return r.ShowRefVerify(RefsTags+tag, opts...) +func (r *Repository) TagCommitID(ctx context.Context, tag string, opts ...ShowRefVerifyOptions) (string, error) { + return r.ShowRefVerify(ctx, RefsTags+tag, opts...) } // HasReference returns true if given reference exists in the repository. The // reference must be given in full refspec, e.g. "refs/heads/master". -func (r *Repository) HasReference(ref string, opts ...ShowRefVerifyOptions) bool { - _, err := r.ShowRefVerify(ref, opts...) +func (r *Repository) HasReference(ctx context.Context, ref string, opts ...ShowRefVerifyOptions) bool { + _, err := r.ShowRefVerify(ctx, ref, opts...) return err == nil } // HasBranch returns true if given branch exists in the repository. The branch // must be given in short name e.g. "master". -func (r *Repository) HasBranch(branch string, opts ...ShowRefVerifyOptions) bool { - return r.HasReference(RefsHeads+branch, opts...) +func (r *Repository) HasBranch(ctx context.Context, branch string, opts ...ShowRefVerifyOptions) bool { + return r.HasReference(ctx, RefsHeads+branch, opts...) } // HasTag returns true if given tag exists in the repository. The tag must be // given in short name e.g. "v1.0.0". -func (r *Repository) HasTag(tag string, opts ...ShowRefVerifyOptions) bool { - return r.HasReference(RefsTags+tag, opts...) +func (r *Repository) HasTag(ctx context.Context, tag string, opts ...ShowRefVerifyOptions) bool { + return r.HasReference(ctx, RefsTags+tag, opts...) } // SymbolicRefOptions contains optional arguments for get and set symbolic ref. @@ -105,11 +100,6 @@ type SymbolicRefOptions struct { // The name of the reference, e.g. "refs/heads/master". When set, it will be // used to update the symbolic ref. Ref string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } @@ -117,13 +107,13 @@ type SymbolicRefOptions struct { // SymbolicRef returns the reference name (e.g. "refs/heads/master") pointed by // the symbolic ref. It returns an empty string and nil error when doing set // operation. -func (r *Repository) SymbolicRef(opts ...SymbolicRefOptions) (string, error) { +func (r *Repository) SymbolicRef(ctx context.Context, opts ...SymbolicRefOptions) (string, error) { var opt SymbolicRefOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("symbolic-ref").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "symbolic-ref").AddOptions(opt.CommandOptions) if opt.Name == "" { opt.Name = "HEAD" } @@ -132,7 +122,7 @@ func (r *Repository) SymbolicRef(opts ...SymbolicRefOptions) (string, error) { cmd.AddArgs(opt.Ref) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return "", err } @@ -149,23 +139,18 @@ type ShowRefOptions struct { Tags bool // The list of patterns to filter results. Patterns []string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // ShowRef returns a list of references in the repository. -func (r *Repository) ShowRef(opts ...ShowRefOptions) ([]*Reference, error) { +func (r *Repository) ShowRef(ctx context.Context, opts ...ShowRefOptions) ([]*Reference, error) { var opt ShowRefOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("show-ref").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "show-ref").AddOptions(opt.CommandOptions) if opt.Heads { cmd.AddArgs("--heads") } @@ -177,7 +162,7 @@ func (r *Repository) ShowRef(opts ...ShowRefOptions) ([]*Reference, error) { cmd.AddArgs(opt.Patterns...) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil, err } @@ -198,8 +183,8 @@ func (r *Repository) ShowRef(opts ...ShowRefOptions) ([]*Reference, error) { } // Branches returns a list of all branches in the repository. -func (r *Repository) Branches() ([]string, error) { - heads, err := r.ShowRef(ShowRefOptions{Heads: true}) +func (r *Repository) Branches(ctx context.Context) ([]string, error) { + heads, err := r.ShowRef(ctx, ShowRefOptions{Heads: true}) if err != nil { return nil, err } @@ -217,28 +202,23 @@ func (r *Repository) Branches() ([]string, error) { type DeleteBranchOptions struct { // Indicates whether to force delete the branch. Force bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // DeleteBranch deletes the branch from the repository. -func (r *Repository) DeleteBranch(name string, opts ...DeleteBranchOptions) error { +func (r *Repository) DeleteBranch(ctx context.Context, name string, opts ...DeleteBranchOptions) error { var opt DeleteBranchOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("branch").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "branch").AddOptions(opt.CommandOptions) if opt.Force { cmd.AddArgs("-D") } else { cmd.AddArgs("-d") } - _, err := cmd.AddArgs("--end-of-options", name).RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.AddArgs("--end-of-options", name).RunInDir(r.path) return err } diff --git a/repo_reference_test.go b/repo_reference_test.go index d0e90e17..3299ddf2 100644 --- a/repo_reference_test.go +++ b/repo_reference_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "strconv" "testing" "time" @@ -38,13 +39,15 @@ func TestRefShortName(t *testing.T) { } func TestRepository_ShowRefVerify(t *testing.T) { - t.Run("reference does not exsit", func(t *testing.T) { - rev, err := testrepo.ShowRefVerify("bad_reference") + ctx := context.Background() + + t.Run("reference does not exist", func(t *testing.T) { + rev, err := testrepo.ShowRefVerify(ctx, "bad_reference") assert.NotNil(t, err) assert.Empty(t, rev) }) - rev, err := testrepo.ShowRefVerify("refs/heads/release-1.0") + rev, err := testrepo.ShowRefVerify(ctx, "refs/heads/release-1.0") if err != nil { t.Fatal(err) } @@ -53,13 +56,15 @@ func TestRepository_ShowRefVerify(t *testing.T) { } func TestRepository_BranchCommitID(t *testing.T) { - t.Run("branch does not exsit", func(t *testing.T) { - rev, err := testrepo.BranchCommitID("bad_branch") + ctx := context.Background() + + t.Run("branch does not exist", func(t *testing.T) { + rev, err := testrepo.BranchCommitID(ctx, "bad_branch") assert.NotNil(t, err) assert.Empty(t, rev) }) - rev, err := testrepo.BranchCommitID("release-1.0") + rev, err := testrepo.BranchCommitID(ctx, "release-1.0") if err != nil { t.Fatal(err) } @@ -68,13 +73,15 @@ func TestRepository_BranchCommitID(t *testing.T) { } func TestRepository_TagCommitID(t *testing.T) { - t.Run("tag does not exsit", func(t *testing.T) { - rev, err := testrepo.TagCommitID("bad_tag") + ctx := context.Background() + + t.Run("tag does not exist", func(t *testing.T) { + rev, err := testrepo.TagCommitID(ctx, "bad_tag") assert.NotNil(t, err) assert.Empty(t, rev) }) - rev, err := testrepo.TagCommitID("v1.0.0") + rev, err := testrepo.TagCommitID(ctx, "v1.0.0") if err != nil { t.Fatal(err) } @@ -83,6 +90,7 @@ func TestRepository_TagCommitID(t *testing.T) { } func TestRepository_HasReference(t *testing.T) { + ctx := context.Background() tests := []struct { ref string opt ShowRefVerifyOptions @@ -103,12 +111,13 @@ func TestRepository_HasReference(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - assert.Equal(t, test.expVal, testrepo.HasReference(test.ref, test.opt)) + assert.Equal(t, test.expVal, testrepo.HasReference(ctx, test.ref, test.opt)) }) } } func TestRepository_HasBranch(t *testing.T) { + ctx := context.Background() tests := []struct { ref string opt ShowRefVerifyOptions @@ -125,12 +134,13 @@ func TestRepository_HasBranch(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - assert.Equal(t, test.expVal, testrepo.HasBranch(test.ref, test.opt)) + assert.Equal(t, test.expVal, testrepo.HasBranch(ctx, test.ref, test.opt)) }) } } func TestRepository_HasTag(t *testing.T) { + ctx := context.Background() tests := []struct { ref string opt ShowRefVerifyOptions @@ -147,12 +157,13 @@ func TestRepository_HasTag(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - assert.Equal(t, test.expVal, testrepo.HasTag(test.ref, test.opt)) + assert.Equal(t, test.expVal, testrepo.HasTag(ctx, test.ref, test.opt)) }) } } func TestRepository_SymbolicRef(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -160,14 +171,14 @@ func TestRepository_SymbolicRef(t *testing.T) { defer cleanup() // Get HEAD - ref, err := r.SymbolicRef() + ref, err := r.SymbolicRef(ctx) if err != nil { t.Fatal(err) } assert.Equal(t, RefsHeads+"master", ref) // Set a symbolic reference - _, err = r.SymbolicRef(SymbolicRefOptions{ + _, err = r.SymbolicRef(ctx, SymbolicRefOptions{ Name: "TEST_REF", Ref: RefsHeads + "develop", }) @@ -176,7 +187,7 @@ func TestRepository_SymbolicRef(t *testing.T) { } // Get the symbolic reference we just set - ref, err = r.SymbolicRef(SymbolicRefOptions{ + ref, err = r.SymbolicRef(ctx, SymbolicRefOptions{ Name: "TEST_REF", }) if err != nil { @@ -186,6 +197,7 @@ func TestRepository_SymbolicRef(t *testing.T) { } func TestRepository_ShowRef(t *testing.T) { + ctx := context.Background() tests := []struct { opt ShowRefOptions expRefs []*Reference @@ -216,7 +228,7 @@ func TestRepository_ShowRef(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - refs, err := testrepo.ShowRef(test.opt) + refs, err := testrepo.ShowRef(ctx, test.opt) if err != nil { t.Fatal(err) } @@ -227,12 +239,13 @@ func TestRepository_ShowRef(t *testing.T) { } func TestRepository_Branches(t *testing.T) { + ctx := context.Background() expBranches := map[string]bool{ "master": true, "develop": true, "release-1.0": true, } - branches, err := testrepo.Branches() + branches, err := testrepo.Branches(ctx) if err != nil { t.Fatal(err) } @@ -246,6 +259,7 @@ func TestRepository_Branches(t *testing.T) { } func TestRepository_DeleteBranch(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -269,26 +283,26 @@ func TestRepository_DeleteBranch(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { branch := strconv.Itoa(int(time.Now().UnixNano())) - err := r.Checkout(branch, CheckoutOptions{ + err := r.Checkout(ctx, branch, CheckoutOptions{ BaseBranch: "master", }) if err != nil { t.Fatal(err) } - assert.True(t, r.HasReference(RefsHeads+branch)) + assert.True(t, r.HasReference(ctx, RefsHeads+branch)) - err = r.Checkout("master") + err = r.Checkout(ctx, "master") if err != nil { t.Fatal(err) } - err = r.DeleteBranch(branch, test.opt) + err = r.DeleteBranch(ctx, branch, test.opt) if err != nil { t.Fatal(err) } - assert.False(t, r.HasReference(RefsHeads+branch)) + assert.False(t, r.HasReference(ctx, RefsHeads+branch)) }) } } diff --git a/repo_remote.go b/repo_remote.go index 9ff23c51..3f13755d 100644 --- a/repo_remote.go +++ b/repo_remote.go @@ -6,8 +6,8 @@ package git import ( "bytes" + "context" "strings" - "time" ) // LsRemoteOptions contains arguments for listing references in a remote @@ -23,23 +23,18 @@ type LsRemoteOptions struct { Refs bool // The list of patterns to filter results. Patterns []string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // LsRemote returns a list references in the remote repository. -func LsRemote(url string, opts ...LsRemoteOptions) ([]*Reference, error) { +func LsRemote(ctx context.Context, url string, opts ...LsRemoteOptions) ([]*Reference, error) { var opt LsRemoteOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("ls-remote", "--quiet").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "ls-remote", "--quiet").AddOptions(opt.CommandOptions) if opt.Heads { cmd.AddArgs("--heads") } @@ -54,7 +49,7 @@ func LsRemote(url string, opts ...LsRemoteOptions) ([]*Reference, error) { cmd.AddArgs(opt.Patterns...) } - stdout, err := cmd.RunWithTimeout(opt.Timeout) + stdout, err := cmd.Run() if err != nil { return nil, err } @@ -75,12 +70,11 @@ func LsRemote(url string, opts ...LsRemoteOptions) ([]*Reference, error) { return refs, nil } -// IsURLAccessible returns true if given remote URL is accessible via Git within -// given timeout. -func IsURLAccessible(timeout time.Duration, url string) bool { - _, err := LsRemote(url, LsRemoteOptions{ +// IsURLAccessible returns true if given remote URL is accessible via Git. The +// caller should use context.WithTimeout to control the timeout. +func IsURLAccessible(ctx context.Context, url string) bool { + _, err := LsRemote(ctx, url, LsRemoteOptions{ Patterns: []string{"HEAD"}, - Timeout: timeout, }) return err == nil } @@ -94,23 +88,18 @@ type RemoteAddOptions struct { Fetch bool // Indicates whether to add remote as mirror with --mirror=fetch. MirrorFetch bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteAdd adds a new remote to the repository. -func (r *Repository) RemoteAdd(name, url string, opts ...RemoteAddOptions) error { +func (r *Repository) RemoteAdd(ctx context.Context, name, url string, opts ...RemoteAddOptions) error { var opt RemoteAddOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("remote", "add").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "remote", "add").AddOptions(opt.CommandOptions) if opt.Fetch { cmd.AddArgs("-f") } @@ -118,7 +107,7 @@ func (r *Repository) RemoteAdd(name, url string, opts ...RemoteAddOptions) error cmd.AddArgs("--mirror=fetch") } - _, err := cmd.AddArgs("--end-of-options", name, url).RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.AddArgs("--end-of-options", name, url).RunInDir(r.path) return err } @@ -127,26 +116,21 @@ func (r *Repository) RemoteAdd(name, url string, opts ...RemoteAddOptions) error // // Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emremoveem type RemoteRemoveOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteRemove removes a remote from the repository. -func (r *Repository) RemoteRemove(name string, opts ...RemoteRemoveOptions) error { +func (r *Repository) RemoteRemove(ctx context.Context, name string, opts ...RemoteRemoveOptions) error { var opt RemoteRemoveOptions if len(opts) > 0 { opt = opts[0] } - _, err := NewCommand("remote", "remove"). + _, err := NewCommand(ctx, "remote", "remove"). AddOptions(opt.CommandOptions). AddArgs("--end-of-options", name). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { // the error status may differ from git clients if strings.Contains(err.Error(), "error: No such remote") || @@ -162,25 +146,20 @@ func (r *Repository) RemoteRemove(name string, opts ...RemoteRemoveOptions) erro // / // Docs: https://git-scm.com/docs/git-remote#_commands type RemotesOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Remotes lists remotes of the repository. -func (r *Repository) Remotes(opts ...RemotesOptions) ([]string, error) { +func (r *Repository) Remotes(ctx context.Context, opts ...RemotesOptions) ([]string, error) { var opt RemotesOptions if len(opts) > 0 { opt = opts[0] } - stdout, err := NewCommand("remote"). + stdout, err := NewCommand(ctx, "remote"). AddOptions(opt.CommandOptions). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return nil, err } @@ -198,23 +177,18 @@ type RemoteGetURLOptions struct { // Indicates whether to get all URLs, including lists that are not part of main // URLs. This option is independent of the Push option. All bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteGetURL retrieves URL(s) of a remote of the repository. -func (r *Repository) RemoteGetURL(name string, opts ...RemoteGetURLOptions) ([]string, error) { +func (r *Repository) RemoteGetURL(ctx context.Context, name string, opts ...RemoteGetURLOptions) ([]string, error) { var opt RemoteGetURLOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("remote", "get-url").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "remote", "get-url").AddOptions(opt.CommandOptions) if opt.Push { cmd.AddArgs("--push") } @@ -222,7 +196,7 @@ func (r *Repository) RemoteGetURL(name string, opts ...RemoteGetURLOptions) ([]s cmd.AddArgs("--all") } - stdout, err := cmd.AddArgs("--end-of-options", name).RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.AddArgs("--end-of-options", name).RunInDir(r.path) if err != nil { return nil, err } @@ -238,24 +212,19 @@ type RemoteSetURLOptions struct { Push bool // The regex to match existing URLs to replace (instead of first). Regex string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteSetURL sets the first URL of the remote with given name of the // repository. -func (r *Repository) RemoteSetURL(name, newurl string, opts ...RemoteSetURLOptions) error { +func (r *Repository) RemoteSetURL(ctx context.Context, name, newurl string, opts ...RemoteSetURLOptions) error { var opt RemoteSetURLOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("remote", "set-url").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "remote", "set-url").AddOptions(opt.CommandOptions) if opt.Push { cmd.AddArgs("--push") } @@ -266,7 +235,7 @@ func (r *Repository) RemoteSetURL(name, newurl string, opts ...RemoteSetURLOptio cmd.AddArgs(opt.Regex) } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) if err != nil { if strings.Contains(err.Error(), "No such URL found") { return ErrURLNotExist @@ -285,24 +254,19 @@ func (r *Repository) RemoteSetURL(name, newurl string, opts ...RemoteSetURLOptio type RemoteSetURLAddOptions struct { // Indicates whether to get push URLs instead of fetch URLs. Push bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteSetURLAdd appends an URL to the remote with given name of the // repository. Use RemoteSetURL to overwrite the URL(s) instead. -func (r *Repository) RemoteSetURLAdd(name, newurl string, opts ...RemoteSetURLAddOptions) error { +func (r *Repository) RemoteSetURLAdd(ctx context.Context, name, newurl string, opts ...RemoteSetURLAddOptions) error { var opt RemoteSetURLAddOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("remote", "set-url"). + cmd := NewCommand(ctx, "remote", "set-url"). AddOptions(opt.CommandOptions). AddArgs("--add") if opt.Push { @@ -311,7 +275,7 @@ func (r *Repository) RemoteSetURLAdd(name, newurl string, opts ...RemoteSetURLAd cmd.AddArgs("--end-of-options", name, newurl) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") { return ErrNotDeleteNonPushURLs } @@ -325,24 +289,19 @@ func (r *Repository) RemoteSetURLAdd(name, newurl string, opts ...RemoteSetURLAd type RemoteSetURLDeleteOptions struct { // Indicates whether to get push URLs instead of fetch URLs. Push bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // RemoteSetURLDelete deletes all URLs matching regex of the remote with given // name of the repository. -func (r *Repository) RemoteSetURLDelete(name, regex string, opts ...RemoteSetURLDeleteOptions) error { +func (r *Repository) RemoteSetURLDelete(ctx context.Context, name, regex string, opts ...RemoteSetURLDeleteOptions) error { var opt RemoteSetURLDeleteOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("remote", "set-url"). + cmd := NewCommand(ctx, "remote", "set-url"). AddOptions(opt.CommandOptions). AddArgs("--delete") if opt.Push { @@ -351,7 +310,7 @@ func (r *Repository) RemoteSetURLDelete(name, regex string, opts ...RemoteSetURL cmd.AddArgs("--end-of-options", name, regex) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") { return ErrNotDeleteNonPushURLs } diff --git a/repo_remote_test.go b/repo_remote_test.go index 51ee3ab0..b75e9a58 100644 --- a/repo_remote_test.go +++ b/repo_remote_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "os" "testing" @@ -12,6 +13,7 @@ import ( ) func TestLsRemote(t *testing.T) { + ctx := context.Background() tests := []struct { url string opt LsRemoteOptions @@ -57,7 +59,7 @@ func TestLsRemote(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - refs, err := LsRemote(test.url, test.opt) + refs, err := LsRemote(ctx, test.url, test.opt) if err != nil { t.Fatal(err) } @@ -68,6 +70,7 @@ func TestLsRemote(t *testing.T) { } func TestIsURLAccessible(t *testing.T) { + ctx := context.Background() tests := []struct { url string expVal bool @@ -82,18 +85,19 @@ func TestIsURLAccessible(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - assert.Equal(t, test.expVal, IsURLAccessible(DefaultTimeout, test.url)) + assert.Equal(t, test.expVal, IsURLAccessible(ctx, test.url)) }) } } func TestRepository_RemoteAdd(t *testing.T) { + ctx := context.Background() path := tempPath() defer func() { _ = os.RemoveAll(path) }() - err := Init(path, InitOptions{ + err := Init(ctx, path, InitOptions{ Bare: true, }) if err != nil { @@ -106,7 +110,7 @@ func TestRepository_RemoteAdd(t *testing.T) { } // Add testrepo as the mirror remote and fetch right away - err = r.RemoteAdd("origin", testrepo.Path(), RemoteAddOptions{ + err = r.RemoteAdd(ctx, "origin", testrepo.Path(), RemoteAddOptions{ Fetch: true, MirrorFetch: true, }) @@ -115,24 +119,26 @@ func TestRepository_RemoteAdd(t *testing.T) { } // Check a non-default branch: release-1.0 - assert.True(t, r.HasReference(RefsHeads+"release-1.0")) + assert.True(t, r.HasReference(ctx, RefsHeads+"release-1.0")) } func TestRepository_RemoteRemove(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - err = r.RemoteRemove("origin", RemoteRemoveOptions{}) + err = r.RemoteRemove(ctx, "origin", RemoteRemoveOptions{}) assert.Nil(t, err) - err = r.RemoteRemove("origin", RemoteRemoveOptions{}) + err = r.RemoteRemove(ctx, "origin", RemoteRemoveOptions{}) assert.Equal(t, ErrRemoteNotExist, err) } func TestRepository_Remotes(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -140,69 +146,70 @@ func TestRepository_Remotes(t *testing.T) { defer cleanup() // 1 remote - remotes, err := r.Remotes() + remotes, err := r.Remotes(ctx) assert.Nil(t, err) assert.Equal(t, []string{"origin"}, remotes) // 2 remotes - err = r.RemoteAdd("t", "t") + err = r.RemoteAdd(ctx, "t", "t") assert.Nil(t, err) - remotes, err = r.Remotes() + remotes, err = r.Remotes(ctx) assert.Nil(t, err) assert.Equal(t, []string{"origin", "t"}, remotes) assert.Len(t, remotes, 2) // 0 remotes - err = r.RemoteRemove("t") + err = r.RemoteRemove(ctx, "t") assert.Nil(t, err) - err = r.RemoteRemove("origin") + err = r.RemoteRemove(ctx, "origin") assert.Nil(t, err) - remotes, err = r.Remotes() + remotes, err = r.Remotes(ctx) assert.Nil(t, err) assert.Equal(t, []string{}, remotes) assert.Len(t, remotes, 0) } func TestRepository_RemoteURLFamily(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - err = r.RemoteSetURLDelete("origin", ".*") + err = r.RemoteSetURLDelete(ctx, "origin", ".*") assert.Equal(t, ErrNotDeleteNonPushURLs, err) - err = r.RemoteSetURL("notexist", "t") + err = r.RemoteSetURL(ctx, "notexist", "t") assert.Equal(t, ErrRemoteNotExist, err) - err = r.RemoteSetURL("notexist", "t", RemoteSetURLOptions{Regex: "t"}) + err = r.RemoteSetURL(ctx, "notexist", "t", RemoteSetURLOptions{Regex: "t"}) assert.Equal(t, ErrRemoteNotExist, err) // Default origin URL is not easily testable - err = r.RemoteSetURL("origin", "t") + err = r.RemoteSetURL(ctx, "origin", "t") assert.Nil(t, err) - urls, err := r.RemoteGetURL("origin") + urls, err := r.RemoteGetURL(ctx, "origin") assert.Nil(t, err) assert.Equal(t, []string{"t"}, urls) - err = r.RemoteSetURLAdd("origin", "e") + err = r.RemoteSetURLAdd(ctx, "origin", "e") assert.Nil(t, err) - urls, err = r.RemoteGetURL("origin", RemoteGetURLOptions{All: true}) + urls, err = r.RemoteGetURL(ctx, "origin", RemoteGetURLOptions{All: true}) assert.Nil(t, err) assert.Equal(t, []string{"t", "e"}, urls) - err = r.RemoteSetURL("origin", "s", RemoteSetURLOptions{Regex: "e"}) + err = r.RemoteSetURL(ctx, "origin", "s", RemoteSetURLOptions{Regex: "e"}) assert.Nil(t, err) - urls, err = r.RemoteGetURL("origin", RemoteGetURLOptions{All: true}) + urls, err = r.RemoteGetURL(ctx, "origin", RemoteGetURLOptions{All: true}) assert.Nil(t, err) assert.Equal(t, []string{"t", "s"}, urls) - err = r.RemoteSetURLDelete("origin", "t") + err = r.RemoteSetURLDelete(ctx, "origin", "t") assert.Nil(t, err) - urls, err = r.RemoteGetURL("origin", RemoteGetURLOptions{All: true}) + urls, err = r.RemoteGetURL(ctx, "origin", RemoteGetURLOptions{All: true}) assert.Nil(t, err) assert.Equal(t, []string{"s"}, urls) } diff --git a/repo_tag.go b/repo_tag.go index 26142b27..df3b883a 100644 --- a/repo_tag.go +++ b/repo_tag.go @@ -6,9 +6,9 @@ package git import ( "bytes" + "context" "fmt" "strings" - "time" ) // parseTag parses tag information from the (uncompressed) raw data of the tag @@ -52,7 +52,7 @@ l: } // getTag returns a tag by given SHA1 hash. -func (r *Repository) getTag(timeout time.Duration, id *SHA1) (*Tag, error) { +func (r *Repository) getTag(ctx context.Context, id *SHA1) (*Tag, error) { t, ok := r.cachedTags.Get(id.String()) if ok { log("Cached tag hit: %s", id) @@ -60,7 +60,7 @@ func (r *Repository) getTag(timeout time.Duration, id *SHA1) (*Tag, error) { } // Check tag type - typ, err := r.CatFileType(id.String(), CatFileTypeOptions{Timeout: timeout}) + typ, err := r.CatFileType(ctx, id.String()) if err != nil { return nil, err } @@ -76,7 +76,7 @@ func (r *Repository) getTag(timeout time.Duration, id *SHA1) (*Tag, error) { } case ObjectTag: // Tag is an annotation - data, err := NewCommand("cat-file", "-p", id.String()).RunInDir(r.path) + data, err := NewCommand(ctx, "cat-file", "-p", id.String()).RunInDir(r.path) if err != nil { return nil, err } @@ -100,27 +100,21 @@ func (r *Repository) getTag(timeout time.Duration, id *SHA1) (*Tag, error) { // // Docs: https://git-scm.com/docs/git-cat-file type TagOptions struct { - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Tag returns a Git tag by given name, e.g. "v1.0.0". -func (r *Repository) Tag(name string, opts ...TagOptions) (*Tag, error) { +func (r *Repository) Tag(ctx context.Context, name string, opts ...TagOptions) (*Tag, error) { var opt TagOptions if len(opts) > 0 { opt = opts[0] } - refsepc := RefsTags + name - refs, err := r.ShowRef(ShowRefOptions{ + refspec := RefsTags + name + refs, err := r.ShowRef(ctx, ShowRefOptions{ Tags: true, - Patterns: []string{refsepc}, - Timeout: opt.Timeout, + Patterns: []string{refspec}, CommandOptions: opt.CommandOptions, }) if err != nil { @@ -134,11 +128,11 @@ func (r *Repository) Tag(name string, opts ...TagOptions) (*Tag, error) { return nil, err } - tag, err := r.getTag(opt.Timeout, id) + tag, err := r.getTag(ctx, id) if err != nil { return nil, err } - tag.refspec = refsepc + tag.refspec = refspec return tag, nil } @@ -150,23 +144,18 @@ type TagsOptions struct { SortKey string // Pattern filters tags matching the specified pattern. Pattern string - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // Tags returns a list of tags of the repository. -func (r *Repository) Tags(opts ...TagsOptions) ([]string, error) { +func (r *Repository) Tags(ctx context.Context, opts ...TagsOptions) ([]string, error) { var opt TagsOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("tag", "--list").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "tag", "--list").AddOptions(opt.CommandOptions) if opt.SortKey != "" { cmd.AddArgs("--sort=" + opt.SortKey) @@ -178,7 +167,7 @@ func (r *Repository) Tags(opts ...TagsOptions) ([]string, error) { cmd.AddArgs(opt.Pattern) } - stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + stdout, err := cmd.RunInDir(r.path) if err != nil { return nil, err } @@ -199,23 +188,18 @@ type CreateTagOptions struct { Message string // Author is the author of the tag. It is ignored when tag is not annotated. Author *Signature - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // CreateTag creates a new tag on given revision. -func (r *Repository) CreateTag(name, rev string, opts ...CreateTagOptions) error { +func (r *Repository) CreateTag(ctx context.Context, name, rev string, opts ...CreateTagOptions) error { var opt CreateTagOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("tag").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "tag").AddOptions(opt.CommandOptions) if opt.Annotated { cmd.AddArgs("-a", name) cmd.AddArgs("--message", opt.Message) @@ -229,7 +213,7 @@ func (r *Repository) CreateTag(name, rev string, opts ...CreateTagOptions) error cmd.AddArgs(rev) - _, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + _, err := cmd.RunInDir(r.path) return err } @@ -237,24 +221,19 @@ func (r *Repository) CreateTag(name, rev string, opts ...CreateTagOptions) error // // Docs: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---delete type DeleteTagOptions struct { - // The timeout duration before giving up for each shell command execution. - // The default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // DeleteTag deletes a tag from the repository. -func (r *Repository) DeleteTag(name string, opts ...DeleteTagOptions) error { +func (r *Repository) DeleteTag(ctx context.Context, name string, opts ...DeleteTagOptions) error { var opt DeleteTagOptions if len(opts) > 0 { opt = opts[0] } - _, err := NewCommand("tag", "--delete", "--end-of-options", name). + _, err := NewCommand(ctx, "tag", "--delete", "--end-of-options", name). AddOptions(opt.CommandOptions). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) return err } diff --git a/repo_tag_test.go b/repo_tag_test.go index bd5bc79b..263f0be2 100644 --- a/repo_tag_test.go +++ b/repo_tag_test.go @@ -5,12 +5,14 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestRepository_Tag(t *testing.T) { + ctx := context.Background() tests := []struct { name string opt TagOptions @@ -36,7 +38,7 @@ func TestRepository_Tag(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - tag, err := testrepo.Tag(test.name, test.opt) + tag, err := testrepo.Tag(ctx, test.name, test.opt) if err != nil { t.Fatal(err) } @@ -50,8 +52,9 @@ func TestRepository_Tag(t *testing.T) { } func TestRepository_Tags(t *testing.T) { + ctx := context.Background() // Make sure it does not blow up - tags, err := testrepo.Tags(TagsOptions{}) + tags, err := testrepo.Tags(ctx, TagsOptions{}) if err != nil { t.Fatal(err) } @@ -59,22 +62,23 @@ func TestRepository_Tags(t *testing.T) { } func TestRepository_Tags_VersionSort(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - err = r.CreateTag("v3.0.0", "master") + err = r.CreateTag(ctx, "v3.0.0", "master") if err != nil { t.Fatal(err) } - err = r.CreateTag("v2.999.0", "master") + err = r.CreateTag(ctx, "v2.999.0", "master") if err != nil { t.Fatal(err) } - tags, err := r.Tags(TagsOptions{ + tags, err := r.Tags(ctx, TagsOptions{ SortKey: "-version:refname", Pattern: "v*", }) @@ -90,32 +94,34 @@ func TestRepository_Tags_VersionSort(t *testing.T) { } func TestRepository_CreateTag(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - assert.False(t, r.HasReference(RefsTags+"v2.0.0")) + assert.False(t, r.HasReference(ctx, RefsTags+"v2.0.0")) - err = r.CreateTag("v2.0.0", "master", CreateTagOptions{}) + err = r.CreateTag(ctx, "v2.0.0", "master", CreateTagOptions{}) if err != nil { t.Fatal(err) } - assert.True(t, r.HasReference(RefsTags+"v2.0.0")) + assert.True(t, r.HasReference(ctx, RefsTags+"v2.0.0")) } func TestRepository_CreateAnnotatedTag(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - assert.False(t, r.HasReference(RefsTags+"v2.0.0")) + assert.False(t, r.HasReference(ctx, RefsTags+"v2.0.0")) - err = r.CreateTag("v2.0.0", "master", CreateTagOptions{ + err = r.CreateTag(ctx, "v2.0.0", "master", CreateTagOptions{ Annotated: true, Author: &Signature{ Name: "alice", @@ -126,9 +132,9 @@ func TestRepository_CreateAnnotatedTag(t *testing.T) { t.Fatal(err) } - assert.True(t, r.HasReference(RefsTags+"v2.0.0")) + assert.True(t, r.HasReference(ctx, RefsTags+"v2.0.0")) - tag, err := r.Tag("v2.0.0") + tag, err := r.Tag(ctx, "v2.0.0") if err != nil { t.Fatal(err) } @@ -139,18 +145,19 @@ func TestRepository_CreateAnnotatedTag(t *testing.T) { } func TestRepository_DeleteTag(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) } defer cleanup() - assert.True(t, r.HasReference(RefsTags+"v1.0.0")) + assert.True(t, r.HasReference(ctx, RefsTags+"v1.0.0")) - err = r.DeleteTag("v1.0.0", DeleteTagOptions{}) + err = r.DeleteTag(ctx, "v1.0.0", DeleteTagOptions{}) if err != nil { t.Fatal(err) } - assert.False(t, r.HasReference(RefsTags+"v1.0.0")) + assert.False(t, r.HasReference(ctx, RefsTags+"v1.0.0")) } diff --git a/repo_test.go b/repo_test.go index 42ae99c9..1a3f17de 100644 --- a/repo_test.go +++ b/repo_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "os" "path/filepath" "testing" @@ -22,6 +23,7 @@ func TestRepository(t *testing.T) { } func TestInit(t *testing.T) { + ctx := context.Background() tests := []struct { opt InitOptions }{ @@ -42,7 +44,7 @@ func TestInit(t *testing.T) { _ = os.RemoveAll(path) }() - if err := Init(path, test.opt); err != nil { + if err := Init(ctx, path, test.opt); err != nil { t.Fatal(err) } }) @@ -58,6 +60,7 @@ func TestOpen(t *testing.T) { } func TestClone(t *testing.T) { + ctx := context.Background() tests := []struct { opt CloneOptions }{ @@ -96,7 +99,7 @@ func TestClone(t *testing.T) { _ = os.RemoveAll(path) }() - if err := Clone(testrepo.Path(), path, test.opt); err != nil { + if err := Clone(ctx, testrepo.Path(), path, test.opt); err != nil { t.Fatal(err) } }) @@ -104,6 +107,7 @@ func TestClone(t *testing.T) { } func setupTempRepo() (_ *Repository, cleanup func(), err error) { + ctx := context.Background() path := tempPath() cleanup = func() { _ = os.RemoveAll(path) @@ -114,7 +118,7 @@ func setupTempRepo() (_ *Repository, cleanup func(), err error) { } }() - if err = Clone(testrepo.Path(), path); err != nil { + if err = Clone(ctx, testrepo.Path(), path); err != nil { return nil, cleanup, err } @@ -126,6 +130,7 @@ func setupTempRepo() (_ *Repository, cleanup func(), err error) { } func TestRepository_Fetch(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -147,7 +152,7 @@ func TestRepository_Fetch(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Fetch(test.opt); err != nil { + if err := r.Fetch(ctx, test.opt); err != nil { t.Fatal(err) } }) @@ -155,6 +160,7 @@ func TestRepository_Fetch(t *testing.T) { } func TestRepository_Pull(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -187,7 +193,7 @@ func TestRepository_Pull(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Pull(test.opt); err != nil { + if err := r.Pull(ctx, test.opt); err != nil { t.Fatal(err) } }) @@ -195,6 +201,7 @@ func TestRepository_Pull(t *testing.T) { } func TestRepository_Push(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -215,7 +222,7 @@ func TestRepository_Push(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Push(test.remote, test.branch, test.opt); err != nil { + if err := r.Push(ctx, test.remote, test.branch, test.opt); err != nil { t.Fatal(err) } }) @@ -223,6 +230,7 @@ func TestRepository_Push(t *testing.T) { } func TestRepository_Checkout(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -247,7 +255,7 @@ func TestRepository_Checkout(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Checkout(test.branch, test.opt); err != nil { + if err := r.Checkout(ctx, test.branch, test.opt); err != nil { t.Fatal(err) } }) @@ -255,6 +263,7 @@ func TestRepository_Checkout(t *testing.T) { } func TestRepository_Reset(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -275,7 +284,7 @@ func TestRepository_Reset(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Reset(test.rev, test.opt); err != nil { + if err := r.Reset(ctx, test.rev, test.opt); err != nil { t.Fatal(err) } }) @@ -283,6 +292,7 @@ func TestRepository_Reset(t *testing.T) { } func TestRepository_Move(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -303,7 +313,7 @@ func TestRepository_Move(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { // Make sure it does not blow up - if err := r.Move(test.src, test.dst, test.opt); err != nil { + if err := r.Move(ctx, test.src, test.dst, test.opt); err != nil { t.Fatal(err) } }) @@ -311,6 +321,7 @@ func TestRepository_Move(t *testing.T) { } func TestRepository_Add(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -325,7 +336,7 @@ func TestRepository_Add(t *testing.T) { } // Make sure it does not blow up - if err := r.Add(AddOptions{ + if err := r.Add(ctx, AddOptions{ All: true, Pathspecs: []string{"TESTFILE"}, }); err != nil { @@ -334,6 +345,7 @@ func TestRepository_Add(t *testing.T) { } func TestRepository_Commit(t *testing.T) { + ctx := context.Background() r, cleanup, err := setupTempRepo() if err != nil { t.Fatal(err) @@ -351,7 +363,7 @@ func TestRepository_Commit(t *testing.T) { message := "Add a file" t.Run("nothing to commit", func(t *testing.T) { - if err = r.Commit(committer, message, CommitOptions{ + if err = r.Commit(ctx, committer, message, CommitOptions{ Author: author, }); err != nil { t.Fatal(err) @@ -366,19 +378,19 @@ func TestRepository_Commit(t *testing.T) { t.Fatal(err) } - if err := r.Add(AddOptions{ + if err := r.Add(ctx, AddOptions{ All: true, }); err != nil { t.Fatal(err) } // Make sure it does not blow up - if err = r.Commit(committer, message); err != nil { + if err = r.Commit(ctx, committer, message); err != nil { t.Fatal(err) } // Verify the result - c, err := r.CatFileCommit("master") + c, err := r.CatFileCommit(ctx, "master") if err != nil { t.Fatal(err) } @@ -398,19 +410,19 @@ func TestRepository_Commit(t *testing.T) { t.Fatal(err) } - if err := r.Add(AddOptions{ + if err := r.Add(ctx, AddOptions{ All: true, }); err != nil { t.Fatal(err) } // Make sure it does not blow up - if err = r.Commit(committer, message, CommitOptions{Author: author}); err != nil { + if err = r.Commit(ctx, committer, message, CommitOptions{Author: author}); err != nil { t.Fatal(err) } // Verify the result - c, err := r.CatFileCommit("master") + c, err := r.CatFileCommit(ctx, "master") if err != nil { t.Fatal(err) } @@ -424,6 +436,7 @@ func TestRepository_Commit(t *testing.T) { } func TestRepository_RevParse(t *testing.T) { + ctx := context.Background() tests := []struct { rev string expID string @@ -463,7 +476,7 @@ func TestRepository_RevParse(t *testing.T) { } for _, test := range tests { t.Run("", func(t *testing.T) { - id, err := testrepo.RevParse(test.rev) + id, err := testrepo.RevParse(ctx, test.rev) assert.Equal(t, test.expErr, err) assert.Equal(t, test.expID, id) }) @@ -471,16 +484,18 @@ func TestRepository_RevParse(t *testing.T) { } func TestRepository_CountObjects(t *testing.T) { + ctx := context.Background() // Make sure it does not blow up - _, err := testrepo.CountObjects(CountObjectsOptions{}) + _, err := testrepo.CountObjects(ctx, CountObjectsOptions{}) if err != nil { t.Fatal(err) } } func TestRepository_Fsck(t *testing.T) { + ctx := context.Background() // Make sure it does not blow up - err := testrepo.Fsck(FsckOptions{}) + err := testrepo.Fsck(ctx, FsckOptions{}) if err != nil { t.Fatal(err) } diff --git a/repo_tree.go b/repo_tree.go index fe434d41..8fb2e663 100644 --- a/repo_tree.go +++ b/repo_tree.go @@ -6,8 +6,8 @@ package git import ( "bytes" + "context" "fmt" - "time" ) // UnescapeChars reverses escaped characters in quoted output from Git. @@ -104,17 +104,12 @@ type LsTreeOptions struct { // Verbatim outputs filenames unquoted using the -z flag. This avoids issues // with special characters in filenames that would otherwise be quoted by Git. Verbatim bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - // - // Deprecated: Use CommandOptions.Timeout instead. - Timeout time.Duration // The additional options to be passed to the underlying Git. CommandOptions } // LsTree returns the tree object in the repository by given tree ID. -func (r *Repository) LsTree(treeID string, opts ...LsTreeOptions) (*Tree, error) { +func (r *Repository) LsTree(ctx context.Context, treeID string, opts ...LsTreeOptions) (*Tree, error) { var opt LsTreeOptions if len(opts) > 0 { opt = opts[0] @@ -127,7 +122,7 @@ func (r *Repository) LsTree(treeID string, opts ...LsTreeOptions) (*Tree, error) } var err error - treeID, err = r.RevParse(treeID, RevParseOptions{Timeout: opt.Timeout}) //nolint + treeID, err = r.RevParse(ctx, treeID) //nolint if err != nil { return nil, err } @@ -136,14 +131,14 @@ func (r *Repository) LsTree(treeID string, opts ...LsTreeOptions) (*Tree, error) repo: r, } - cmd := NewCommand("ls-tree") + cmd := NewCommand(ctx, "ls-tree") if opt.Verbatim { cmd.AddArgs("-z") } stdout, err := cmd. AddOptions(opt.CommandOptions). AddArgs(treeID). - RunInDirWithTimeout(opt.Timeout, r.path) + RunInDir(r.path) if err != nil { return nil, err } diff --git a/repo_tree_test.go b/repo_tree_test.go index 51965f6c..1c361f2a 100644 --- a/repo_tree_test.go +++ b/repo_tree_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "os" "path/filepath" "runtime" @@ -55,6 +56,7 @@ func TestUnescapeChars(t *testing.T) { } func TestRepository_LsTree(t *testing.T) { + ctx := context.Background() if runtime.GOOS == "windows" { t.Skip(`Windows does not allow '"' in filenames`) } @@ -62,7 +64,7 @@ func TestRepository_LsTree(t *testing.T) { path := tempPath() defer os.RemoveAll(path) - err := Init(path) + err := Init(ctx, path) require.NoError(t, err) specialName := `Test "Wiki" Page.md` @@ -72,23 +74,23 @@ func TestRepository_LsTree(t *testing.T) { repo, err := Open(path) require.NoError(t, err) - err = repo.Add(AddOptions{All: true}) + err = repo.Add(ctx, AddOptions{All: true}) require.NoError(t, err) - err = repo.Commit(&Signature{Name: "test", Email: "test@test.com"}, "initial commit") + err = repo.Commit(ctx, &Signature{Name: "test", Email: "test@test.com"}, "initial commit") require.NoError(t, err) - commit, err := repo.CatFileCommit("HEAD") + commit, err := repo.CatFileCommit(ctx, "HEAD") require.NoError(t, err) // Without Verbatim, Git quotes and escapes the filename. - entries, err := commit.Entries() + entries, err := commit.Entries(ctx) require.NoError(t, err) require.Len(t, entries, 1) assert.Equal(t, specialName, entries[0].Name()) // With Verbatim, Git outputs the filename as-is. - entries, err = commit.Entries(LsTreeOptions{Verbatim: true}) + entries, err = commit.Entries(ctx, LsTreeOptions{Verbatim: true}) require.NoError(t, err) require.Len(t, entries, 1) assert.Equal(t, specialName, entries[0].Name()) diff --git a/server.go b/server.go index 0c2d275e..1b4d2356 100755 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ package git import ( + "context" "time" ) @@ -15,25 +16,22 @@ import ( type UpdateServerInfoOptions struct { // Indicates whether to overwrite the existing server info. Force bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // UpdateServerInfo updates the auxiliary info file on the server side for the // repository in given path. -func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) error { +func UpdateServerInfo(ctx context.Context, path string, opts ...UpdateServerInfoOptions) error { var opt UpdateServerInfoOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("update-server-info").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "update-server-info").AddOptions(opt.CommandOptions) if opt.Force { cmd.AddArgs("--force") } - _, err := cmd.RunInDirWithTimeout(opt.Timeout, path) + _, err := cmd.RunInDir(path) return err } @@ -46,20 +44,17 @@ type ReceivePackOptions struct { Quiet bool // Indicates whether to generate the "info/refs" used by the "git http-backend". HTTPBackendInfoRefs bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // ReceivePack receives what is pushed into the repository in given path. -func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) { +func ReceivePack(ctx context.Context, path string, opts ...ReceivePackOptions) ([]byte, error) { var opt ReceivePackOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("receive-pack").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "receive-pack").AddOptions(opt.CommandOptions) if opt.Quiet { cmd.AddArgs("--quiet") } @@ -67,7 +62,7 @@ func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) { cmd.AddArgs("--http-backend-info-refs") } cmd.AddArgs(".") - return cmd.RunInDirWithTimeout(opt.Timeout, path) + return cmd.RunInDir(path) } // UploadPackOptions contains optional arguments for sending the packfile to the @@ -82,32 +77,33 @@ type UploadPackOptions struct { Strict bool // Indicates whether to generate the "info/refs" used by the "git http-backend". HTTPBackendInfoRefs bool - // The timeout duration before giving up for each shell command execution. The - // default timeout duration will be used when not supplied. - Timeout time.Duration + // The git-level inactivity timeout passed to git-upload-pack's --timeout flag. + // This is separate from the command execution timeout which is controlled via + // context.Context. + InactivityTimeout time.Duration // The additional options to be passed to the underlying git. CommandOptions } // UploadPack sends the packfile to the client for the repository in given path. -func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) { +func UploadPack(ctx context.Context, path string, opts ...UploadPackOptions) ([]byte, error) { var opt UploadPackOptions if len(opts) > 0 { opt = opts[0] } - cmd := NewCommand("upload-pack").AddOptions(opt.CommandOptions) + cmd := NewCommand(ctx, "upload-pack").AddOptions(opt.CommandOptions) if opt.StatelessRPC { cmd.AddArgs("--stateless-rpc") } if opt.Strict { cmd.AddArgs("--strict") } - if opt.Timeout > 0 { - cmd.AddArgs("--timeout", opt.Timeout.String()) + if opt.InactivityTimeout > 0 { + cmd.AddArgs("--timeout", opt.InactivityTimeout.String()) } if opt.HTTPBackendInfoRefs { cmd.AddArgs("--http-backend-info-refs") } cmd.AddArgs(".") - return cmd.RunInDirWithTimeout(opt.Timeout, path) + return cmd.RunInDir(path) } diff --git a/server_test.go b/server_test.go index b8d95dc2..2d343dd5 100755 --- a/server_test.go +++ b/server_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "os" "path/filepath" "testing" @@ -14,22 +15,25 @@ import ( ) func TestUpdateServerInfo(t *testing.T) { + ctx := context.Background() err := os.RemoveAll(filepath.Join(repoPath, "info")) require.NoError(t, err) - err = UpdateServerInfo(repoPath, UpdateServerInfoOptions{Force: true}) + err = UpdateServerInfo(ctx, repoPath, UpdateServerInfoOptions{Force: true}) require.NoError(t, err) assert.True(t, isFile(filepath.Join(repoPath, "info", "refs"))) } func TestReceivePack(t *testing.T) { - got, err := ReceivePack(repoPath, ReceivePackOptions{HTTPBackendInfoRefs: true}) + ctx := context.Background() + got, err := ReceivePack(ctx, repoPath, ReceivePackOptions{HTTPBackendInfoRefs: true}) require.NoError(t, err) const contains = "report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta object-format=sha1 agent=git/" assert.Contains(t, string(got), contains) } func TestUploadPack(t *testing.T) { - got, err := UploadPack(repoPath, + ctx := context.Background() + got, err := UploadPack(ctx, repoPath, UploadPackOptions{ StatelessRPC: true, Strict: true, diff --git a/tag.go b/tag.go index e26cb1b3..137eb039 100644 --- a/tag.go +++ b/tag.go @@ -4,6 +4,8 @@ package git +import "context" + // Tag contains information of a Git tag. type Tag struct { typ ObjectType @@ -47,6 +49,6 @@ func (t *Tag) Message() string { } // Commit returns the underlying commit of the tag. -func (t *Tag) Commit(opts ...CatFileCommitOptions) (*Commit, error) { - return t.repo.CatFileCommit(t.commitID.String(), opts...) +func (t *Tag) Commit(ctx context.Context, opts ...CatFileCommitOptions) (*Commit, error) { + return t.repo.CatFileCommit(ctx, t.commitID.String(), opts...) } diff --git a/tag_test.go b/tag_test.go index fdd63cc9..83341e75 100644 --- a/tag_test.go +++ b/tag_test.go @@ -5,13 +5,15 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestTag(t *testing.T) { - tag, err := testrepo.Tag("v1.1.0") + ctx := context.Background() + tag, err := testrepo.Tag(ctx, "v1.1.0") if err != nil { t.Fatal(err) } @@ -31,12 +33,13 @@ func TestTag(t *testing.T) { } func TestTag_Commit(t *testing.T) { - tag, err := testrepo.Tag("v1.1.0") + ctx := context.Background() + tag, err := testrepo.Tag(ctx, "v1.1.0") if err != nil { t.Fatal(err) } - c, err := tag.Commit() + c, err := tag.Commit(ctx) if err != nil { t.Fatal(err) } diff --git a/tree.go b/tree.go index ee16e370..998b65f3 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package git import ( + "context" "strings" "sync" ) @@ -16,13 +17,13 @@ type Tree struct { repo *Repository - entries Entries - entriesOnce sync.Once - entriesErr error + entries Entries + entriesMu sync.Mutex + entriesSet bool } // Subtree returns a subtree by given subpath of the tree. -func (t *Tree) Subtree(subpath string, opts ...LsTreeOptions) (*Tree, error) { +func (t *Tree) Subtree(ctx context.Context, subpath string, opts ...LsTreeOptions) (*Tree, error) { if len(subpath) == 0 { return t, nil } @@ -35,7 +36,7 @@ func (t *Tree) Subtree(subpath string, opts ...LsTreeOptions) (*Tree, error) { e *TreeEntry ) for _, name := range paths { - e, err = p.TreeEntry(name, opts...) + e, err = p.TreeEntry(ctx, name, opts...) if err != nil { return nil, err } @@ -50,20 +51,21 @@ func (t *Tree) Subtree(subpath string, opts ...LsTreeOptions) (*Tree, error) { return g, nil } -// Entries returns all entries of the tree. -func (t *Tree) Entries(opts ...LsTreeOptions) (Entries, error) { - t.entriesOnce.Do(func() { - if t.entries != nil { - return - } +// Entries returns all entries of the tree. Successful results are cached; +// failed attempts are not cached, allowing retries with a fresh context. +func (t *Tree) Entries(ctx context.Context, opts ...LsTreeOptions) (Entries, error) { + t.entriesMu.Lock() + defer t.entriesMu.Unlock() - var tt *Tree - tt, t.entriesErr = t.repo.LsTree(t.id.String(), opts...) - if t.entriesErr != nil { - return - } - t.entries = tt.entries - }) + if t.entriesSet { + return t.entries, nil + } - return t.entries, t.entriesErr + tt, err := t.repo.LsTree(ctx, t.id.String(), opts...) + if err != nil { + return nil, err + } + t.entries = tt.entries + t.entriesSet = true + return t.entries, nil } diff --git a/tree_blob.go b/tree_blob.go index 57fea178..020ee3c5 100644 --- a/tree_blob.go +++ b/tree_blob.go @@ -5,12 +5,13 @@ package git import ( + "context" "path" "strings" ) // TreeEntry returns the TreeEntry by given subpath of the tree. -func (t *Tree) TreeEntry(subpath string, opts ...LsTreeOptions) (*TreeEntry, error) { +func (t *Tree) TreeEntry(ctx context.Context, subpath string, opts ...LsTreeOptions) (*TreeEntry, error) { if len(subpath) == 0 { return &TreeEntry{ id: t.id, @@ -26,7 +27,7 @@ func (t *Tree) TreeEntry(subpath string, opts ...LsTreeOptions) (*TreeEntry, err for i, name := range paths { // Reached end of the loop if i == len(paths)-1 { - entries, err := tree.Entries(opts...) + entries, err := tree.Entries(ctx, opts...) if err != nil { return nil, err } @@ -37,7 +38,7 @@ func (t *Tree) TreeEntry(subpath string, opts ...LsTreeOptions) (*TreeEntry, err } } } else { - tree, err = tree.Subtree(name, opts...) + tree, err = tree.Subtree(ctx, name, opts...) if err != nil { return nil, err } @@ -47,8 +48,8 @@ func (t *Tree) TreeEntry(subpath string, opts ...LsTreeOptions) (*TreeEntry, err } // Blob returns the blob object by given subpath of the tree. -func (t *Tree) Blob(subpath string, opts ...LsTreeOptions) (*Blob, error) { - e, err := t.TreeEntry(subpath, opts...) +func (t *Tree) Blob(ctx context.Context, subpath string, opts ...LsTreeOptions) (*Blob, error) { + e, err := t.TreeEntry(ctx, subpath, opts...) if err != nil { return nil, err } @@ -61,8 +62,8 @@ func (t *Tree) Blob(subpath string, opts ...LsTreeOptions) (*Blob, error) { } // BlobByIndex returns blob object by given index. -func (t *Tree) BlobByIndex(index string) (*Blob, error) { - typ, err := t.repo.CatFileType(index) +func (t *Tree) BlobByIndex(ctx context.Context, index string) (*Blob, error) { + typ, err := t.repo.CatFileType(ctx, index) if err != nil { return nil, err } @@ -71,7 +72,7 @@ func (t *Tree) BlobByIndex(index string) (*Blob, error) { return nil, ErrNotBlob } - id, err := t.repo.RevParse(index) + id, err := t.repo.RevParse(ctx, index) if err != nil { return nil, err } diff --git a/tree_blob_test.go b/tree_blob_test.go index 6138be5b..3af1fc59 100644 --- a/tree_blob_test.go +++ b/tree_blob_test.go @@ -5,18 +5,20 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" ) func TestTree_TreeEntry(t *testing.T) { - tree, err := testrepo.LsTree("master") + ctx := context.Background() + tree, err := testrepo.LsTree(ctx, "master") if err != nil { t.Fatal(err) } - e, err := tree.TreeEntry("") + e, err := tree.TreeEntry(ctx, "") if err != nil { t.Fatal(err) } @@ -27,18 +29,19 @@ func TestTree_TreeEntry(t *testing.T) { } func TestTree_Blob(t *testing.T) { - tree, err := testrepo.LsTree("d58e3ef9f123eea6857161c79275ee22b228f659") + ctx := context.Background() + tree, err := testrepo.LsTree(ctx, "d58e3ef9f123eea6857161c79275ee22b228f659") if err != nil { t.Fatal(err) } t.Run("not a blob", func(t *testing.T) { - _, err := tree.Blob("src") + _, err := tree.Blob(ctx, "src") assert.Equal(t, ErrNotBlob, err) }) t.Run("get a blob", func(t *testing.T) { - b, err := tree.Blob("README.txt") + b, err := tree.Blob(ctx, "README.txt") if err != nil { t.Fatal(err) } @@ -47,7 +50,7 @@ func TestTree_Blob(t *testing.T) { }) t.Run("get an executable as blob", func(t *testing.T) { - b, err := tree.Blob("run.sh") + b, err := tree.Blob(ctx, "run.sh") if err != nil { t.Fatal(err) } diff --git a/tree_entry.go b/tree_entry.go index 8fdf251a..30f99641 100644 --- a/tree_entry.go +++ b/tree_entry.go @@ -5,6 +5,7 @@ package git import ( + "context" "fmt" "path" "runtime" @@ -13,7 +14,6 @@ import ( "strings" "sync" "sync/atomic" - "time" ) // EntryMode is the unix file mode of a tree entry. @@ -37,8 +37,9 @@ type TreeEntry struct { parent *Tree - size int64 - sizeOnce sync.Once + size int64 + sizeMu sync.Mutex + sizeSet bool } // Mode returns the entry mode if the tree entry. @@ -86,20 +87,23 @@ func (e *TreeEntry) Name() string { return e.name } -// Size returns the size of thr entry. -func (e *TreeEntry) Size() int64 { - e.sizeOnce.Do(func() { - if e.IsTree() { - return - } +// Size returns the size of the entry. It runs a git command to determine the +// size on first call. Successful results are cached; failed attempts are not +// cached, allowing retries with a fresh context. +func (e *TreeEntry) Size(ctx context.Context) int64 { + e.sizeMu.Lock() + defer e.sizeMu.Unlock() - stdout, err := NewCommand("cat-file", "-s", e.id.String()).RunInDir(e.parent.repo.path) - if err != nil { - return - } - e.size, _ = strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64) - }) + if e.sizeSet || e.IsTree() { + return e.size + } + stdout, err := NewCommand(ctx, "cat-file", "-s", e.id.String()).RunInDir(e.parent.repo.path) + if err != nil { + return 0 + } + e.size, _ = strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64) + e.sizeSet = true return e.size } @@ -159,9 +163,6 @@ type CommitsInfoOptions struct { // The maximum number of goroutines to be used for getting commits information. // When not set (i.e. <=0), runtime.GOMAXPROCS is used to determine the value. MaxConcurrency int - // The timeout duration before giving up for each shell command execution. - // The default timeout duration will be used when not supplied. - Timeout time.Duration } var defaultConcurrency = runtime.GOMAXPROCS(0) @@ -170,7 +171,7 @@ var defaultConcurrency = runtime.GOMAXPROCS(0) // the state of given commit and subpath. It takes advantages of concurrency to // speed up the process. The returned list has the same number of items as tree // entries, so the caller can access them via slice indices. -func (es Entries) CommitsInfo(commit *Commit, opts ...CommitsInfoOptions) ([]*EntryCommitInfo, error) { +func (es Entries) CommitsInfo(ctx context.Context, commit *Commit, opts ...CommitsInfoOptions) ([]*EntryCommitInfo, error) { if len(es) == 0 { return []*EntryCommitInfo{}, nil } @@ -232,9 +233,8 @@ func (es Entries) CommitsInfo(commit *Commit, opts ...CommitsInfoOptions) ([]*En epath := path.Join(opt.Path, e.Name()) var err error - info.Commit, err = commit.CommitByPath(CommitByRevisionOptions{ - Path: epath, - Timeout: opt.Timeout, + info.Commit, err = commit.CommitByPath(ctx, CommitByRevisionOptions{ + Path: epath, }) if err != nil { setError(fmt.Errorf("get commit by path %q: %v", epath, err)) @@ -244,7 +244,7 @@ func (es Entries) CommitsInfo(commit *Commit, opts ...CommitsInfoOptions) ([]*En // Get extra information for submodules if e.IsCommit() { // Be tolerant to implicit submodules - info.Submodule, err = commit.Submodule(epath) + info.Submodule, err = commit.Submodule(ctx, epath) if err != nil { info.Submodule = &Submodule{Name: epath} } diff --git a/tree_entry_test.go b/tree_entry_test.go index 50b09ef0..0d305b21 100644 --- a/tree_entry_test.go +++ b/tree_entry_test.go @@ -5,6 +5,7 @@ package git import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -31,12 +32,13 @@ func TestTreeEntry(t *testing.T) { } func TestEntries_Sort(t *testing.T) { - tree, err := testrepo.LsTree("0eedd79eba4394bbef888c804e899731644367fe") + ctx := context.Background() + tree, err := testrepo.LsTree(ctx, "0eedd79eba4394bbef888c804e899731644367fe") if err != nil { t.Fatal(err) } - es, err := tree.Entries() + es, err := tree.Entries(ctx) if err != nil { t.Fatal(err) } @@ -120,23 +122,24 @@ func TestEntries_Sort(t *testing.T) { } func TestEntries_CommitsInfo(t *testing.T) { - tree, err := testrepo.LsTree("cfc3b2993f74726356887a5ec093de50486dc617") + ctx := context.Background() + tree, err := testrepo.LsTree(ctx, "cfc3b2993f74726356887a5ec093de50486dc617") if err != nil { t.Fatal(err) } - c, err := testrepo.CatFileCommit(tree.id.String()) + c, err := testrepo.CatFileCommit(ctx, tree.id.String()) if err != nil { t.Fatal(err) } t.Run("general directory", func(t *testing.T) { - es, err := tree.Entries() + es, err := tree.Entries(ctx) if err != nil { t.Fatal(err) } - infos, err := es.CommitsInfo(c) + infos, err := es.CommitsInfo(ctx, c) if err != nil { t.Fatal(err) } @@ -249,17 +252,17 @@ func TestEntries_CommitsInfo(t *testing.T) { }) t.Run("directory with submodule", func(t *testing.T) { - subtree, err := tree.Subtree("gogs") + subtree, err := tree.Subtree(ctx, "gogs") if err != nil { t.Fatal(err) } - es, err := subtree.Entries() + es, err := subtree.Entries(ctx) if err != nil { t.Fatal(err) } - infos, err := es.CommitsInfo(c, CommitsInfoOptions{ + infos, err := es.CommitsInfo(ctx, c, CommitsInfoOptions{ Path: "gogs", }) if err != nil { @@ -282,18 +285,18 @@ func TestEntries_CommitsInfo(t *testing.T) { } }) - t.Run("direcotry with files have same SHA", func(t *testing.T) { - subtree, err := tree.Subtree("sameSHAs") + t.Run("directory with files that have the same SHA", func(t *testing.T) { + subtree, err := tree.Subtree(ctx, "sameSHAs") if err != nil { t.Fatal(err) } - es, err := subtree.Entries() + es, err := subtree.Entries(ctx) if err != nil { t.Fatal(err) } - infos, err := es.CommitsInfo(c, CommitsInfoOptions{ + infos, err := es.CommitsInfo(ctx, c, CommitsInfoOptions{ Path: "sameSHAs", }) if err != nil {