Repository: seachicken/gh-poi
Pull request #81 (2022-10-26 13:02:10)

Support keyboard interrupt
## What

- Handle `os.Interrupt` signal and propagate cancellation using context
- Add `defer spinner.Stop()` to ensure that the spinner is stopped and cleaned up

## Why

Current implementation does not handle keyboard interrupt, which causes unexpected side effect to the shell:
When I input ctrl-c while a spinner is running (with the message `Fetching pull requests...` for example), the cursor is still hidden even after the command finished.


Patch:
diff --git a/conn/command.go b/conn/command.go
index 4e5cba3..ce419cc 100644
--- a/conn/command.go
+++ b/conn/command.go
@@ -2,6 +2,7 @@ package conn
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"os/exec"
 	"strings"
@@ -11,7 +12,7 @@ import (
 
 type Connection struct{}
 
-func (conn *Connection) CheckRepos(hostname string, repoNames []string) error {
+func (conn *Connection) CheckRepos(ctx context.Context, hostname string, repoNames []string) error {
 	for _, name := range repoNames {
 		args := []string{
 			"api",
@@ -19,28 +20,28 @@ func (conn *Connection) CheckRepos(hostname string, repoNames []string) error {
 			"repos/" + name,
 			"--silent",
 		}
-		if _, err := run("gh", args); err != nil {
+		if _, err := run(ctx, "gh", args); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func (conn *Connection) GetRemoteNames() (string, error) {
+func (conn *Connection) GetRemoteNames(ctx context.Context) (string, error) {
 	args := []string{
 		"remote", "-v",
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) GetSshConfig(name string) (string, error) {
+func (conn *Connection) GetSshConfig(ctx context.Context, name string) (string, error) {
 	args := []string{
 		"-T", "-G", name,
 	}
-	return run("ssh", args)
+	return run(ctx, "ssh", args)
 }
 
-func (conn *Connection) GetRepoNames(hostname string, repoName string) (string, error) {
+func (conn *Connection) GetRepoNames(ctx context.Context, hostname string, repoName string) (string, error) {
 	args := []string{
 		"repo", "view", hostname + "/" + repoName,
 		"--json", "owner",
@@ -48,40 +49,41 @@ func (conn *Connection) GetRepoNames(hostname string, repoName string) (string,
 		"--json", "parent",
 		"--json", "defaultBranchRef",
 	}
-	return run("gh", args)
+	return run(ctx, "gh", args)
 }
 
-func (conn *Connection) GetBranchNames() (string, error) {
+func (conn *Connection) GetBranchNames(ctx context.Context) (string, error) {
 	args := []string{
 		"branch", "-v", "--no-abbrev",
 		"--format=%(HEAD):%(refname:lstrip=2):%(objectname)",
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) GetMergedBranchNames(remoteName string, branchName string) (string, error) {
+func (conn *Connection) GetMergedBranchNames(ctx context.Context, remoteName string, branchName string) (string, error) {
 	args := []string{
 		"branch", "--merged", fmt.Sprintf("%s/%s", remoteName, branchName),
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) GetLog(branchName string) (string, error) {
+func (conn *Connection) GetLog(ctx context.Context, branchName string) (string, error) {
 	args := []string{
 		"log", "--first-parent", "--max-count=30", "--format=%H", branchName, "--",
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) GetAssociatedRefNames(oid string) (string, error) {
+func (conn *Connection) GetAssociatedRefNames(ctx context.Context, oid string) (string, error) {
 	args := []string{
 		"branch", "--all", "--format=%(refname)",
 		"--contains", oid,
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
 func (conn *Connection) GetPullRequests(
+	ctx context.Context,
 	hostname string, repoNames []string, queryHashes string) (string, error) {
 	args := []string{
 		"api", "graphql",
@@ -114,43 +116,43 @@ func (conn *Connection) GetPullRequests(
 			queryHashes,
 		),
 	}
-	return run("gh", args)
+	return run(ctx, "gh", args)
 }
 
-func (conn *Connection) GetUncommittedChanges() (string, error) {
+func (conn *Connection) GetUncommittedChanges(ctx context.Context) (string, error) {
 	args := []string{
 		"status", "--short",
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) GetConfig(key string) (string, error) {
+func (conn *Connection) GetConfig(ctx context.Context, key string) (string, error) {
 	args := []string{
 		"config", "--get", key,
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) CheckoutBranch(branchName string) (string, error) {
+func (conn *Connection) CheckoutBranch(ctx context.Context, branchName string) (string, error) {
 	args := []string{
 		"checkout", "--quiet", branchName,
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) DeleteBranches(branchNames []string) (string, error) {
+func (conn *Connection) DeleteBranches(ctx context.Context, branchNames []string) (string, error) {
 	args := append([]string{
 		"branch", "-D"},
 		branchNames...,
 	)
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
-func (conn *Connection) PruneRemoteBranches(remoteName string) (string, error) {
+func (conn *Connection) PruneRemoteBranches(ctx context.Context, remoteName string) (string, error) {
 	args := []string{
 		"remote", "prune", remoteName,
 	}
-	return run("git", args)
+	return run(ctx, "git", args)
 }
 
 func getQueryRepos(repoNames []string) string {
@@ -161,14 +163,14 @@ func getQueryRepos(repoNames []string) string {
 	return repos.String()
 }
 
-func run(name string, args []string) (string, error) {
+func run(ctx context.Context, name string, args []string) (string, error) {
 	cmdPath, err := safeexec.LookPath(name)
 	if err != nil {
 		return "", err
 	}
 
 	var stdout bytes.Buffer
-	cmd := exec.Command(cmdPath, args...)
+	cmd := exec.CommandContext(ctx, cmdPath, args...)
 	cmd.Stdout = &stdout
 
 	err = cmd.Run()
diff --git a/conn/stub.go b/conn/stub.go
index 5b2b28d..5110e57 100644
--- a/conn/stub.go
+++ b/conn/stub.go
@@ -1,8 +1,8 @@
 package conn
 
 import (
-	"io/ioutil"
-	"path"
+	"os"
+	"path/filepath"
 	"runtime"
 
 	"github.com/golang/mock/gomock"
@@ -59,7 +59,7 @@ func (s *Stub) CheckRepos(err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			CheckRepos(gomock.Any(), gomock.Any()).
+			CheckRepos(gomock.Any(), gomock.Any(), gomock.Any()).
 			Return(err),
 		conf,
 	)
@@ -71,7 +71,7 @@ func (s *Stub) GetRemoteNames(filename string, err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			GetRemoteNames().
+			GetRemoteNames(gomock.Any()).
 			Return(s.readFile("git", "remote", filename), err),
 		conf,
 	)
@@ -83,7 +83,7 @@ func (s *Stub) GetSshConfig(filename string, err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			GetSshConfig(gomock.Any()).
+			GetSshConfig(gomock.Any(), gomock.Any()).
 			Return(s.readFile("ssh", "config", filename), err),
 		conf,
 	)
@@ -95,7 +95,7 @@ func (s *Stub) GetRepoNames(filename string, err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			GetRepoNames(gomock.Any(), gomock.Any()).
+			GetRepoNames(gomock.Any(), gomock.Any(), gomock.Any()).
 			Return(s.readFile("gh", "repo", filename), err),
 		conf,
 	)
@@ -106,7 +106,7 @@ func (s *Stub) GetBranchNames(filename string, err error, conf *Conf) *Stub {
 	s.t.Helper()
 	configure(
 		s.Conn.EXPECT().
-			GetBranchNames().
+			GetBranchNames(gomock.Any()).
 			Return(s.readFile("git", "branch", filename), err),
 		conf,
 	)
@@ -117,7 +117,7 @@ func (s *Stub) GetMergedBranchNames(filename string, err error, conf *Conf) *Stu
 	s.t.Helper()
 	configure(
 		s.Conn.EXPECT().
-			GetMergedBranchNames("origin", "main").
+			GetMergedBranchNames(gomock.Any(), "origin", "main").
 			Return(s.readFile("git", "branchMerged", filename), err),
 		conf,
 	)
@@ -129,7 +129,7 @@ func (s *Stub) GetAssociatedRefNames(stubs []AssociatedBranchNamesStub, err erro
 	for _, stub := range stubs {
 		configure(
 			s.Conn.EXPECT().
-				GetAssociatedRefNames(stub.Oid).
+				GetAssociatedRefNames(gomock.Any(), stub.Oid).
 				Return(s.readFile("git", "abranch", stub.Filename), err),
 			conf,
 		)
@@ -142,7 +142,7 @@ func (s *Stub) GetLog(stubs []LogStub, err error, conf *Conf) *Stub {
 	for _, stub := range stubs {
 		configure(
 			s.Conn.EXPECT().
-				GetLog(stub.BranchName).
+				GetLog(gomock.Any(), stub.BranchName).
 				Return(s.readFile("git", "log", stub.Filename), err),
 			conf,
 		)
@@ -155,7 +155,7 @@ func (s *Stub) GetPullRequests(filename string, err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			GetPullRequests(gomock.Any(), gomock.Any(), gomock.Any()).
+			GetPullRequests(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
 			Return(s.readFile("gh", "pr", filename), err),
 		conf,
 	)
@@ -167,7 +167,7 @@ func (s *Stub) GetUncommittedChanges(uncommittedChanges string, err error, conf
 	configure(
 		s.Conn.
 			EXPECT().
-			GetUncommittedChanges().
+			GetUncommittedChanges(gomock.Any()).
 			Return(uncommittedChanges, err),
 		conf,
 	)
@@ -180,7 +180,7 @@ func (s *Stub) GetConfig(stubs []ConfigStub, err error, conf *Conf) *Stub {
 		configure(
 			s.Conn.
 				EXPECT().
-				GetConfig(stub.BranchName).
+				GetConfig(gomock.Any(), stub.BranchName).
 				Return(s.readFile("git", "configMerge", stub.Filename), err),
 			conf,
 		)
@@ -193,7 +193,7 @@ func (s *Stub) CheckoutBranch(err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			CheckoutBranch(gomock.Any()).
+			CheckoutBranch(gomock.Any(), gomock.Any()).
 			Return("", err),
 		conf,
 	)
@@ -205,7 +205,7 @@ func (s *Stub) DeleteBranches(err error, conf *Conf) *Stub {
 	configure(
 		s.Conn.
 			EXPECT().
-			DeleteBranches(gomock.Any()).
+			DeleteBranches(gomock.Any(), gomock.Any()).
 			Return("", err),
 		conf,
 	)
@@ -227,7 +227,7 @@ func (s *Stub) readFile(command string, category string, name string) string {
 	if command == "gh" {
 		ext = ".json"
 	}
-	b, err := ioutil.ReadFile(path.Join(filename, "..", fixturePath, command, category+"_"+name+ext))
+	b, err := os.ReadFile(filepath.Join(filename, "..", fixturePath, command, category+"_"+name+ext))
 	if err != nil {
 		s.t.Fatalf("%v", err)
 	}
diff --git a/main.go b/main.go
index f3cfa93..e2020bd 100644
--- a/main.go
+++ b/main.go
@@ -1,9 +1,11 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"os"
+	"os/signal"
 	"time"
 
 	"github.com/briandowns/spinner"
@@ -29,25 +31,29 @@ func main() {
 }
 
 func runMain(dryRun bool) {
+	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
+	defer stop()
+
 	if dryRun {
 		fmt.Fprintf(color.Output, "%s\n", whiteBold("== DRY RUN =="))
 	}
 
 	connection := &conn.Connection{}
 	sp := spinner.New(spinner.CharSets[14], 40*time.Millisecond)
+	defer sp.Stop()
 
 	fetchingMsg := " Fetching pull requests..."
 	sp.Suffix = fetchingMsg
 	sp.Start()
 	var fetchingErr error
 
-	remote, err := GetRemote(connection)
+	remote, err := GetRemote(ctx, connection)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 		return
 	}
 
-	branches, fetchingErr := GetBranches(remote, connection, dryRun)
+	branches, fetchingErr := GetBranches(ctx, remote, connection, dryRun)
 
 	sp.Stop()
 
@@ -68,8 +74,8 @@ func runMain(dryRun bool) {
 		sp.Suffix = deletingMsg
 		sp.Restart()
 
-		branches, deletingErr = DeleteBranches(branches, connection)
-		connection.PruneRemoteBranches(remote.Name)
+		branches, deletingErr = DeleteBranches(ctx, branches, connection)
+		connection.PruneRemoteBranches(ctx, remote.Name)
 
 		sp.Stop()
 
diff --git a/mocks/poi_mock.go b/mocks/poi_mock.go
index 43188fc..55c87b2 100644
--- a/mocks/poi_mock.go
+++ b/mocks/poi_mock.go
@@ -5,6 +5,7 @@
 package mocks
 
 import (
+	context "context"
 	reflect "reflect"
 
 	gomock "github.com/golang/mock/gomock"
@@ -34,195 +35,195 @@ func (m *MockConnection) EXPECT() *MockConnectionMockRecorder {
 }
 
 // CheckRepos mocks base method.
-func (m *MockConnection) CheckRepos(hostname string, repoNames []string) error {
+func (m *MockConnection) CheckRepos(ctx context.Context, hostname string, repoNames []string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CheckRepos", hostname, repoNames)
+	ret := m.ctrl.Call(m, "CheckRepos", ctx, hostname, repoNames)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // CheckRepos indicates an expected call of CheckRepos.
-func (mr *MockConnectionMockRecorder) CheckRepos(hostname, repoNames interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) CheckRepos(ctx, hostname, repoNames interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckRepos", reflect.TypeOf((*MockConnection)(nil).CheckRepos), hostname, repoNames)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckRepos", reflect.TypeOf((*MockConnection)(nil).CheckRepos), ctx, hostname, repoNames)
 }
 
 // CheckoutBranch mocks base method.
-func (m *MockConnection) CheckoutBranch(branchName string) (string, error) {
+func (m *MockConnection) CheckoutBranch(ctx context.Context, branchName string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CheckoutBranch", branchName)
+	ret := m.ctrl.Call(m, "CheckoutBranch", ctx, branchName)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // CheckoutBranch indicates an expected call of CheckoutBranch.
-func (mr *MockConnectionMockRecorder) CheckoutBranch(branchName interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) CheckoutBranch(ctx, branchName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckoutBranch", reflect.TypeOf((*MockConnection)(nil).CheckoutBranch), branchName)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckoutBranch", reflect.TypeOf((*MockConnection)(nil).CheckoutBranch), ctx, branchName)
 }
 
 // DeleteBranches mocks base method.
-func (m *MockConnection) DeleteBranches(branchNames []string) (string, error) {
+func (m *MockConnection) DeleteBranches(ctx context.Context, branchNames []string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DeleteBranches", branchNames)
+	ret := m.ctrl.Call(m, "DeleteBranches", ctx, branchNames)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // DeleteBranches indicates an expected call of DeleteBranches.
-func (mr *MockConnectionMockRecorder) DeleteBranches(branchNames interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) DeleteBranches(ctx, branchNames interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBranches", reflect.TypeOf((*MockConnection)(nil).DeleteBranches), branchNames)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBranches", reflect.TypeOf((*MockConnection)(nil).DeleteBranches), ctx, branchNames)
 }
 
 // GetAssociatedRefNames mocks base method.
-func (m *MockConnection) GetAssociatedRefNames(oid string) (string, error) {
+func (m *MockConnection) GetAssociatedRefNames(ctx context.Context, oid string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetAssociatedRefNames", oid)
+	ret := m.ctrl.Call(m, "GetAssociatedRefNames", ctx, oid)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetAssociatedRefNames indicates an expected call of GetAssociatedRefNames.
-func (mr *MockConnectionMockRecorder) GetAssociatedRefNames(oid interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetAssociatedRefNames(ctx, oid interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAssociatedRefNames", reflect.TypeOf((*MockConnection)(nil).GetAssociatedRefNames), oid)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAssociatedRefNames", reflect.TypeOf((*MockConnection)(nil).GetAssociatedRefNames), ctx, oid)
 }
 
 // GetBranchNames mocks base method.
-func (m *MockConnection) GetBranchNames() (string, error) {
+func (m *MockConnection) GetBranchNames(ctx context.Context) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetBranchNames")
+	ret := m.ctrl.Call(m, "GetBranchNames", ctx)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetBranchNames indicates an expected call of GetBranchNames.
-func (mr *MockConnectionMockRecorder) GetBranchNames() *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetBranchNames(ctx interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchNames", reflect.TypeOf((*MockConnection)(nil).GetBranchNames))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchNames", reflect.TypeOf((*MockConnection)(nil).GetBranchNames), ctx)
 }
 
 // GetConfig mocks base method.
-func (m *MockConnection) GetConfig(key string) (string, error) {
+func (m *MockConnection) GetConfig(ctx context.Context, key string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetConfig", key)
+	ret := m.ctrl.Call(m, "GetConfig", ctx, key)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetConfig indicates an expected call of GetConfig.
-func (mr *MockConnectionMockRecorder) GetConfig(key interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetConfig(ctx, key interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockConnection)(nil).GetConfig), key)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockConnection)(nil).GetConfig), ctx, key)
 }
 
 // GetLog mocks base method.
-func (m *MockConnection) GetLog(branchName string) (string, error) {
+func (m *MockConnection) GetLog(ctx context.Context, branchName string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetLog", branchName)
+	ret := m.ctrl.Call(m, "GetLog", ctx, branchName)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetLog indicates an expected call of GetLog.
-func (mr *MockConnectionMockRecorder) GetLog(branchName interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetLog(ctx, branchName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLog", reflect.TypeOf((*MockConnection)(nil).GetLog), branchName)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLog", reflect.TypeOf((*MockConnection)(nil).GetLog), ctx, branchName)
 }
 
 // GetMergedBranchNames mocks base method.
-func (m *MockConnection) GetMergedBranchNames(remoteName, branchName string) (string, error) {
+func (m *MockConnection) GetMergedBranchNames(ctx context.Context, remoteName, branchName string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetMergedBranchNames", remoteName, branchName)
+	ret := m.ctrl.Call(m, "GetMergedBranchNames", ctx, remoteName, branchName)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetMergedBranchNames indicates an expected call of GetMergedBranchNames.
-func (mr *MockConnectionMockRecorder) GetMergedBranchNames(remoteName, branchName interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetMergedBranchNames(ctx, remoteName, branchName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergedBranchNames", reflect.TypeOf((*MockConnection)(nil).GetMergedBranchNames), remoteName, branchName)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergedBranchNames", reflect.TypeOf((*MockConnection)(nil).GetMergedBranchNames), ctx, remoteName, branchName)
 }
 
 // GetPullRequests mocks base method.
-func (m *MockConnection) GetPullRequests(hostname string, repoNames []string, queryHashes string) (string, error) {
+func (m *MockConnection) GetPullRequests(ctx context.Context, hostname string, repoNames []string, queryHashes string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetPullRequests", hostname, repoNames, queryHashes)
+	ret := m.ctrl.Call(m, "GetPullRequests", ctx, hostname, repoNames, queryHashes)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetPullRequests indicates an expected call of GetPullRequests.
-func (mr *MockConnectionMockRecorder) GetPullRequests(hostname, repoNames, queryHashes interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetPullRequests(ctx, hostname, repoNames, queryHashes interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequests", reflect.TypeOf((*MockConnection)(nil).GetPullRequests), hostname, repoNames, queryHashes)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequests", reflect.TypeOf((*MockConnection)(nil).GetPullRequests), ctx, hostname, repoNames, queryHashes)
 }
 
 // GetRemoteNames mocks base method.
-func (m *MockConnection) GetRemoteNames() (string, error) {
+func (m *MockConnection) GetRemoteNames(ctx context.Context) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetRemoteNames")
+	ret := m.ctrl.Call(m, "GetRemoteNames", ctx)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetRemoteNames indicates an expected call of GetRemoteNames.
-func (mr *MockConnectionMockRecorder) GetRemoteNames() *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetRemoteNames(ctx interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteNames", reflect.TypeOf((*MockConnection)(nil).GetRemoteNames))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteNames", reflect.TypeOf((*MockConnection)(nil).GetRemoteNames), ctx)
 }
 
 // GetRepoNames mocks base method.
-func (m *MockConnection) GetRepoNames(hostname, repoName string) (string, error) {
+func (m *MockConnection) GetRepoNames(ctx context.Context, hostname, repoName string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetRepoNames", hostname, repoName)
+	ret := m.ctrl.Call(m, "GetRepoNames", ctx, hostname, repoName)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetRepoNames indicates an expected call of GetRepoNames.
-func (mr *MockConnectionMockRecorder) GetRepoNames(hostname, repoName interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetRepoNames(ctx, hostname, repoName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepoNames", reflect.TypeOf((*MockConnection)(nil).GetRepoNames), hostname, repoName)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepoNames", reflect.TypeOf((*MockConnection)(nil).GetRepoNames), ctx, hostname, repoName)
 }
 
 // GetSshConfig mocks base method.
-func (m *MockConnection) GetSshConfig(name string) (string, error) {
+func (m *MockConnection) GetSshConfig(ctx context.Context, name string) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetSshConfig", name)
+	ret := m.ctrl.Call(m, "GetSshConfig", ctx, name)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetSshConfig indicates an expected call of GetSshConfig.
-func (mr *MockConnectionMockRecorder) GetSshConfig(name interface{}) *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetSshConfig(ctx, name interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSshConfig", reflect.TypeOf((*MockConnection)(nil).GetSshConfig), name)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSshConfig", reflect.TypeOf((*MockConnection)(nil).GetSshConfig), ctx, name)
 }
 
 // GetUncommittedChanges mocks base method.
-func (m *MockConnection) GetUncommittedChanges() (string, error) {
+func (m *MockConnection) GetUncommittedChanges(ctx context.Context) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetUncommittedChanges")
+	ret := m.ctrl.Call(m, "GetUncommittedChanges", ctx)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetUncommittedChanges indicates an expected call of GetUncommittedChanges.
-func (mr *MockConnectionMockRecorder) GetUncommittedChanges() *gomock.Call {
+func (mr *MockConnectionMockRecorder) GetUncommittedChanges(ctx interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUncommittedChanges", reflect.TypeOf((*MockConnection)(nil).GetUncommittedChanges))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUncommittedChanges", reflect.TypeOf((*MockConnection)(nil).GetUncommittedChanges), ctx)
 }
diff --git a/poi.go b/poi.go
index f137f0a..564328f 100644
--- a/poi.go
+++ b/poi.go
@@ -2,6 +2,7 @@
 package main
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"regexp"
@@ -14,19 +15,19 @@ import (
 
 type (
 	Connection interface {
-		CheckRepos(hostname string, repoNames []string) error
-		GetRemoteNames() (string, error)
-		GetSshConfig(name string) (string, error)
-		GetRepoNames(hostname string, repoName string) (string, error)
-		GetBranchNames() (string, error)
-		GetMergedBranchNames(remoteName string, branchName string) (string, error)
-		GetLog(branchName string) (string, error)
-		GetAssociatedRefNames(oid string) (string, error)
-		GetPullRequests(hostname string, repoNames []string, queryHashes string) (string, error)
-		GetUncommittedChanges() (string, error)
-		GetConfig(key string) (string, error)
-		CheckoutBranch(branchName string) (string, error)
-		DeleteBranches(branchNames []string) (string, error)
+		CheckRepos(ctx context.Context, hostname string, repoNames []string) error
+		GetRemoteNames(ctx context.Context) (string, error)
+		GetSshConfig(ctx context.Context, name string) (string, error)
+		GetRepoNames(ctx context.Context, hostname string, repoName string) (string, error)
+		GetBranchNames(ctx context.Context) (string, error)
+		GetMergedBranchNames(ctx context.Context, remoteName string, branchName string) (string, error)
+		GetLog(ctx context.Context, branchName string) (string, error)
+		GetAssociatedRefNames(ctx context.Context, oid string) (string, error)
+		GetPullRequests(ctx context.Context, hostname string, repoNames []string, queryHashes string) (string, error)
+		GetUncommittedChanges(ctx context.Context) (string, error)
+		GetConfig(ctx context.Context, key string) (string, error)
+		CheckoutBranch(ctx context.Context, branchName string) (string, error)
+		DeleteBranches(ctx context.Context, branchNames []string) (string, error)
 	}
 
 	Remote struct {
@@ -86,8 +87,8 @@ const (
 var detachedBranchNameRegex = regexp.MustCompile(`^\(.+\)`)
 var ErrNotFound = errors.New("not found")
 
-func GetRemote(connection Connection) (Remote, error) {
-	remoteNames, err := connection.GetRemoteNames()
+func GetRemote(ctx context.Context, connection Connection) (Remote, error) {
+	remoteNames, err := connection.GetRemoteNames(ctx)
 	if err != nil {
 		return Remote{}, err
 	}
@@ -95,7 +96,7 @@ func GetRemote(connection Connection) (Remote, error) {
 	remotes := toRemotes(splitLines(remoteNames))
 	if remote, err := getPrimaryRemote(remotes); err == nil {
 		hostname := remote.Hostname
-		if config, err := connection.GetSshConfig(hostname); err == nil {
+		if config, err := connection.GetSshConfig(ctx, hostname); err == nil {
 			remote.Hostname = normalizeHostname(findHostname(splitLines(config), hostname))
 		}
 		return remote, nil
@@ -104,29 +105,29 @@ func GetRemote(connection Connection) (Remote, error) {
 	}
 }
 
-func GetBranches(remote Remote, connection Connection, dryRun bool) ([]Branch, error) {
+func GetBranches(ctx context.Context, remote Remote, connection Connection, dryRun bool) ([]Branch, error) {
 	var repoNames []string
 	var defaultBranchName string
-	if json, err := connection.GetRepoNames(remote.Hostname, remote.RepoName); err == nil {
+	if json, err := connection.GetRepoNames(ctx, remote.Hostname, remote.RepoName); err == nil {
 		repoNames, defaultBranchName, _ = getRepo(json)
 	} else {
 		return nil, err
 	}
 
-	err := connection.CheckRepos(remote.Hostname, repoNames)
+	err := connection.CheckRepos(ctx, remote.Hostname, repoNames)
 	if err != nil {
 		return nil, err
 	}
 
 	var branches []Branch
-	if names, err := connection.GetBranchNames(); err == nil {
+	if names, err := connection.GetBranchNames(ctx); err == nil {
 		branches = toBranch(splitLines(names))
-		mergedNames, err := connection.GetMergedBranchNames(remote.Name, defaultBranchName)
+		mergedNames, err := connection.GetMergedBranchNames(ctx, remote.Name, defaultBranchName)
 		if err != nil {
 			return nil, err
 		}
 		branches = applyMerged(branches, extractMergedBranchNames(splitLines(mergedNames)))
-		branches, err = applyCommits(branches, defaultBranchName, connection)
+		branches, err = applyCommits(ctx, branches, defaultBranchName, connection)
 		if err != nil {
 			return nil, err
 		}
@@ -136,7 +137,7 @@ func GetBranches(remote Remote, connection Connection, dryRun bool) ([]Branch, e
 
 	prs := []PullRequest{}
 	for _, queryHashes := range getQueryHashes(branches) {
-		json, err := connection.GetPullRequests(remote.Hostname, repoNames, queryHashes)
+		json, err := connection.GetPullRequests(ctx, remote.Hostname, repoNames, queryHashes)
 		if err != nil {
 			return nil, err
 		}
@@ -146,10 +147,10 @@ func GetBranches(remote Remote, connection Connection, dryRun bool) ([]Branch, e
 		}
 	}
 
-	branches = applyPullRequest(branches, prs, connection)
+	branches = applyPullRequest(ctx, branches, prs, connection)
 
 	var uncommittedChanges []UncommittedChange
-	if changes, err := connection.GetUncommittedChanges(); err == nil {
+	if changes, err := connection.GetUncommittedChanges(ctx); err == nil {
 		uncommittedChanges = toUncommittedChange(splitLines(changes))
 	} else {
 		return nil, err
@@ -169,7 +170,7 @@ func GetBranches(remote Remote, connection Connection, dryRun bool) ([]Branch, e
 		result := []Branch{}
 
 		if !dryRun {
-			_, err := connection.CheckoutBranch(defaultBranchName)
+			_, err := connection.CheckoutBranch(ctx, defaultBranchName)
 			if err != nil {
 				return nil, err
 			}
@@ -279,7 +280,7 @@ func nameExists(name string, names []string) bool {
 	return false
 }
 
-func applyCommits(branches []Branch, defaultBranchName string, connection Connection) ([]Branch, error) {
+func applyCommits(ctx context.Context, branches []Branch, defaultBranchName string, connection Connection) ([]Branch, error) {
 	results := []Branch{}
 
 	for _, branch := range branches {
@@ -288,12 +289,12 @@ func applyCommits(branches []Branch, defaultBranchName string, connection Connec
 			continue
 		}
 
-		oids, err := connection.GetLog(branch.Name)
+		oids, err := connection.GetLog(ctx, branch.Name)
 		if err != nil {
 			return nil, err
 		}
 
-		trimmedOids, err := trimBranch(splitLines(oids), branch.IsMerged,
+		trimmedOids, err := trimBranch(ctx, splitLines(oids), branch.IsMerged,
 			branch.Name, defaultBranchName, connection)
 		if err != nil {
 			return nil, err
@@ -306,7 +307,7 @@ func applyCommits(branches []Branch, defaultBranchName string, connection Connec
 	return results, nil
 }
 
-func trimBranch(oids []string, isMerged bool,
+func trimBranch(ctx context.Context, oids []string, isMerged bool,
 	branchName string, defaultBranchName string, connection Connection) ([]string, error) {
 	results := []string{}
 	childNames := []string{}
@@ -317,7 +318,7 @@ func trimBranch(oids []string, isMerged bool,
 			break
 		}
 
-		refNames, err := connection.GetAssociatedRefNames(oid)
+		refNames, err := connection.GetAssociatedRefNames(ctx, oid)
 		if err != nil {
 			return nil, err
 		}
@@ -364,13 +365,13 @@ func extractBranchNames(refNames []string) []string {
 	return result
 }
 
-func applyPullRequest(branches []Branch, prs []PullRequest, connection Connection) []Branch {
+func applyPullRequest(ctx context.Context, branches []Branch, prs []PullRequest, connection Connection) []Branch {
 	prNumbers := map[string]int{}
 	for _, branch := range branches {
 		if branch.IsDetached() {
 			continue
 		}
-		mergeConfig, _ := connection.GetConfig(fmt.Sprintf("branch.%s.merge", branch.Name))
+		mergeConfig, _ := connection.GetConfig(ctx, fmt.Sprintf("branch.%s.merge", branch.Name))
 		if n := getPRNumber(mergeConfig); n > 0 {
 			prNumbers[branch.Name] = n
 		}
@@ -631,15 +632,15 @@ func toPullRequestState(state string) (PullRequestState, error) {
 	}
 }
 
-func DeleteBranches(branches []Branch, connection Connection) ([]Branch, error) {
+func DeleteBranches(ctx context.Context, branches []Branch, connection Connection) ([]Branch, error) {
 	branchNames := getBranchNames(branches, Deletable)
 	if len(branchNames) == 0 {
 		return branches, nil
 	}
 
-	connection.DeleteBranches(branchNames)
+	connection.DeleteBranches(ctx, branchNames)
 
-	branchNamesAfter, err := connection.GetBranchNames()
+	branchNamesAfter, err := connection.GetBranchNames(ctx)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/conn/command_test.go b/conn/command_test.go
index fb7f53e..c0b20e5 100644
--- a/conn/command_test.go
+++ b/conn/command_test.go
@@ -1,8 +1,9 @@
 package conn
 
 import (
+	"context"
 	"os"
-	"path"
+	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -17,7 +18,7 @@ func Test_RepoBasic(t *testing.T) {
 	stub := &Stub{nil, t}
 
 	t.Run("GetRemoteNames", func(t *testing.T) {
-		actual, _ := conn.GetRemoteNames()
+		actual, _ := conn.GetRemoteNames(context.Background())
 		assert.Equal(t,
 			stub.readFile("git", "remote", "origin"),
 			actual,
@@ -25,7 +26,7 @@ func Test_RepoBasic(t *testing.T) {
 	})
 
 	t.Run("GetBranchNames", func(t *testing.T) {
-		actual, _ := conn.GetBranchNames()
+		actual, _ := conn.GetBranchNames(context.Background())
 		assert.Equal(t,
 			stub.readFile("git", "branch", "@main_issue1"),
 			actual,
@@ -35,7 +36,7 @@ func Test_RepoBasic(t *testing.T) {
 	t.Run("GetLog", func(t *testing.T) {
 
 		t.Run("main", func(t *testing.T) {
-			actual, _ := conn.GetLog("main")
+			actual, _ := conn.GetLog(context.Background(), "main")
 			assert.Equal(t,
 				stub.readFile("git", "log", "main"),
 				actual,
@@ -43,7 +44,7 @@ func Test_RepoBasic(t *testing.T) {
 		})
 
 		t.Run("issue1", func(t *testing.T) {
-			actual, _ := conn.GetLog("issue1")
+			actual, _ := conn.GetLog(context.Background(), "issue1")
 			assert.Equal(t,
 				stub.readFile("git", "log", "issue1"),
 				actual,
@@ -54,7 +55,7 @@ func Test_RepoBasic(t *testing.T) {
 	t.Run("GetAssociatedRefNames", func(t *testing.T) {
 
 		t.Run("issue1", func(t *testing.T) {
-			actual, _ := conn.GetAssociatedRefNames("a97e9630426df5d34ca9ee77ae1159bdfd5ff8f0")
+			actual, _ := conn.GetAssociatedRefNames(context.Background(), "a97e9630426df5d34ca9ee77ae1159bdfd5ff8f0")
 			assert.Equal(t,
 				stub.readFile("git", "abranch", "issue1"),
 				actual,
@@ -62,7 +63,7 @@ func Test_RepoBasic(t *testing.T) {
 		})
 
 		t.Run("main_issue1", func(t *testing.T) {
-			actual, _ := conn.GetAssociatedRefNames("6ebe3d30d23531af56bd23b5a098d3ccae2a534a")
+			actual, _ := conn.GetAssociatedRefNames(context.Background(), "6ebe3d30d23531af56bd23b5a098d3ccae2a534a")
 			assert.Equal(t,
 				stub.readFile("git", "abranch", "main_issue1"),
 				actual,
@@ -71,12 +72,12 @@ func Test_RepoBasic(t *testing.T) {
 	})
 
 	t.Run("GetUncommittedChanges", func(t *testing.T) {
-		actual, _ := conn.GetUncommittedChanges()
+		actual, _ := conn.GetUncommittedChanges(context.Background())
 		assert.Equal(t, "A  README.md\n", actual)
 	})
 
 	t.Run("GetConfig", func(t *testing.T) {
-		actual, _ := conn.GetConfig("branch.main.merge")
+		actual, _ := conn.GetConfig(context.Background(), "branch.main.merge")
 		assert.Equal(t,
 			stub.readFile("git", "configMerge", "main"),
 			actual,
@@ -88,8 +89,8 @@ func setGitDir(repoName string, t *testing.T) {
 	gitDirOrg := os.Getenv("GIT_DIR")
 	gitWorkTreeOrg := os.Getenv("GIT_WORK_TREE")
 
-	os.Setenv("GIT_DIR", path.Join(fixturePath, repoName, ".git"))
-	os.Setenv("GIT_WORK_TREE", path.Join(fixturePath, repoName))
+	os.Setenv("GIT_DIR", filepath.Join(fixturePath, repoName, ".git"))
+	os.Setenv("GIT_WORK_TREE", filepath.Join(fixturePath, repoName))
 
 	t.Cleanup(func() {
 		os.Setenv("GIT_DIR", gitDirOrg)
diff --git a/poi_test.go b/poi_test.go
index b0d8343..f08b97d 100644
--- a/poi_test.go
+++ b/poi_test.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"errors"
 	"testing"
 
@@ -34,9 +35,9 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithMergedPR(t *testing.T) {
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -88,9 +89,9 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithSquashAndMergedPR(t *testin
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -142,9 +143,9 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithUpstreamSquashAndMergedPR(t
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -196,9 +197,9 @@ func Test_ShouldBeDeletableWhenPRCheckoutBranchesAssociatedWithUpstreamSquashAnd
 			{BranchName: "branch.fork/main.merge", Filename: "forkMain"},
 			{BranchName: "branch.main.merge", Filename: "main"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -251,9 +252,9 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithTheCheckIsFalse(t *testing.
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil).
 		CheckoutBranch(nil, conn.NewConf(&conn.Times{N: 1}))
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -306,9 +307,9 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithTheCheckIsTrue(t *testing.T
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil).
 		CheckoutBranch(nil, conn.NewConf(&conn.Times{N: 0}))
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, true)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, true)
 
 	assert.Equal(t, []Branch{
 		{
@@ -360,9 +361,9 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithoutADefaultBranch(t *testin
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil).
 		CheckoutBranch(nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -415,9 +416,9 @@ func Test_ShouldNotDeletableWhenBranchHasModifiedUncommittedChanges(t *testing.T
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil).
 		CheckoutBranch(nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -470,9 +471,9 @@ func Test_ShouldBeDeletableWhenBranchHasUntrackedUncommittedChanges(t *testing.T
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil).
 		CheckoutBranch(nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -524,9 +525,9 @@ func Test_ShouldNotDeletableWhenBranchesAssociatedWithClosedPR(t *testing.T) {
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -578,9 +579,9 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithSquashAndMergedAndClosedPRs
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -641,9 +642,9 @@ func Test_ShouldNotDeletableWhenBranchesAssociatedWithNotFullyMergedPR(t *testin
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -696,9 +697,9 @@ func Test_ShouldNotDeletableWhenDefaultBranchAssociatedWithMergedPR(t *testing.T
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -752,9 +753,9 @@ func Test_BranchesAndPRsAreNotAssociatedWhenManyLocalCommitsAreAhead(t *testing.
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -799,9 +800,9 @@ func Test_ShouldBeNoCommitHistoryWhenTheFirstCommitOfATopicBranchIsAssociatedWit
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -841,9 +842,9 @@ func Test_ShouldBeNoCommitHistoryWhenDetachedBranch(t *testing.T) {
 		GetConfig([]conn.ConfigStub{
 			{BranchName: "branch.main.merge", Filename: "main"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	actual, _ := GetBranches(remote, s.Conn, false)
+	actual, _ := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Equal(t, []Branch{
 		{
@@ -868,7 +869,7 @@ func Test_ReturnsAnErrorWhenGetRemoteNamesFails(t *testing.T) {
 	s := conn.Setup(ctrl).
 		GetRemoteNames("origin", errors.New("failed to run external command: git"), nil)
 
-	_, err := GetRemote(s.Conn)
+	_, err := GetRemote(context.Background(), s.Conn)
 
 	assert.NotNil(t, err)
 }
@@ -897,9 +898,9 @@ func Test_DoesNotReturnsAnErrorWhenGetSshConfigFails(t *testing.T) {
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.Nil(t, err)
 }
@@ -912,9 +913,9 @@ func Test_ReturnsAnErrorWhenGetRepoNamesFails(t *testing.T) {
 		GetRemoteNames("origin", nil, nil).
 		GetSshConfig("github.com", nil, nil).
 		GetRepoNames("origin", errors.New("failed to run external command: gh"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -928,9 +929,9 @@ func Test_ReturnsAnErrorWhenCheckReposFails(t *testing.T) {
 		GetRemoteNames("origin", nil, nil).
 		GetSshConfig("github.com", nil, nil).
 		GetRepoNames("origin", nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -945,9 +946,9 @@ func Test_ReturnsAnErrorWhenGetBranchNamesFails(t *testing.T) {
 		GetSshConfig("github.com", nil, nil).
 		GetRepoNames("origin", nil, nil).
 		GetBranchNames("@main_issue1", errors.New("failed to run external command: git"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -963,9 +964,9 @@ func Test_ReturnsAnErrorWhenGetMergedBranchNames(t *testing.T) {
 		GetRepoNames("origin", nil, nil).
 		GetBranchNames("@main_issue1", nil, nil).
 		GetMergedBranchNames("@main", errors.New("failed to run external command: git"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -984,9 +985,9 @@ func Test_ReturnsAnErrorWhenGetLogFails(t *testing.T) {
 		GetLog([]conn.LogStub{
 			{BranchName: "main", Filename: "main"}, {BranchName: "issue1", Filename: "issue1"},
 		}, errors.New("failed to run external command: git"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -1009,9 +1010,9 @@ func Test_ReturnsAnErrorWhenGetAssociatedRefNamesFails(t *testing.T) {
 			{Oid: "a97e9630426df5d34ca9ee77ae1159bdfd5ff8f0", Filename: "issue1"},
 			{Oid: "6ebe3d30d23531af56bd23b5a098d3ccae2a534a", Filename: "main_issue1"},
 		}, errors.New("failed to run external command: git"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -1035,9 +1036,9 @@ func Test_ReturnsAnErrorWhenGetPullRequestsFails(t *testing.T) {
 			{Oid: "6ebe3d30d23531af56bd23b5a098d3ccae2a534a", Filename: "main_issue1"},
 		}, nil, nil).
 		GetPullRequests("issue1Merged", errors.New("failed to run external command: gh"), nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -1066,9 +1067,9 @@ func Test_ReturnsAnErrorWhenGetUncommittedChangesFails(t *testing.T) {
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -1098,9 +1099,9 @@ func Test_ReturnsAnErrorWhenCheckoutBranchFails(t *testing.T) {
 			{BranchName: "branch.main.merge", Filename: "main"},
 			{BranchName: "branch.issue1.merge", Filename: "issue1"},
 		}, nil, nil)
-	remote, _ := GetRemote(s.Conn)
+	remote, _ := GetRemote(context.Background(), s.Conn)
 
-	_, err := GetBranches(remote, s.Conn, false)
+	_, err := GetBranches(context.Background(), remote, s.Conn, false)
 
 	assert.NotNil(t, err)
 }
@@ -1118,7 +1119,7 @@ func Test_DeletingDeletableBranches(t *testing.T) {
 		{true, "main", true, []string{}, []PullRequest{}, NotDeletable},
 	}
 
-	actual, _ := DeleteBranches(branches, s.Conn)
+	actual, _ := DeleteBranches(context.Background(), branches, s.Conn)
 
 	expected := []Branch{
 		{false, "issue1", false, []string{}, []PullRequest{}, Deleted},
@@ -1139,7 +1140,7 @@ func Test_DoNotDeleteNotDeletableBranches(t *testing.T) {
 		{true, "main", true, []string{}, []PullRequest{}, NotDeletable},
 	}
 
-	actual, _ := DeleteBranches(branches, s.Conn)
+	actual, _ := DeleteBranches(context.Background(), branches, s.Conn)
 
 	expected := []Branch{
 		{false, "issue1", false, []string{}, []PullRequest{}, NotDeletable},