From 7b6f1cd33f2bac0180193d0d0db22137ca3f9fac Mon Sep 17 00:00:00 2001 From: Xiaoyang Tan Date: Wed, 25 Feb 2026 18:26:12 -0800 Subject: [PATCH 1/2] git reset --- core/git/git.go | 16 ++++++++++++++++ core/git/gitmock/gitmock.go | 28 ++++++++++++++++++++++++++++ core/workspace/workspace.go | 10 +++++++++- core/workspace/workspace_test.go | 5 ++++- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/core/git/git.go b/core/git/git.go index 710b9666..f12e97dc 100644 --- a/core/git/git.go +++ b/core/git/git.go @@ -27,6 +27,8 @@ type Interface interface { ApplyPatch(ctx context.Context, patch []byte) error RevParse(ctx context.Context, ref string) (string, error) Commit(ctx context.Context, message string, options ...string) error + Clean(ctx context.Context) error + Reset(ctx context.Context) error SubmoduleUpdate(ctx context.Context) error FileHashes(ctx context.Context, ref string) (map[string][]byte, error) } @@ -102,6 +104,20 @@ func (c *impl) Commit(ctx context.Context, message string, options ...string) er return c.runner.run(ctx, c.directory, "git", args...) } +// Clean removes untracked files and directories (git clean -fdx). +func (c *impl) Clean(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, _gitTimeout) + defer cancel() + return c.runner.run(ctx, c.directory, "git", "clean", "-fdx") +} + +// Reset discards all local changes (git reset --hard). +func (c *impl) Reset(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, _gitTimeout) + defer cancel() + return c.runner.run(ctx, c.directory, "git", "reset", "--hard") +} + // SubmoduleUpdate updates the submodules in the repository. func (c *impl) SubmoduleUpdate(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, _gitTimeout) diff --git a/core/git/gitmock/gitmock.go b/core/git/gitmock/gitmock.go index d902db12..73b6d439 100644 --- a/core/git/gitmock/gitmock.go +++ b/core/git/gitmock/gitmock.go @@ -54,6 +54,20 @@ func (mr *MockInterfaceMockRecorder) ApplyPatch(ctx, patch any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyPatch", reflect.TypeOf((*MockInterface)(nil).ApplyPatch), ctx, patch) } +// Clean mocks base method. +func (m *MockInterface) Clean(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clean", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// Clean indicates an expected call of Clean. +func (mr *MockInterfaceMockRecorder) Clean(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockInterface)(nil).Clean), ctx) +} + // Checkout mocks base method. func (m *MockInterface) Checkout(ctx context.Context, ref string, options ...string) error { m.ctrl.T.Helper() @@ -165,6 +179,20 @@ func (mr *MockInterfaceMockRecorder) FileHashes(ctx, ref any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileHashes", reflect.TypeOf((*MockInterface)(nil).FileHashes), ctx, ref) } +// Reset mocks base method. +func (m *MockInterface) Reset(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Reset", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// Reset indicates an expected call of Reset. +func (mr *MockInterfaceMockRecorder) Reset(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockInterface)(nil).Reset), ctx) +} + // RevParse mocks base method. func (m *MockInterface) RevParse(ctx context.Context, ref string) (string, error) { m.ctrl.T.Helper() diff --git a/core/workspace/workspace.go b/core/workspace/workspace.go index 9d297dc9..88fe615d 100644 --- a/core/workspace/workspace.go +++ b/core/workspace/workspace.go @@ -53,8 +53,16 @@ func (w *workspace) ApplyRequests(ctx context.Context, requests []Request) error return nil } -// Checkout checks out the given reference in the workspace. +// Checkout resets the workspace to a clean state and checks out the given ref. func (w *workspace) Checkout(ctx context.Context, remote string, ref string) error { + // Reset any local modifications so the checkout starts clean. + if err := w.git.Reset(ctx); err != nil { + return fmt.Errorf("git reset --hard: %w", err) + } + if err := w.git.Clean(ctx); err != nil { + return fmt.Errorf("git clean -fdx: %w", err) + } + // check if base ref exists in local repository commit := fmt.Sprintf("%s^{commit}", ref) _, err := w.git.RevParse(ctx, commit) diff --git a/core/workspace/workspace_test.go b/core/workspace/workspace_test.go index 4d09decf..610c6a66 100644 --- a/core/workspace/workspace_test.go +++ b/core/workspace/workspace_test.go @@ -70,8 +70,9 @@ func TestWorkspace_Checkout_RevParseSuccess(t *testing.T) { ref := "abc123" commitRef := ref + "^{commit}" + g.EXPECT().Reset(gomock.Any()).Return(nil) + g.EXPECT().Clean(gomock.Any()).Return(nil) g.EXPECT().RevParse(gomock.Any(), commitRef).Return("abc123", nil) - // expect Checkout with commit hash and workspace path as option g.EXPECT().Checkout(gomock.Any(), "abc123").Return(nil) err := w.Checkout(context.Background(), "origin", ref) @@ -89,6 +90,8 @@ func TestWorkspace_Checkout_FetchError(t *testing.T) { ref := "refs/heads/feature" commitRef := ref + "^{commit}" + g.EXPECT().Reset(gomock.Any()).Return(nil) + g.EXPECT().Clean(gomock.Any()).Return(nil) g.EXPECT().RevParse(gomock.Any(), commitRef).Return("", errors.New("missing")) g.EXPECT().Fetch(gomock.Any(), remote, ref).Return(errors.New("fetch failed")) From 4e9f6db5271541627ab56d1d4fa148dd502ca3b1 Mon Sep 17 00:00:00 2001 From: xiao <48696694+xytan0056@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:28:52 -0800 Subject: [PATCH 2/2] Update workspace.go --- core/workspace/workspace.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/workspace/workspace.go b/core/workspace/workspace.go index 88fe615d..a074a32b 100644 --- a/core/workspace/workspace.go +++ b/core/workspace/workspace.go @@ -55,7 +55,6 @@ func (w *workspace) ApplyRequests(ctx context.Context, requests []Request) error // Checkout resets the workspace to a clean state and checks out the given ref. func (w *workspace) Checkout(ctx context.Context, remote string, ref string) error { - // Reset any local modifications so the checkout starts clean. if err := w.git.Reset(ctx); err != nil { return fmt.Errorf("git reset --hard: %w", err) }