Skip to content

Commit 1848afa

Browse files
committed
Fix #78: clear error when a configured branch does not exist
- git_checkout detects 'remote branch not found' clone failures and raises a message naming the package, branch and URL, pointing at the mx.ini setting. - git_switch_branch and git_merge_rbranch raise the same clear GitError instead of logging a terse 'No such branch' and calling sys.exit(1), so the update path behaves consistently with checkout. - The worker reports expected WCErrors as a plain error message and keeps the full traceback at debug level only.
1 parent d16dc4a commit 1848afa

5 files changed

Lines changed: 38 additions & 10 deletions

File tree

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
with a `# managed by mxdev` marker and prunes managed entries that are no longer
1414
configured, while leaving user-defined sources untouched. [jensens]
1515

16+
- Fix #78: Give a clear, actionable error when a configured `branch` does not exist on the
17+
remote (e.g. it was deleted), naming the package, branch and URL and pointing at the
18+
`mx.ini` setting, on both checkout and update. Expected VCS errors are no longer reported
19+
with a full Python traceback (the traceback is kept at debug level). [jensens]
20+
1621

1722
## 5.3.2 (2026-05-30)
1823

src/mxdev/vcs/common.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,14 @@ def worker(working_copies: WorkingCopies, the_queue: queue.Queue) -> None:
426426
return
427427
try:
428428
output = action(**kwargs)
429-
except WCError:
429+
except WCError as e:
430430
with output_lock:
431431
for lvl, msg in wc._output:
432432
lvl(msg)
433-
logger.exception("Can not execute action!")
433+
# WCError is an expected operational failure: show a clean,
434+
# actionable message and keep the full traceback for debug only.
435+
logger.error("%s", e)
436+
logger.debug("Traceback for the error above:", exc_info=True)
434437
working_copies.errors = True
435438
else:
436439
with output_lock:

src/mxdev/vcs/git.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ class GitError(common.WCError):
1515
pass
1616

1717

18+
def _branch_not_found_message(name: str, branch: str, url: str) -> str:
19+
"""Build a clear, actionable error for a configured branch that is missing."""
20+
return (
21+
f"Branch '{branch}' for package '{name}' does not exist at '{url}'. "
22+
f"Check the 'branch' setting for [{name}] in your mx.ini."
23+
)
24+
25+
1826
class GitWorkingCopy(common.BaseWorkingCopy):
1927
"""The git working copy.
2028
@@ -117,8 +125,7 @@ def git_merge_rbranch(self, stdout_in: str, stderr_in: str, accept_missing: bool
117125
if accept_missing:
118126
logger.info("No such branch %r", branch)
119127
return (stdout_in, stderr_in)
120-
logger.error("No such branch %r", branch)
121-
sys.exit(1)
128+
raise GitError(_branch_not_found_message(self.source["name"], branch, self.source["url"]))
122129

123130
rbp = self._remote_branch_prefix
124131
cmd = self.run_git(["merge", f"{rbp}/{branch}"], cwd=path)
@@ -151,6 +158,9 @@ def git_checkout(self, **kwargs) -> str | None:
151158
cmd = self.run_git(args)
152159
stdout, stderr = cmd.communicate()
153160
if cmd.returncode != 0:
161+
branch = self.source.get("branch")
162+
if branch and "not found in upstream" in stderr:
163+
raise GitError(_branch_not_found_message(name, branch, url))
154164
raise GitError(f"git cloning of '{name}' failed.\n{stderr}")
155165
if "rev" in self.source:
156166
stdout, stderr = self.git_switch_branch(stdout, stderr)
@@ -206,8 +216,7 @@ def git_switch_branch(self, stdout_in: str, stderr_in: str, accept_missing: bool
206216
self.output((logger.info, f"No such branch {branch}"))
207217
return (stdout_in + stdout, stderr_in + stderr)
208218
else:
209-
self.output((logger.error, f"No such branch {branch}"))
210-
sys.exit(1)
219+
raise GitError(_branch_not_found_message(self.source["name"], branch, self.source["url"]))
211220
# runs the checkout with predetermined arguments
212221
cmd = self.run_git(argv, cwd=path)
213222
stdout, stderr = cmd.communicate()

tests/test_common.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,9 @@ def update(self, **kwargs):
525525
common.worker(working_copies, test_queue)
526526

527527
assert working_copies.errors is True
528-
assert "Can not execute action!" in caplog.text
528+
# The actual error message is shown (no generic message / traceback noise).
529+
assert "Test error" in caplog.text
530+
assert "Can not execute action!" not in caplog.text
529531

530532

531533
def test_worker_with_bytes_output(mocker):

tests/test_git_additional.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ def test_git_merge_rbranch_missing_branch_accept():
275275

276276
def test_git_merge_rbranch_missing_branch_no_accept():
277277
"""Test git_merge_rbranch with missing branch and accept_missing=False."""
278+
from mxdev.vcs.git import GitError
278279
from mxdev.vcs.git import GitWorkingCopy
279280

280281
with patch("mxdev.vcs.common.which", return_value="/usr/bin/git"):
@@ -293,9 +294,12 @@ def test_git_merge_rbranch_missing_branch_no_accept():
293294
mock_process.communicate.return_value = ("* main\n develop\n", "")
294295

295296
with patch.object(wc, "run_git", return_value=mock_process):
296-
with pytest.raises(SystemExit):
297+
with pytest.raises(GitError) as excinfo:
297298
wc.git_merge_rbranch("", "", accept_missing=False)
298299

300+
assert "Branch 'nonexistent' for package 'test-package' does not exist" in str(excinfo.value)
301+
assert "Check the 'branch' setting for [test-package] in your mx.ini" in str(excinfo.value)
302+
299303

300304
def test_git_merge_rbranch_merge_failure():
301305
"""Test git_merge_rbranch handles merge failure."""
@@ -888,6 +892,7 @@ def test_git_switch_branch_failure():
888892

889893
def test_git_switch_branch_missing_no_accept():
890894
"""Test git_switch_branch with missing branch and accept_missing=False."""
895+
from mxdev.vcs.git import GitError
891896
from mxdev.vcs.git import GitWorkingCopy
892897

893898
with patch("mxdev.vcs.common.which", return_value="/usr/bin/git"):
@@ -905,8 +910,12 @@ def test_git_switch_branch_missing_no_accept():
905910
mock_process.communicate.return_value = ("* main\n", "")
906911

907912
with patch.object(wc, "run_git", return_value=mock_process):
908-
with pytest.raises(SystemExit):
909-
wc.git_switch_branch("", "", accept_missing=False)
913+
with patch.object(wc, "git_version", return_value=(2, 30, 0)):
914+
with pytest.raises(GitError) as excinfo:
915+
wc.git_switch_branch("", "", accept_missing=False)
916+
917+
assert "Branch 'nonexistent' for package 'test-package' does not exist" in str(excinfo.value)
918+
assert "Check the 'branch' setting for [test-package] in your mx.ini" in str(excinfo.value)
910919

911920

912921
def test_git_switch_branch_missing_accept():

0 commit comments

Comments
 (0)