Skip to content

Commit f38a75d

Browse files
authored
Merge pull request #58 from mxstack/fix/46-git-tag-update-regression
Fix Git tag update regression
2 parents c9ba1ae + 7895ce1 commit f38a75d

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 4.1.2 (unreleased)
44

5+
- Fix #46: Git tags in branch option are now correctly detected and handled during updates. Previously, updating from one tag to another failed because tags were incorrectly treated as branches.
6+
[jensens]
7+
58
- Fix #53: Per-package target setting now correctly overrides default-target when constructing checkout paths.
69
[jensens]
710

src/mxdev/vcs/git.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,25 @@ def git_switch_branch(
231231
raise GitError(f"git checkout of branch '{branch}' failed.\n{stderr}")
232232
return (stdout_in + stdout, stderr_in + stderr)
233233

234+
def git_is_tag(self, tag_name: str) -> bool:
235+
"""Check if the given name is a git tag.
236+
237+
Returns True if tag_name exists as a tag in the repository.
238+
"""
239+
path = self.source["path"]
240+
cmd = self.run_git(["tag", "-l", tag_name], cwd=path)
241+
stdout, stderr = cmd.communicate()
242+
if cmd.returncode != 0:
243+
return False
244+
# git tag -l returns the tag name if it exists, empty if not
245+
return tag_name in stdout.strip().split("\n")
246+
234247
def git_update(self, **kwargs) -> typing.Union[str, None]:
235248
name = self.source["name"]
236249
path = self.source["path"]
237250
self.output((logger.info, "Updated '%s' with git." % name))
238251
# First we fetch. This should always be possible.
239-
argv = ["fetch"]
252+
argv = ["fetch", "--tags"] # Also fetch tags explicitly
240253
update_git_submodules = self.source.get("submodules", kwargs["submodules"])
241254
if update_git_submodules == "recursive":
242255
argv.append("--recurse-submodules")
@@ -247,8 +260,23 @@ def git_update(self, **kwargs) -> typing.Union[str, None]:
247260
if "rev" in self.source:
248261
stdout, stderr = self.git_switch_branch(stdout, stderr)
249262
elif "branch" in self.source:
250-
stdout, stderr = self.git_switch_branch(stdout, stderr)
251-
stdout, stderr = self.git_merge_rbranch(stdout, stderr)
263+
# Check if 'branch' is actually a tag (#46)
264+
branch_value = self.source["branch"]
265+
if self.git_is_tag(branch_value):
266+
# It's a tag - checkout directly without merge
267+
cmd = self.run_git(["checkout", branch_value], cwd=path)
268+
tag_stdout, tag_stderr = cmd.communicate()
269+
if cmd.returncode != 0:
270+
raise GitError(
271+
f"git checkout of tag '{branch_value}' failed.\n{tag_stderr}"
272+
)
273+
stdout += tag_stdout
274+
stderr += tag_stderr
275+
self.output((logger.info, f"Switched to tag '{branch_value}'."))
276+
else:
277+
# It's a branch - use normal branch switch + merge
278+
stdout, stderr = self.git_switch_branch(stdout, stderr)
279+
stdout, stderr = self.git_merge_rbranch(stdout, stderr)
252280
else:
253281
# We may have specified a branch previously but not
254282
# anymore. In that case, we want to revert to master.

tests/test_git.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,60 @@ def test_update_verbose(mkgitrepo, src, capsys):
202202
assert "Already up to date" in captured.out.replace("-", " ")
203203
status = vcs_status(sources, verbose=True)
204204
assert status == {"egg": ("clean", "## master...origin/master\n")}
205+
206+
207+
def test_update_git_tag_to_new_tag(mkgitrepo, src):
208+
"""Test that updating from one git tag to another works correctly.
209+
210+
This test reproduces issue #46: changing the branch option from one tag
211+
to another tag should update the checkout to the new tag.
212+
213+
Regression in v4.x - worked in v2.x
214+
"""
215+
repository = mkgitrepo("repository")
216+
# Create initial content and tag it as 1.0.0
217+
repository.add_file("foo", msg="Initial")
218+
repository("git tag 1.0.0", echo=False)
219+
220+
# Create more content and tag as 2.0.0
221+
repository.add_file("bar", msg="Second")
222+
repository("git tag 2.0.0", echo=False)
223+
224+
path = src / "egg"
225+
226+
# Initial checkout with tag 1.0.0
227+
sources = {
228+
"egg": dict(
229+
vcs="git",
230+
name="egg",
231+
branch="1.0.0", # This is actually a tag, not a branch
232+
url=str(repository.base),
233+
path=str(path),
234+
)
235+
}
236+
packages = ["egg"]
237+
verbose = False
238+
239+
# Checkout at tag 1.0.0
240+
vcs_checkout(sources, packages, verbose)
241+
assert {x for x in path.iterdir()} == {path / ".git", path / "foo"}
242+
243+
# Verify we're at tag 1.0.0
244+
result = repository.process.check_call(f"git -C {path} describe --tags", echo=False)
245+
current_tag = result[0].decode("utf8").strip()
246+
assert current_tag == "1.0.0"
247+
248+
# Now update the sources to use tag 2.0.0
249+
sources["egg"]["branch"] = "2.0.0"
250+
251+
# Update should switch to tag 2.0.0
252+
# BUG: This will fail because the code treats tags as branches
253+
vcs_update(sources, packages, verbose)
254+
255+
# After update, we should have both foo and bar (tag 2.0.0)
256+
assert {x for x in path.iterdir()} == {path / ".git", path / "foo", path / "bar"}
257+
258+
# Verify we're now at tag 2.0.0
259+
result = repository.process.check_call(f"git -C {path} describe --tags", echo=False)
260+
current_tag = result[0].decode("utf8").strip()
261+
assert current_tag == "2.0.0"

0 commit comments

Comments
 (0)