From e0d964094f20a0d196b8387620bf2c88ddb3e0fd Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:10:58 +0530 Subject: [PATCH 1/5] Add flag in rill deploy to force push changes for rill managed repos --- cli/cmd/deploy/deploy.go | 5 +- cli/cmd/project/deploy.go | 17 +++- cli/pkg/cmdutil/githelper.go | 5 +- cli/pkg/local/server.go | 2 +- runtime/pkg/gitutil/commit.go | 34 ++++++++ runtime/pkg/gitutil/commit_test.go | 120 +++++++++++++++++++++++++++++ runtime/pkg/gitutil/sync.go | 8 ++ 7 files changed, 184 insertions(+), 7 deletions(-) diff --git a/cli/cmd/deploy/deploy.go b/cli/cmd/deploy/deploy.go index 0fa74e9ba26a..a6b8ca98d3b1 100644 --- a/cli/cmd/deploy/deploy.go +++ b/cli/cmd/deploy/deploy.go @@ -72,7 +72,6 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { deployCmd.Flags().StringVar(&opts.PrimaryBranch, "primary-branch", "", "Git branch to deploy from (default: the default Git branch)") deployCmd.Flags().IntVar(&opts.Slots, "prod-slots", local.DefaultProdSlots(ch), "Slots to allocate for production deployments") deployCmd.Flags().IntVar(&opts.DevSlots, "dev-slots", local.DefaultDevSlots(ch), "Slots to allocate for dev deployments") - deployCmd.Flags().BoolVar(&opts.PushEnv, "push-env", true, "Push local .env file to Rill Cloud") if !ch.IsDev() { if err := deployCmd.Flags().MarkHidden("prod-slots"); err != nil { panic(err) @@ -81,6 +80,10 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { panic(err) } } + deployCmd.Flags().BoolVar(&opts.PushEnv, "push-env", true, "Push local .env file to Rill Cloud") + + deployCmd.Flags().BoolVar(&opts.ForcePush, "", false, "Force push local changes in case of Rill managed repos") + deployCmd.MarkFlagsMutuallyExclusive("force-push", "github") deployCmd.Flags().BoolVar(&opts.Managed, "managed", false, "Create project using rill managed repo") deployCmd.Flags().BoolVar(&opts.ArchiveUpload, "archive", false, "Create project using tarballs(for testing only)") diff --git a/cli/cmd/project/deploy.go b/cli/cmd/project/deploy.go index 152a3e91a553..316fad96a776 100644 --- a/cli/cmd/project/deploy.go +++ b/cli/cmd/project/deploy.go @@ -38,6 +38,7 @@ type DeployOpts struct { Slots int DevSlots int PushEnv bool + ForcePush bool ArchiveUpload bool // Managed indicates if the project should be deployed using Rill Managed Git. @@ -90,6 +91,9 @@ func (o *DeployOpts) ValidateAndApplyDefaults(ctx context.Context, ch *cmdutil.H return err } if exists { + if p.ManagedGitId == "" && p.ArchiveAssetId == "" && o.ForcePush { + return fmt.Errorf("project %q/%q is connected to a GitHub repository. Cannot use --force-push flag", ch.Org, o.Name) + } if ch.Interactive { if err := cmdutil.ConfirmPrompt(fmt.Sprintf("Project with name %q already exists. Do you want to push current changes to the existing project?", o.Name), true); err != nil { return err @@ -128,9 +132,14 @@ func (o *DeployOpts) ValidateAndApplyDefaults(ctx context.Context, ch *cmdutil.H // if there is a project already connected to this repo+subpath offer to push changes to it if o.pushToProject != nil { - if o.pushToProject.ManagedGitId == "" && o.Managed { - ch.PrintfError("Project %s/%s is already connected to this GitHub repository. Cannot use --managed flag.\n", o.pushToProject.OrgName, o.pushToProject.Name) - return fmt.Errorf("aborting deploy") + if o.pushToProject.ManagedGitId == "" { + if o.Managed { + ch.PrintfError("Project %s/%s is already connected to this GitHub repository. Cannot use --managed flag.\n", o.pushToProject.OrgName, o.pushToProject.Name) + return fmt.Errorf("aborting deploy") + } + if o.ForcePush { + return fmt.Errorf("project %s/%s is connected to a GitHub repository. Cannot use --force-push flag", o.pushToProject.OrgName, o.pushToProject.Name) + } } if o.pushToProject.ManagedGitId != "" && o.Github { ch.Printf("Found another rill managed project %s/%s connected to this folder\n", o.pushToProject.OrgName, o.pushToProject.Name) @@ -464,7 +473,7 @@ func redeployProject(ctx context.Context, ch *cmdutil.Helper, opts *DeployOpts) } proj := opts.pushToProject if proj.ManagedGitId != "" { - err := ch.GitHelper(ch.Org, proj.Name, opts.LocalProjectPath()).PushToManagedRepo(ctx) + err := ch.GitHelper(ch.Org, proj.Name, opts.LocalProjectPath()).PushToManagedRepo(ctx, opts.ForcePush) if err != nil { return err } diff --git a/cli/pkg/cmdutil/githelper.go b/cli/pkg/cmdutil/githelper.go index a135d1ed2897..8ea313ca57b5 100644 --- a/cli/pkg/cmdutil/githelper.go +++ b/cli/pkg/cmdutil/githelper.go @@ -117,7 +117,7 @@ func (g *GitHelper) PushToNewManagedRepo(ctx context.Context, primaryBranch stri return gitRepo, nil } -func (g *GitHelper) PushToManagedRepo(ctx context.Context) error { +func (g *GitHelper) PushToManagedRepo(ctx context.Context, forcePush bool) error { gitConfig, err := g.GitConfig(ctx) if err != nil { return err @@ -127,6 +127,9 @@ func (g *GitHelper) PushToManagedRepo(ctx context.Context) error { if err != nil { return err } + if forcePush { + return gitutil.CommitAndForcePush(ctx, g.localPath, gitConfig, "", author) + } err = g.h.CommitAndSafePush(ctx, g.localPath, gitConfig, "", author, "1") if err != nil { return err diff --git a/cli/pkg/local/server.go b/cli/pkg/local/server.go index f5870397813a..7b419b6c3da1 100644 --- a/cli/pkg/local/server.go +++ b/cli/pkg/local/server.go @@ -517,7 +517,7 @@ func (s *Server) RedeployProject(ctx context.Context, r *connect.Request[localv1 } else if r.Msg.Reupload { if projResp.Project.ManagedGitId != "" { // If rill-managed project then push to the repo based on org/project passed in. - err = s.app.ch.GitHelper(projResp.Project.OrgName, projResp.Project.Name, s.app.ProjectPath).PushToManagedRepo(ctx) + err = s.app.ch.GitHelper(projResp.Project.OrgName, projResp.Project.Name, s.app.ProjectPath).PushToManagedRepo(ctx, false) if err != nil { return nil, err } diff --git a/runtime/pkg/gitutil/commit.go b/runtime/pkg/gitutil/commit.go index 840fbc7c3d4c..641aafb99de8 100644 --- a/runtime/pkg/gitutil/commit.go +++ b/runtime/pkg/gitutil/commit.go @@ -166,6 +166,40 @@ func CommitAndPush(ctx context.Context, path string, config *Config, commitMsg s return Push(ctx, path, remote, config.DefaultBranch) } +// CommitAndForcePush stages and commits all changes at path (scoped to config.Subpath if set) and +// force-pushes HEAD to config.DefaultBranch on the remote described by config. +// Unlike CommitAndPush, the current local branch need not match config.DefaultBranch, and HEAD may +// be detached. If there is nothing new to commit, it still attempts the push. +func CommitAndForcePush(ctx context.Context, path string, config *Config, commitMsg string, author Signature) error { + err := EnsureInit(ctx, path, config.DefaultBranch) + if err != nil { + return fmt.Errorf("failed to init git repo: %w", err) + } + + if commitMsg == "" { + commitMsg = "Auto committed by Rill" + } + _, err = CommitAll(ctx, path, config.Subpath, commitMsg, author) + if err != nil && !errors.Is(err, ErrEmptyCommit) { + return fmt.Errorf("failed to commit files to git: %w", err) + } + + err = SetRemote(path, config) + if err != nil { + return err + } + + if config.Username == "" { + return ForcePush(ctx, path, config.RemoteName(), config.DefaultBranch) + } + + remote, err := config.FullyQualifiedRemote() + if err != nil { + return err + } + return ForcePush(ctx, path, remote, config.DefaultBranch) +} + // IsCommitHash reports whether s is a full hex commit hash (SHA-1 or SHA-256). // Use it to validate untrusted hashes before passing them as git CLI arguments: it rules out // strings that git would interpret as flags or other revision syntax. diff --git a/runtime/pkg/gitutil/commit_test.go b/runtime/pkg/gitutil/commit_test.go index 83fd30d41b6c..21c6bf6497fb 100644 --- a/runtime/pkg/gitutil/commit_test.go +++ b/runtime/pkg/gitutil/commit_test.go @@ -9,6 +9,126 @@ import ( "github.com/stretchr/testify/require" ) +func TestCommitAndForcePush(t *testing.T) { + ctx := context.Background() + author := Signature{Name: "Rill", Email: "noreply@rilldata.com"} + + t.Run("succeeds on matching branch with uncommitted changes", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + branch := getCurrentBranch(t, local) + + require.NoError(t, os.WriteFile(filepath.Join(local, "new.txt"), []byte("hello"), 0644)) + + config := &Config{Remote: remote, DefaultBranch: branch} + require.NoError(t, CommitAndForcePush(ctx, local, config, "force push commit", author)) + + localTip, err := Hash(ctx, local, "HEAD") + require.NoError(t, err) + remoteTip, err := Hash(ctx, remote, "refs/heads/"+branch) + require.NoError(t, err) + require.Equal(t, localTip, remoteTip) + }) + + t.Run("pushes even when there is nothing to commit", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + branch := getCurrentBranch(t, local) + + // create a local-only commit with nothing left in the working tree + createCommit(t, local, "unpushed.txt", "content", "local-only commit") + localTip, err := Hash(ctx, local, "HEAD") + require.NoError(t, err) + + config := &Config{Remote: remote, DefaultBranch: branch} + require.NoError(t, CommitAndForcePush(ctx, local, config, "", author)) + + remoteTip, err := Hash(ctx, remote, "refs/heads/"+branch) + require.NoError(t, err) + require.Equal(t, localTip, remoteTip, "local-only commit must reach the remote") + }) + + t.Run("succeeds from a detached HEAD", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + branch := getCurrentBranch(t, local) + + require.NoError(t, execGit(local, "checkout", "--detach")) + require.NoError(t, os.WriteFile(filepath.Join(local, "detached.txt"), []byte("from detached HEAD"), 0644)) + + config := &Config{Remote: remote, DefaultBranch: branch} + require.NoError(t, CommitAndForcePush(ctx, local, config, "detached commit", author)) + + localTip, err := Hash(ctx, local, "HEAD") + require.NoError(t, err) + remoteTip, err := Hash(ctx, remote, "refs/heads/"+branch) + require.NoError(t, err) + require.Equal(t, localTip, remoteTip, "force push from detached HEAD must update the remote branch") + }) + + t.Run("succeeds from a different local branch", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + defaultBranch := getCurrentBranch(t, local) + + require.NoError(t, execGit(local, "checkout", "-b", "feature")) + require.NoError(t, os.WriteFile(filepath.Join(local, "feature.txt"), []byte("feature work"), 0644)) + + config := &Config{Remote: remote, DefaultBranch: defaultBranch} + require.NoError(t, CommitAndForcePush(ctx, local, config, "feature commit", author)) + + localTip, err := Hash(ctx, local, "HEAD") + require.NoError(t, err) + remoteTip, err := Hash(ctx, remote, "refs/heads/"+defaultBranch) + require.NoError(t, err) + require.Equal(t, localTip, remoteTip, "remote default branch must be overwritten with the local feature branch tip") + }) + + t.Run("overwrites divergent remote history", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + branch := getCurrentBranch(t, local) + + // advance the remote with a commit that local doesn't have + createRemoteCommit(t, remote, "remote-only.txt", "remote content", "remote-only commit") + + // advance local with a different commit, creating a true divergence from the initial tip + createCommit(t, local, "local-only.txt", "local content", "local-only commit") + + // add uncommitted changes so CommitAll also has work to do + require.NoError(t, os.WriteFile(filepath.Join(local, "extra.txt"), []byte("extra"), 0644)) + + config := &Config{Remote: remote, DefaultBranch: branch} + require.NoError(t, CommitAndForcePush(ctx, local, config, "force over divergence", author)) + + localTip, err := Hash(ctx, local, "HEAD") + require.NoError(t, err) + remoteTip, err := Hash(ctx, remote, "refs/heads/"+branch) + require.NoError(t, err) + require.Equal(t, localTip, remoteTip, "force push must overwrite the divergent remote history") + }) + + t.Run("scopes the commit to the subpath", func(t *testing.T) { + local, remote := setupRepoWithRemote(t) + branch := getCurrentBranch(t, local) + + require.NoError(t, os.MkdirAll(filepath.Join(local, "sub"), 0755)) + createCommit(t, local, "sub/seeded.txt", "seeded", "seed subpath") + require.NoError(t, execGit(local, "push", "origin", branch)) + + require.NoError(t, os.WriteFile(filepath.Join(local, "sub", "inside.txt"), []byte("inside"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(local, "outside.txt"), []byte("outside"), 0644)) + + config := &Config{Remote: remote, DefaultBranch: branch, Subpath: "sub"} + require.NoError(t, CommitAndForcePush(ctx, local, config, "subpath commit", author)) + + committed, err := Run(ctx, local, "show", "--name-status", "--pretty=format:", "HEAD") + require.NoError(t, err) + require.Contains(t, committed, "sub/inside.txt", "changes inside the subpath must be committed") + require.NotContains(t, committed, "outside.txt", "changes outside the subpath must not be committed") + + // the outside change is left untouched in the working tree + status, err := Run(ctx, local, "status", "--porcelain") + require.NoError(t, err) + require.Contains(t, status, "outside.txt") + }) +} + func TestCurrentBranch(t *testing.T) { ctx := context.Background() diff --git a/runtime/pkg/gitutil/sync.go b/runtime/pkg/gitutil/sync.go index 28a7926f7fc2..958db91f3036 100644 --- a/runtime/pkg/gitutil/sync.go +++ b/runtime/pkg/gitutil/sync.go @@ -174,6 +174,14 @@ func Push(ctx context.Context, path, remote, refspec string) error { return err } +// ForcePush force-pushes HEAD to branch on remote, overwriting the remote ref unconditionally. +// It uses the refspec HEAD: so it works even when the local HEAD is detached or points +// to a different branch. remote may be a remote name or a URL with embedded credentials. +func ForcePush(ctx context.Context, path, remote, branch string) error { + _, err := Run(ctx, path, "push", "--force", remote, "HEAD:"+branch) + return err +} + // UpstreamMerge merges the remote tracking branch `/` into the current branch. // If favourLocal is true, merge conflicts are resolved in favour of local changes. func UpstreamMerge(ctx context.Context, path, remoteName, branch string, favourLocal bool) error { From 49ae0ee38b98b55ee93111f7d1c9522702bf9205 Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:15:22 +0530 Subject: [PATCH 2/5] docs generate --- cli/cmd/deploy/deploy.go | 7 ++----- cli/cmd/project/deploy.go | 3 ++- docs/docs/reference/cli/deploy.md | 1 + docs/docs/reference/cli/project/deploy.md | 1 + 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/cmd/deploy/deploy.go b/cli/cmd/deploy/deploy.go index a6b8ca98d3b1..e17aac39948b 100644 --- a/cli/cmd/deploy/deploy.go +++ b/cli/cmd/deploy/deploy.go @@ -81,10 +81,7 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { } } deployCmd.Flags().BoolVar(&opts.PushEnv, "push-env", true, "Push local .env file to Rill Cloud") - - deployCmd.Flags().BoolVar(&opts.ForcePush, "", false, "Force push local changes in case of Rill managed repos") - deployCmd.MarkFlagsMutuallyExclusive("force-push", "github") - + deployCmd.Flags().BoolVar(&opts.ForcePush, "force-push", false, "Force push local changes in case of Rill managed repos") deployCmd.Flags().BoolVar(&opts.Managed, "managed", false, "Create project using rill managed repo") deployCmd.Flags().BoolVar(&opts.ArchiveUpload, "archive", false, "Create project using tarballs(for testing only)") err := deployCmd.Flags().MarkHidden("archive") @@ -95,7 +92,7 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { // subpath cannot be used with archive or managed deploys deployCmd.MarkFlagsMutuallyExclusive("managed", "archive", "subpath") deployCmd.MarkFlagsMutuallyExclusive("managed", "archive", "github") - + deployCmd.MarkFlagsMutuallyExclusive("force-push", "github") deployCmd.Flags().BoolVar(&opts.SkipDeploy, "skip-deploy", false, "Skip the runtime deployment step (for testing only)") if !ch.IsDev() { err = deployCmd.Flags().MarkHidden("skip-deploy") diff --git a/cli/cmd/project/deploy.go b/cli/cmd/project/deploy.go index 316fad96a776..eb892aa1f034 100644 --- a/cli/cmd/project/deploy.go +++ b/cli/cmd/project/deploy.go @@ -297,7 +297,6 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { deployCmd.Flags().StringVar(&opts.PrimaryBranch, "primary-branch", "", "Git branch to deploy from (default: the default Git branch)") deployCmd.Flags().IntVar(&opts.Slots, "prod-slots", local.DefaultProdSlots(ch), "Slots to allocate for production deployments") deployCmd.Flags().IntVar(&opts.DevSlots, "dev-slots", local.DefaultDevSlots(ch), "Slots to allocate for dev deployments") - deployCmd.Flags().BoolVar(&opts.PushEnv, "push-env", true, "Push local .env file to Rill Cloud") if !ch.IsDev() { if err := deployCmd.Flags().MarkHidden("prod-slots"); err != nil { panic(err) @@ -307,6 +306,8 @@ func DeployCmd(ch *cmdutil.Helper) *cobra.Command { } } + deployCmd.Flags().BoolVar(&opts.PushEnv, "push-env", true, "Push local .env file to Rill Cloud") + deployCmd.Flags().BoolVar(&opts.ForcePush, "force-push", false, "Force push local changes") deployCmd.Flags().BoolVar(&opts.SkipDeploy, "skip-deploy", false, "Skip the runtime deployment step (for testing only)") if !ch.IsDev() { err := deployCmd.Flags().MarkHidden("skip-deploy") diff --git a/docs/docs/reference/cli/deploy.md b/docs/docs/reference/cli/deploy.md index ec6b0b154dce..4ef349e4a6dc 100644 --- a/docs/docs/reference/cli/deploy.md +++ b/docs/docs/reference/cli/deploy.md @@ -23,6 +23,7 @@ rill deploy [] [flags] --provisioner string Project provisioner --primary-branch string Git branch to deploy from (default: the default Git branch) --push-env Push local .env file to Rill Cloud (default true) + --force-push Force push local changes in case of Rill managed repos --managed Create project using rill managed repo --github Use github repo to create the project ``` diff --git a/docs/docs/reference/cli/project/deploy.md b/docs/docs/reference/cli/project/deploy.md index e3919d95e2fd..e927a739b519 100644 --- a/docs/docs/reference/cli/project/deploy.md +++ b/docs/docs/reference/cli/project/deploy.md @@ -22,6 +22,7 @@ rill project deploy [] [flags] --provisioner string Project provisioner --primary-branch string Git branch to deploy from (default: the default Git branch) --push-env Push local .env file to Rill Cloud (default true) + --force-push Force push local changes ``` ### Global flags From 058babcb27ea34bcb2534158e2ab85d5da7dd05e Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:18:01 +0530 Subject: [PATCH 3/5] self review --- runtime/pkg/gitutil/commit.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/pkg/gitutil/commit.go b/runtime/pkg/gitutil/commit.go index 641aafb99de8..851747e471c1 100644 --- a/runtime/pkg/gitutil/commit.go +++ b/runtime/pkg/gitutil/commit.go @@ -166,10 +166,9 @@ func CommitAndPush(ctx context.Context, path string, config *Config, commitMsg s return Push(ctx, path, remote, config.DefaultBranch) } -// CommitAndForcePush stages and commits all changes at path (scoped to config.Subpath if set) and -// force-pushes HEAD to config.DefaultBranch on the remote described by config. +// CommitAndForcePush is similar to CommitAndPush but force pushes the local changes to the remote. // Unlike CommitAndPush, the current local branch need not match config.DefaultBranch, and HEAD may -// be detached. If there is nothing new to commit, it still attempts the push. +// be detached. func CommitAndForcePush(ctx context.Context, path string, config *Config, commitMsg string, author Signature) error { err := EnsureInit(ctx, path, config.DefaultBranch) if err != nil { From b909cd418e0902c6153da2b898d461097a78cd90 Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:23:36 +0530 Subject: [PATCH 4/5] review comment --- runtime/pkg/gitutil/commit.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/pkg/gitutil/commit.go b/runtime/pkg/gitutil/commit.go index 851747e471c1..48a38547d226 100644 --- a/runtime/pkg/gitutil/commit.go +++ b/runtime/pkg/gitutil/commit.go @@ -170,6 +170,10 @@ func CommitAndPush(ctx context.Context, path string, config *Config, commitMsg s // Unlike CommitAndPush, the current local branch need not match config.DefaultBranch, and HEAD may // be detached. func CommitAndForcePush(ctx context.Context, path string, config *Config, commitMsg string, author Signature) error { + if config.Subpath != "" { + // this is ensured upstream but just to be safe + return fmt.Errorf("force push does not support subpath, please commit and push from the root of the repository") + } err := EnsureInit(ctx, path, config.DefaultBranch) if err != nil { return fmt.Errorf("failed to init git repo: %w", err) @@ -178,7 +182,7 @@ func CommitAndForcePush(ctx context.Context, path string, config *Config, commit if commitMsg == "" { commitMsg = "Auto committed by Rill" } - _, err = CommitAll(ctx, path, config.Subpath, commitMsg, author) + _, err = CommitAll(ctx, path, "", commitMsg, author) if err != nil && !errors.Is(err, ErrEmptyCommit) { return fmt.Errorf("failed to commit files to git: %w", err) } From b1475228ab92ac8d03c9d8e5540c25d0d27d2a41 Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:27:07 +0530 Subject: [PATCH 5/5] fix test --- runtime/pkg/gitutil/commit_test.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/runtime/pkg/gitutil/commit_test.go b/runtime/pkg/gitutil/commit_test.go index 21c6bf6497fb..c2d928c20329 100644 --- a/runtime/pkg/gitutil/commit_test.go +++ b/runtime/pkg/gitutil/commit_test.go @@ -102,31 +102,6 @@ func TestCommitAndForcePush(t *testing.T) { require.NoError(t, err) require.Equal(t, localTip, remoteTip, "force push must overwrite the divergent remote history") }) - - t.Run("scopes the commit to the subpath", func(t *testing.T) { - local, remote := setupRepoWithRemote(t) - branch := getCurrentBranch(t, local) - - require.NoError(t, os.MkdirAll(filepath.Join(local, "sub"), 0755)) - createCommit(t, local, "sub/seeded.txt", "seeded", "seed subpath") - require.NoError(t, execGit(local, "push", "origin", branch)) - - require.NoError(t, os.WriteFile(filepath.Join(local, "sub", "inside.txt"), []byte("inside"), 0644)) - require.NoError(t, os.WriteFile(filepath.Join(local, "outside.txt"), []byte("outside"), 0644)) - - config := &Config{Remote: remote, DefaultBranch: branch, Subpath: "sub"} - require.NoError(t, CommitAndForcePush(ctx, local, config, "subpath commit", author)) - - committed, err := Run(ctx, local, "show", "--name-status", "--pretty=format:", "HEAD") - require.NoError(t, err) - require.Contains(t, committed, "sub/inside.txt", "changes inside the subpath must be committed") - require.NotContains(t, committed, "outside.txt", "changes outside the subpath must not be committed") - - // the outside change is left untouched in the working tree - status, err := Run(ctx, local, "status", "--porcelain") - require.NoError(t, err) - require.Contains(t, status, "outside.txt") - }) } func TestCurrentBranch(t *testing.T) {