patx/hglab
make hglabs bookmarks behave more like git/github branches workflows
Commit f0e776786838 · Harrison Erd · 2026-05-05 10:50 -0400
Comments
No comments yet.
Diff
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -29,6 +29,24 @@
hg push http://<user>@127.0.0.1:8080/hg/<user>/<repo>
```
+For a Git-like branch workflow, keep the main line on Mercurial's special `@`
+bookmark and put feature work on separate bookmarks:
+
+```sh
+hg bookmark @
+hg push -B @
+
+hg update @
+hg bookmark feature/xyz
+# edit files
+hg commit -m "Implement xyz"
+hg push -B feature/xyz
+```
+
+HgLab treats `@` as the repository's default code ref when it exists. The
+feature bookmark remains browsable from the Bookmarks tab without replacing the
+unqualified repository view.
+
## Configuration
- `SECRET_KEY`: Bottle signed-cookie secret.
diff --git a/app.py b/app.py
--- a/app.py
+++ b/app.py
@@ -76,6 +76,7 @@
REF_PICKER_TABS = {"overview", "source", "commits", "tags", "branches", "bookmarks"}
REF_QUERY_KEYS = {"ref", "ref_type", "ref_value"}
REF_VALUE_SEPARATOR = "|"
+DEFAULT_BOOKMARK_NAME = "@"
SCRIPT_STYLE_RE = re.compile(r"(?is)<(script|style)\b[^>]*>.*?</\1>")
RESERVED_USERNAMES = {"dashboard", "favicon.ico", "hg", "login", "logout", "new", "settings", "signup", "static", "harrisonerd"}
WRITE_HG_COMMANDS = {"pushkey", "unbundle"}
@@ -1670,7 +1671,19 @@
return bookmarks
+def default_bookmark_ref(path):
+ for bookmark in list_repo_bookmarks(path):
+ if bookmark["name"] == DEFAULT_BOOKMARK_NAME:
+ selected = dict(bookmark)
+ selected["is_default"] = True
+ return selected
+ return None
+
+
def default_code_ref(path):
+ selected_bookmark = default_bookmark_ref(path)
+ if selected_bookmark:
+ return selected_bookmark
for branch in list_repo_branches(path):
if branch["name"] == "default" and not branch["closed"]:
selected = dict(branch)
diff --git a/tests/test_app.py b/tests/test_app.py
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -694,6 +694,38 @@
assert isolated_app.is_ancestor(path, isolated_app.NULL_REV, node)
+def test_default_code_ref_prefers_at_bookmark_for_git_like_bookmark_branches(isolated_app):
+ owner = create_user("alice")
+ isolated_app.create_repository(owner, "demo", "")
+ path = isolated_app.repo_path("alice", "demo")
+ default_node = commit_file(path, "README.md", "# Demo\n", message="initial", user="alice")
+ isolated_app.run_hg(["bookmark", "-r", default_node, isolated_app.DEFAULT_BOOKMARK_NAME], cwd=path)
+ isolated_app.run_hg(["update", isolated_app.DEFAULT_BOOKMARK_NAME], cwd=path)
+ isolated_app.run_hg(["bookmark", "feature/xyz"], cwd=path)
+ feature_node = commit_file(path, "feature.txt", "feature work\n", message="feature work", user="alice")
+
+ branches = {branch["name"]: branch for branch in isolated_app.list_repo_branches(path)}
+ default_ref = isolated_app.default_code_ref(path)
+ feature_query = urlencode({"ref_type": isolated_app.REF_TYPE_BOOKMARK, "ref": "feature/xyz"})
+ client = WsgiClient(isolated_app.app)
+
+ assert branches["default"]["node"] == feature_node
+ assert default_ref["type"] == isolated_app.REF_TYPE_BOOKMARK
+ assert default_ref["name"] == isolated_app.DEFAULT_BOOKMARK_NAME
+ assert default_ref["node"] == default_node
+ assert isolated_app.hg_files(path, default_ref["node"]) == ["README.md"]
+ assert "feature.txt" in isolated_app.hg_files(path, feature_node)
+
+ default_response = client.get("/alice/demo/src")
+ feature_response = client.get(f"/alice/demo/src?{feature_query}")
+
+ assert default_response.status_code == 200
+ assert "README.md" in default_response.text
+ assert "feature.txt" not in default_response.text
+ assert feature_response.status_code == 200
+ assert "feature.txt" in feature_response.text
+
+
def test_create_pull_request_between_fork_repositories(isolated_app):
owner = create_user("alice")
author = create_user("bob")