Skip to content

Commit

Permalink
Add support for Fossil SCM branch names in vc.py. (xonsh#5046)
Browse files Browse the repository at this point in the history
Add support for Fossil VCS in vc.py.

The prompt now shows the currently active Fossil branch, while inside a Fossil checkout.
  • Loading branch information
luziferius committed Mar 2, 2023
1 parent 237e231 commit cb95f0e
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 20 deletions.
24 changes: 24 additions & 0 deletions news/vc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
**Added:**

* Display the current branch of Fossil VCS checkouts in the prompt,
similar to git and hg.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
63 changes: 44 additions & 19 deletions tests/prompt/test_vc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import subprocess as sp
import textwrap
from typing import Dict, List
from pathlib import Path
from unittest.mock import Mock

Expand All @@ -8,7 +10,19 @@
from xonsh.prompt import vc

# Xonsh interaction with version control systems.
VC_BRANCH = {"git": {"master", "main"}, "hg": {"default"}}
VC_BRANCH = {
"git": {"master", "main"},
"hg": {"default"},
"fossil": {"trunk"},
}
VC_INIT: Dict[str, List[List[str]]] = {
# A sequence of commands required to initialize a repository
"git": [["init"]],
"hg": [["init"]],
# Fossil "init" creates a central repository file with the given name,
# "open" creates the working directory at another, arbitrary location.
"fossil": [["init", "test.fossil"], ["open", "test.fossil"]]
}


@pytest.fixture(params=VC_BRANCH.keys())
Expand All @@ -20,28 +34,32 @@ def repo(request, tmpdir_factory):
temp_dir = Path(tmpdir_factory.mktemp("dir"))
os.chdir(temp_dir)
try:
sp.call([vc, "init"])
for init_command in VC_INIT[vc]:
sp.call([vc] + init_command)
except FileNotFoundError:
pytest.skip(f"cannot find {vc} executable")
if vc == "git":
git_config = temp_dir / ".git/config"
git_config.write_text(
"""
[user]
name = me
email = [email protected]
[init]
defaultBranch = main
"""
)

# git needs at least one commit
Path("test-file").touch()
sp.call(["git", "add", "test-file"])
sp.call(["git", "commit", "-m", "test commit"])
_init_git_repository(temp_dir)
return {"vc": vc, "dir": temp_dir}


def _init_git_repository(temp_dir):
git_config = temp_dir / ".git/config"
git_config.write_text(textwrap.dedent(
"""\
[user]
name = me
email = [email protected]
[init]
defaultBranch = main
"""
))
# git needs at least one commit
Path("test-file").touch()
sp.call(["git", "add", "test-file"])
sp.call(["git", "commit", "-m", "test commit"])


@pytest.fixture
def set_xenv(xession, monkeypatch):
def _wrapper(path):
Expand All @@ -52,8 +70,15 @@ def _wrapper(path):


def test_test_repo(repo):
test_vc_dir = repo["dir"] / ".{}".format(repo["vc"])
assert test_vc_dir.is_dir()
if repo["vc"] == "fossil":
# Fossil stores the check-out meta-data in a special file within the open check-out.
# At least one of these below must exist
metadata_file_names = {".fslckout", "_FOSSIL_"} # .fslckout on Unix, _FOSSIL_ on Windows
existing_files = set(file.name for file in repo["dir"].iterdir())
assert existing_files.intersection(metadata_file_names)
else:
test_vc_dir = repo["dir"] / ".{}".format(repo["vc"])
assert test_vc_dir.is_dir()
if repo["vc"] == "git":
test_file = repo["dir"] / "test-file"
assert test_file.exists()
Expand Down
41 changes: 40 additions & 1 deletion xonsh/prompt/vc.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,27 @@ def get_hg_branch(root=None):
return branch


def _run_fossil_cmd(cmd):
timeout = XSH.env.get("VC_BRANCH_TIMEOUT")
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, timeout=timeout)
return result


def get_fossil_branch():
"""Attempts to find the current fossil branch. If this could not
be determined (timeout, not in a fossil checkout, etc.) then this returns None.
"""
# from fossil branch --help: "fossil branch current: Print the name of the branch for the current check-out"
cmd = "fossil branch current".split()
try:
branch = xt.decode_bytes(_run_fossil_cmd(cmd))
except (subprocess.CalledProcessError, OSError):
branch = None
else:
branch = RE_REMOVE_ANSI.sub("", branch.splitlines()[0])
return branch


_FIRST_BRANCH_TIMEOUT = True


Expand Down Expand Up @@ -156,7 +177,7 @@ def _vc_has(binary):

def current_branch():
"""Gets the branch for a current working directory. Returns an empty string
if the cwd is not a repository. This currently only works for git and hg
if the cwd is not a repository. This currently only works for git, hg, and fossil
and should be extended in the future. If a timeout occurred, the string
'<branch-timeout>' is returned.
"""
Expand All @@ -165,6 +186,8 @@ def current_branch():
branch = get_git_branch()
if not branch and _vc_has("hg"):
branch = get_hg_branch()
if not branch and _vc_has("fossil"):
branch = get_fossil_branch()
if isinstance(branch, subprocess.TimeoutExpired):
branch = "<branch-timeout>"
_first_branch_timeout_message()
Expand Down Expand Up @@ -234,6 +257,20 @@ def hg_dirty_working_directory():
return None


def fossil_dirty_working_directory():
"""Returns whether the fossil checkout is dirty. If this could not
be determined (timeout, file not found, etc.) then this returns None.
"""
cmd = ["fossil", "changes"]
try:
status = _run_fossil_cmd(cmd)
except (subprocess.CalledProcessError, OSError):
status = None
else:
status = bool(status)
return status


def dirty_working_directory():
"""Returns a boolean as to whether there are uncommitted files in version
control repository we are inside. If this cannot be determined, returns
Expand All @@ -244,6 +281,8 @@ def dirty_working_directory():
dwd = git_dirty_working_directory()
if dwd is None and _vc_has("hg"):
dwd = hg_dirty_working_directory()
if dwd is None and _vc_has("fossil"):
dwd = fossil_dirty_working_directory()
return dwd


Expand Down

0 comments on commit cb95f0e

Please sign in to comment.