diff --git a/docs/src/worktrees.md b/docs/src/worktrees.md
index 847cdad..72258f8 100644
--- a/docs/src/worktrees.md
+++ b/docs/src/worktrees.md
@@ -309,6 +309,10 @@ grm wt pull --rebase
[✔] my-cool-branch: Done
```
+As noted, this will fail if there are any local changes in your worktree. If you
+want to stash these changes automatically before the pull (and unstash them
+afterwards), use the `--stash` option.
+
This will rebase your changes onto the upstream branch. This is mainly helpful
for persistent branches that change on the remote side.
@@ -346,6 +350,10 @@ run two commands.
I understand that the UX is not the most intuitive. If you can think of an
improvement, please let me know (e.g. via an GitHub issue)!
+As with `pull`, `rebase` will also refuse to run when there are changes in your
+worktree. And you can also use the `--stash` option to stash/unstash changes
+automatically.
+
### Manual access
GRM isn't doing any magic, it's just git under the hood. If you need to have access
diff --git a/e2e_tests/test_worktree_fetch.py b/e2e_tests/test_worktree_fetch.py
index 1c165a9..df01462 100644
--- a/e2e_tests/test_worktree_fetch.py
+++ b/e2e_tests/test_worktree_fetch.py
@@ -2,6 +2,8 @@
from helpers import *
+import re
+
import pytest
import git
@@ -51,7 +53,9 @@ def test_worktree_fetch():
@pytest.mark.parametrize("rebase", [True, False])
@pytest.mark.parametrize("ffable", [True, False])
-def test_worktree_pull(rebase, ffable):
+@pytest.mark.parametrize("has_changes", [True, False])
+@pytest.mark.parametrize("stash", [True, False])
+def test_worktree_pull(rebase, ffable, has_changes, stash):
with TempGitRepositoryWorktree() as (base_dir, root_commit):
with TempGitFileRemote() as (remote_path, _remote_sha):
shell(
@@ -94,51 +98,79 @@ def test_worktree_pull(rebase, ffable):
"""
)
+ if has_changes:
+ shell(
+ f"""
+ cd {base_dir}/master
+ echo change >> root-commit-in-worktree-1
+ echo uncommitedchange > uncommitedchange
+ """
+ )
+
args = ["wt", "pull"]
if rebase:
args += ["--rebase"]
+ if stash:
+ args += ["--stash"]
cmd = grm(args, cwd=base_dir)
- assert cmd.returncode == 0
-
- assert repo.commit("upstream/master").hexsha == remote_commit
- assert repo.commit("origin/master").hexsha == root_commit
- assert (
- repo.commit("master").hexsha != repo.commit("origin/master").hexsha
- )
-
- if not rebase:
- if ffable:
- assert (
- repo.commit("master").hexsha
- != repo.commit("origin/master").hexsha
- )
- assert (
- repo.commit("master").hexsha
- == repo.commit("upstream/master").hexsha
- )
- assert repo.commit("upstream/master").hexsha == remote_commit
- else:
- assert "cannot be fast forwarded" in cmd.stderr
- assert (
- repo.commit("master").hexsha
- != repo.commit("origin/master").hexsha
- )
- assert repo.commit("master").hexsha != remote_commit
- assert repo.commit("upstream/master").hexsha == remote_commit
+ if has_changes and not stash:
+ assert cmd.returncode != 0
+ assert re.match(r".*master.*contains changes.*", cmd.stderr)
else:
- if ffable:
- assert (
- repo.commit("master").hexsha
- != repo.commit("origin/master").hexsha
- )
- assert (
- repo.commit("master").hexsha
- == repo.commit("upstream/master").hexsha
- )
- assert repo.commit("upstream/master").hexsha == remote_commit
+ assert repo.commit("upstream/master").hexsha == remote_commit
+ assert repo.commit("origin/master").hexsha == root_commit
+ assert (
+ repo.commit("master").hexsha
+ != repo.commit("origin/master").hexsha
+ )
+ if has_changes:
+ assert ["uncommitedchange"] == repo.untracked_files
+ assert repo.is_dirty()
else:
- assert (
- repo.commit("master").message.strip()
- == "local-commit-in-master"
- )
- assert repo.commit("master~1").hexsha == remote_commit
+ assert not repo.is_dirty()
+
+ if not rebase:
+ if ffable:
+ assert cmd.returncode == 0
+ assert (
+ repo.commit("master").hexsha
+ != repo.commit("origin/master").hexsha
+ )
+ assert (
+ repo.commit("master").hexsha
+ == repo.commit("upstream/master").hexsha
+ )
+ assert (
+ repo.commit("upstream/master").hexsha == remote_commit
+ )
+ else:
+ assert cmd.returncode != 0
+ assert "cannot be fast forwarded" in cmd.stderr
+ assert (
+ repo.commit("master").hexsha
+ != repo.commit("origin/master").hexsha
+ )
+ assert repo.commit("master").hexsha != remote_commit
+ assert (
+ repo.commit("upstream/master").hexsha == remote_commit
+ )
+ else:
+ assert cmd.returncode == 0
+ if ffable:
+ assert (
+ repo.commit("master").hexsha
+ != repo.commit("origin/master").hexsha
+ )
+ assert (
+ repo.commit("master").hexsha
+ == repo.commit("upstream/master").hexsha
+ )
+ assert (
+ repo.commit("upstream/master").hexsha == remote_commit
+ )
+ else:
+ assert (
+ repo.commit("master").message.strip()
+ == "local-commit-in-master"
+ )
+ assert repo.commit("master~1").hexsha == remote_commit
diff --git a/e2e_tests/test_worktree_rebase.py b/e2e_tests/test_worktree_rebase.py
index c913aaf..a64f755 100644
--- a/e2e_tests/test_worktree_rebase.py
+++ b/e2e_tests/test_worktree_rebase.py
@@ -2,15 +2,18 @@
from helpers import *
-import pytest
+import re
+import pytest
import git
@pytest.mark.parametrize("pull", [True, False])
@pytest.mark.parametrize("rebase", [True, False])
@pytest.mark.parametrize("ffable", [True, False])
-def test_worktree_rebase(pull, rebase, ffable):
+@pytest.mark.parametrize("has_changes", [True, False])
+@pytest.mark.parametrize("stash", [True, False])
+def test_worktree_rebase(pull, rebase, ffable, has_changes, stash):
with TempGitRepositoryWorktree() as (base_dir, _root_commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write('persistent_branches = ["mybasebranch"]')
@@ -83,6 +86,14 @@ def test_worktree_rebase(pull, rebase, ffable):
"""
)
+ if has_changes:
+ shell(
+ f"""
+ cd {base_dir}/myfeatbranch
+ echo uncommitedchange > uncommitedchange
+ """
+ )
+
grm(["wt", "delete", "--force", "tmp"], cwd=base_dir)
repo = git.Repo(f"{base_dir}/.git-main-working-tree")
@@ -133,17 +144,23 @@ def test_worktree_rebase(pull, rebase, ffable):
args += ["--pull"]
if rebase:
args += ["--rebase"]
+ if stash:
+ args += ["--stash"]
cmd = grm(args, cwd=base_dir)
- print(args)
if rebase and not pull:
assert cmd.returncode != 0
assert len(cmd.stderr) != 0
+ elif has_changes and not stash:
+ assert cmd.returncode != 0
+ assert re.match(r".*myfeatbranch.*contains changes.*", cmd.stderr)
else:
- assert cmd.returncode == 0
repo = git.Repo(f"{base_dir}/myfeatbranch")
+ if has_changes:
+ assert ["uncommitedchange"] == repo.untracked_files
if pull:
if rebase:
+ assert cmd.returncode == 0
if ffable:
assert (
repo.commit("HEAD").message.strip()
@@ -190,6 +207,7 @@ def test_worktree_rebase(pull, rebase, ffable):
assert repo.commit("HEAD~6").message.strip() == "commit-root"
else:
if ffable:
+ assert cmd.returncode == 0
assert (
repo.commit("HEAD").message.strip()
== "commit-in-feat-remote"
@@ -208,6 +226,7 @@ def test_worktree_rebase(pull, rebase, ffable):
)
assert repo.commit("HEAD~4").message.strip() == "commit-root"
else:
+ assert cmd.returncode != 0
assert (
repo.commit("HEAD").message.strip()
== "commit-in-feat-local-no-ff"
@@ -226,6 +245,7 @@ def test_worktree_rebase(pull, rebase, ffable):
)
assert repo.commit("HEAD~4").message.strip() == "commit-root"
else:
+ assert cmd.returncode == 0
if ffable:
assert repo.commit("HEAD").message.strip() == "commit-in-feat-local"
assert (
diff --git a/src/grm/cmd.rs b/src/grm/cmd.rs
index 7588499..4307a66 100644
--- a/src/grm/cmd.rs
+++ b/src/grm/cmd.rs
@@ -132,6 +132,8 @@ pub struct WorktreeFetchArgs {}
pub struct WorktreePullArgs {
#[clap(long = "--rebase", help = "Perform a rebase instead of a fast-forward")]
pub rebase: bool,
+ #[clap(long = "--stash", help = "Stash & unstash changes before & after pull")]
+ pub stash: bool,
}
#[derive(Parser)]
@@ -140,6 +142,11 @@ pub struct WorktreeRebaseArgs {
pub pull: bool,
#[clap(long = "--rebase", help = "Perform a rebase when doing a pull")]
pub rebase: bool,
+ #[clap(
+ long = "--stash",
+ help = "Stash & unstash changes before & after rebase"
+ )]
+ pub stash: bool,
}
pub fn parse() -> Opts {
diff --git a/src/grm/main.rs b/src/grm/main.rs
index 9a2996a..bc63641 100644
--- a/src/grm/main.rs
+++ b/src/grm/main.rs
@@ -350,26 +350,27 @@ fn main() {
process::exit(1);
});
+ let mut failures = false;
for worktree in repo.get_worktrees().unwrap_or_else(|error| {
print_error(&format!("Error getting worktrees: {}", error));
process::exit(1);
}) {
- if let Some(warning) =
- worktree
- .forward_branch(args.rebase)
- .unwrap_or_else(|error| {
- print_error(&format!(
- "Error updating worktree branch: {}",
- error
- ));
- process::exit(1);
- })
+ if let Some(warning) = worktree
+ .forward_branch(args.rebase, args.stash)
+ .unwrap_or_else(|error| {
+ print_error(&format!("Error updating worktree branch: {}", error));
+ process::exit(1);
+ })
{
print_warning(&format!("{}: {}", worktree.name(), warning));
+ failures = true;
} else {
print_success(&format!("{}: Done", worktree.name()));
}
}
+ if failures {
+ process::exit(1);
+ }
}
cmd::WorktreeAction::Rebase(args) => {
if args.rebase && !args.pull {
@@ -406,10 +407,12 @@ fn main() {
process::exit(1);
});
+ let mut failures = false;
+
for worktree in &worktrees {
if args.pull {
if let Some(warning) = worktree
- .forward_branch(args.rebase)
+ .forward_branch(args.rebase, args.stash)
.unwrap_or_else(|error| {
print_error(&format!(
"Error updating worktree branch: {}",
@@ -418,28 +421,29 @@ fn main() {
process::exit(1);
})
{
+ failures = true;
print_warning(&format!("{}: {}", worktree.name(), warning));
}
}
}
for worktree in &worktrees {
- if let Some(warning) =
- worktree
- .rebase_onto_default(&config)
- .unwrap_or_else(|error| {
- print_error(&format!(
- "Error rebasing worktree branch: {}",
- error
- ));
- process::exit(1);
- })
+ if let Some(warning) = worktree
+ .rebase_onto_default(&config, args.stash)
+ .unwrap_or_else(|error| {
+ print_error(&format!("Error rebasing worktree branch: {}", error));
+ process::exit(1);
+ })
{
+ failures = true;
print_warning(&format!("{}: {}", worktree.name(), warning));
} else {
print_success(&format!("{}: Done", worktree.name()));
}
}
+ if failures {
+ process::exit(1);
+ }
}
}
}
diff --git a/src/repo.rs b/src/repo.rs
index f6adb0d..f2cc015 100644
--- a/src/repo.rs
+++ b/src/repo.rs
@@ -181,17 +181,30 @@ impl Worktree {
&self.name
}
- pub fn forward_branch(&self, rebase: bool) -> Result