bitp 1.3.1__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bitp-1.3.1 → bitp-1.3.2}/PKG-INFO +1 -1
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/explore.py +61 -1
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/projects.py +62 -5
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/query.py +39 -3
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/dashboard.js +7 -1
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/explore.js +59 -6
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/PKG-INFO +1 -1
- {bitp-1.3.1 → bitp-1.3.2}/.gitignore +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/.gitlab-ci.yml +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/COPYING +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/INSTALL.md +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/README.md +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/__main__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/cli.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/b4.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/branch.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/common.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/config.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/confjson.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/daemon_cmd.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/deps.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/export.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/fragment.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/info.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/patches.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/recipe.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/repos.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/search.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/setup.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/ssh_remote.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/commands/update.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/core.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/daemon.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/fzf_bindings.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/persona.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/generic/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/yocto/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/yocto/bblayers.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/yocto/commands/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/personas/yocto/layers.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tags.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tmux.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/base.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/demo_dialogs.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/demo_inline_prompt.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/dialogs.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/explore_dialogs.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/factory.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/fzf_backend.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/fzf_inline_prompt.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/inline_options.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/tui/textual_backend.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/api.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/api_commands.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/server.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/sse.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/favicon.svg +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/index.html +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/api.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/app.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/column-resizer.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/sortable.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/terminal-ws.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/utils.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/b4.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/branch.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/commits.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/config.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/deps.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/export.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/fragments.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/hosts.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/https.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/info.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/layer-index.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/messages.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/patches.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/recipes.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/setup.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/terminal.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/js/views/update.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/style.css +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/terminal.html +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/Sortable.min.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/diff2html-ui.min.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/diff2html.min.css +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/github-dark.min.css +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/highlight.min.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/xterm-addon-fit.min.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/xterm.css +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/vendor/xterm.min.js +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/static/ws-test.html +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/terminal.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitbake_project/web/websocket.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/SOURCES.txt +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/dependency_links.txt +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/entry_points.txt +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/requires.txt +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/bitp.egg-info/top_level.txt +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/commits.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/dashboard.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/explore-expanded.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/explore.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/recipes-deps.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/recipes.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-b4.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-branch.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-commits.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-config.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-dashboard.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-explore.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-export.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-hosts.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-patches.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/images/web-terminal.png +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/docs/render_screenshot.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/pyproject.toml +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/scripts/build-standalone.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/setup.cfg +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/README.md +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/conftest.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/data/bblayers/simple.conf +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/data/bblayers/with_variables.conf +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/integration/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/integration/test_cli_dispatch.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/integration/test_projects_command.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/__init__.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_bblayers_parser.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_cli_parser.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_colors.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_confjson.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_custom_update.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_daemon.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_explore.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_fzf_bindings.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_fzf_menu.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_gitrepo.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_helpers.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_info.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_setup.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_ssh_remote.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_state.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_tags.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_tmux.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_update.py +0 -0
- {bitp-1.3.1 → bitp-1.3.2}/tests/unit/test_web_server.py +0 -0
|
@@ -471,6 +471,7 @@ def _build_explore_menu_lines(
|
|
|
471
471
|
branch_priorities: Optional[List[str]] = None,
|
|
472
472
|
branch_excludes: Optional[List[str]] = None,
|
|
473
473
|
branch_stale_days: int = 365,
|
|
474
|
+
all_layers_repos: Optional[Set[str]] = None,
|
|
474
475
|
) -> str:
|
|
475
476
|
"""Build fzf menu input for explore repo/layers list. Returns newline-joined string."""
|
|
476
477
|
menu_lines = []
|
|
@@ -479,6 +480,7 @@ def _build_explore_menu_lines(
|
|
|
479
480
|
expanded_repos = expanded_repos or set()
|
|
480
481
|
configured_layers = configured_layers or set()
|
|
481
482
|
branch_expanded_repos = branch_expanded_repos or set()
|
|
483
|
+
all_layers_repos = all_layers_repos or set()
|
|
482
484
|
|
|
483
485
|
# Layers mode: show individual layers instead of repos
|
|
484
486
|
if layers_mode:
|
|
@@ -999,7 +1001,7 @@ def fzf_explore_repo_list(
|
|
|
999
1001
|
prompt = "Select layer: "
|
|
1000
1002
|
else:
|
|
1001
1003
|
prefix = get_project_header_prefix()
|
|
1002
|
-
header = f"{prefix}{Colors.cyan('REPOS VIEW')} | Enter=explore | q=quit | L=layers view\n{Colors.cyan('Update:')} u=single | U=all | m=merge | r=refresh | R=refresh all | b=branches | B=branch all\n{Colors.cyan('View:')} v=verbose | Enter/←/→/\\=fold | t=history | S=shell | M=messages\n{Colors.cyan('Manage:')} a/d=add/remove | +/-=track/hide | c=config | A=discover | P=pull patches | X=prune backups"
|
|
1004
|
+
header = f"{prefix}{Colors.cyan('REPOS VIEW')} | Enter=explore | q=quit | L=layers view\n{Colors.cyan('Update:')} u=single | U=all | m=merge | r=refresh | R=refresh all | b=branches | B=branch all\n{Colors.cyan('View:')} v=verbose | Enter/←/→/\\=fold | t=history | S=shell | M=messages | *=all layers\n{Colors.cyan('Manage:')} a/d=add/remove | +/-=track/hide | c=config | A=discover | P=pull patches | X=prune backups"
|
|
1003
1005
|
if is_yocto:
|
|
1004
1006
|
header += f"\n{Colors.cyan('Yocto:')} i=info | ctrl-r=recipes | D=deps | f=fragments"
|
|
1005
1007
|
prompt = "Select repo: "
|
|
@@ -1124,6 +1126,7 @@ def fzf_explore_repo_list(
|
|
|
1124
1126
|
fzf_cmd.extend(get_action_binding("L", "LAYERS_TOGGLE"))
|
|
1125
1127
|
fzf_cmd.extend(get_action_binding("c", "CONFIG {1}"))
|
|
1126
1128
|
fzf_cmd.extend(get_action_binding("A", "DISCOVER_TOGGLE"))
|
|
1129
|
+
fzf_cmd.extend(get_action_binding("*", "ALL_LAYERS {1}")) # per-repo all layers toggle
|
|
1127
1130
|
fzf_cmd.extend(get_action_binding("U", "UPDATE_ALL"))
|
|
1128
1131
|
fzf_cmd.extend(get_action_binding("b", "BRANCH_TOGGLE {1}"))
|
|
1129
1132
|
fzf_cmd.extend(get_action_binding("B", "BRANCH_ALL"))
|
|
@@ -1392,6 +1395,16 @@ def fzf_explore_repo_list(
|
|
|
1392
1395
|
return "layers_toggle", None, query
|
|
1393
1396
|
elif output == "DISCOVER_TOGGLE":
|
|
1394
1397
|
return "discover_toggle", None, query
|
|
1398
|
+
elif output.startswith("ALL_LAYERS "):
|
|
1399
|
+
repo_path = output[11:].strip()
|
|
1400
|
+
if repo_path.startswith("LAYER:"):
|
|
1401
|
+
# Resolve parent repo from layer path
|
|
1402
|
+
layer_path = repo_path[6:]
|
|
1403
|
+
from .common import git_toplevel
|
|
1404
|
+
repo_path = git_toplevel(layer_path) or layer_path
|
|
1405
|
+
if repo_path in ("HEADER", "PROJECT") or repo_path.startswith("SEPARATOR") or repo_path.startswith("BRANCH:"):
|
|
1406
|
+
return "cancelled", None, query
|
|
1407
|
+
return "all_layers_toggle", repo_path, query
|
|
1395
1408
|
elif output == "UPDATE_ALL":
|
|
1396
1409
|
return "update_all", None, query
|
|
1397
1410
|
elif output.startswith("BRANCH_TOGGLE "):
|
|
@@ -4492,6 +4505,8 @@ def run_explore(args) -> int:
|
|
|
4492
4505
|
explore_verbose_mode = False
|
|
4493
4506
|
explore_layers_mode = False
|
|
4494
4507
|
expanded_repos: Set[str] = set()
|
|
4508
|
+
all_layers_repos: Set[str] = set() # repos showing all layers (not just tracked)
|
|
4509
|
+
all_layers_saved: Dict[str, List[str]] = {} # original repo_layers before all-layers merge
|
|
4495
4510
|
branch_expanded_repos: Set[str] = set()
|
|
4496
4511
|
branch_priorities: List[str] = defaults.get("__branch_priorities__", [])
|
|
4497
4512
|
branch_excludes: List[str] = defaults.get("__branch_excludes__", [])
|
|
@@ -5247,6 +5262,51 @@ def run_explore(args) -> int:
|
|
|
5247
5262
|
if selected_repo:
|
|
5248
5263
|
next_selection = selected_repo
|
|
5249
5264
|
continue
|
|
5265
|
+
elif list_action == "all_layers_toggle" and selected_repo:
|
|
5266
|
+
# Toggle showing all layers (including untracked) for a repo
|
|
5267
|
+
if selected_repo in ("HEADER", "PROJECT", "SETTINGS") or selected_repo.startswith("SEPARATOR") or selected_repo.startswith("BRANCH:"):
|
|
5268
|
+
continue
|
|
5269
|
+
if selected_repo in all_layers_repos:
|
|
5270
|
+
# Toggle off: restore original tracked layers
|
|
5271
|
+
all_layers_repos.discard(selected_repo)
|
|
5272
|
+
if selected_repo in all_layers_saved:
|
|
5273
|
+
repo_layers[selected_repo] = all_layers_saved.pop(selected_repo)
|
|
5274
|
+
# Recompute layer upstream counts for tracked layers only
|
|
5275
|
+
info = repo_info[selected_repo]
|
|
5276
|
+
branch = info["branch"]
|
|
5277
|
+
layers = repo_layers.get(selected_repo, [])
|
|
5278
|
+
if len(layers) > 1 and branch != "(detached)":
|
|
5279
|
+
try:
|
|
5280
|
+
info["layer_upstream_counts"] = get_upstream_layer_counts(selected_repo, branch, layers)
|
|
5281
|
+
except Exception:
|
|
5282
|
+
info["layer_upstream_counts"] = {}
|
|
5283
|
+
else:
|
|
5284
|
+
info["layer_upstream_counts"] = {}
|
|
5285
|
+
else:
|
|
5286
|
+
# Toggle on: scan repo for all layers, merge with tracked
|
|
5287
|
+
from .query import _scan_repo_layers
|
|
5288
|
+
all_layers_saved[selected_repo] = list(repo_layers.get(selected_repo, []))
|
|
5289
|
+
scanned = _scan_repo_layers(selected_repo)
|
|
5290
|
+
tracked = set(repo_layers.get(selected_repo, []))
|
|
5291
|
+
# Merge: tracked first, then untracked
|
|
5292
|
+
merged = list(repo_layers.get(selected_repo, []))
|
|
5293
|
+
for lp in scanned:
|
|
5294
|
+
if lp not in tracked:
|
|
5295
|
+
merged.append(lp)
|
|
5296
|
+
repo_layers[selected_repo] = merged
|
|
5297
|
+
all_layers_repos.add(selected_repo)
|
|
5298
|
+
# Auto-expand to show the layers
|
|
5299
|
+
expanded_repos.add(selected_repo)
|
|
5300
|
+
# Compute upstream counts for all layers
|
|
5301
|
+
info = repo_info[selected_repo]
|
|
5302
|
+
branch = info["branch"]
|
|
5303
|
+
if branch != "(detached)" and len(merged) > 1:
|
|
5304
|
+
try:
|
|
5305
|
+
info["layer_upstream_counts"] = get_upstream_layer_counts(selected_repo, branch, merged)
|
|
5306
|
+
except Exception:
|
|
5307
|
+
info["layer_upstream_counts"] = {}
|
|
5308
|
+
next_selection = selected_repo
|
|
5309
|
+
continue
|
|
5250
5310
|
elif list_action == "prune_backups" and selected_repo:
|
|
5251
5311
|
is_special = (selected_repo in ("HEADER", "PROJECT", "PROJECT_CONFIG",
|
|
5252
5312
|
"PROJECT_INFO", "PROJECT_DEPS", "SETTINGS", "INFO")
|
|
@@ -1105,6 +1105,15 @@ def _background_fetch_remote_summaries(remote_projects: list) -> None:
|
|
|
1105
1105
|
log_message(f"Remote status updated ({', '.join(sorted(hosts))})")
|
|
1106
1106
|
|
|
1107
1107
|
|
|
1108
|
+
def _count_subdirs(repo: str) -> int:
|
|
1109
|
+
"""Count immediate subdirectories in a repo (quick layer-count heuristic)."""
|
|
1110
|
+
try:
|
|
1111
|
+
return sum(1 for e in os.scandir(repo)
|
|
1112
|
+
if e.is_dir(follow_symlinks=False) and not e.name.startswith("."))
|
|
1113
|
+
except OSError:
|
|
1114
|
+
return 0
|
|
1115
|
+
|
|
1116
|
+
|
|
1108
1117
|
def _get_project_git_repos(path: str) -> List[str]:
|
|
1109
1118
|
"""Find all git repos in a project directory, up to 2 levels deep."""
|
|
1110
1119
|
git_repos: list = []
|
|
@@ -1155,6 +1164,7 @@ def _get_dashboard_summary(path: str, project_info: Optional[dict] = None) -> di
|
|
|
1155
1164
|
"dirty_count": 0,
|
|
1156
1165
|
"ahead": 0,
|
|
1157
1166
|
"behind": 0,
|
|
1167
|
+
"tracked_behind": None, # None = no layer info, int = filtered count
|
|
1158
1168
|
"branch": "",
|
|
1159
1169
|
"needs_setup": False,
|
|
1160
1170
|
}
|
|
@@ -1179,18 +1189,20 @@ def _get_dashboard_summary(path: str, project_info: Optional[dict] = None) -> di
|
|
|
1179
1189
|
|
|
1180
1190
|
# Gather per-repo stats
|
|
1181
1191
|
branches: dict = {} # branch_name -> count
|
|
1192
|
+
repos_behind: dict = {} # repo_path -> (behind_count, branch)
|
|
1182
1193
|
|
|
1183
1194
|
for repo in git_repos:
|
|
1184
1195
|
# Branch
|
|
1196
|
+
repo_branch = ""
|
|
1185
1197
|
try:
|
|
1186
1198
|
r = subprocess.run(
|
|
1187
1199
|
["git", "-C", repo, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
1188
1200
|
capture_output=True, text=True, timeout=3,
|
|
1189
1201
|
)
|
|
1190
1202
|
if r.returncode == 0:
|
|
1191
|
-
|
|
1192
|
-
if
|
|
1193
|
-
branches[
|
|
1203
|
+
repo_branch = r.stdout.strip()
|
|
1204
|
+
if repo_branch:
|
|
1205
|
+
branches[repo_branch] = branches.get(repo_branch, 0) + 1
|
|
1194
1206
|
except Exception:
|
|
1195
1207
|
pass
|
|
1196
1208
|
|
|
@@ -1214,8 +1226,12 @@ def _get_dashboard_summary(path: str, project_info: Optional[dict] = None) -> di
|
|
|
1214
1226
|
if r.returncode == 0:
|
|
1215
1227
|
parts = r.stdout.strip().split()
|
|
1216
1228
|
if len(parts) == 2:
|
|
1217
|
-
|
|
1218
|
-
|
|
1229
|
+
ahead = int(parts[0])
|
|
1230
|
+
behind = int(parts[1])
|
|
1231
|
+
summary["ahead"] += ahead
|
|
1232
|
+
summary["behind"] += behind
|
|
1233
|
+
if behind > 0:
|
|
1234
|
+
repos_behind[repo] = (behind, repo_branch)
|
|
1219
1235
|
except Exception:
|
|
1220
1236
|
pass
|
|
1221
1237
|
|
|
@@ -1223,6 +1239,47 @@ def _get_dashboard_summary(path: str, project_info: Optional[dict] = None) -> di
|
|
|
1223
1239
|
if branches:
|
|
1224
1240
|
summary["branch"] = max(branches, key=branches.get)
|
|
1225
1241
|
|
|
1242
|
+
# Compute tracked-layer behind count for multi-layer repos
|
|
1243
|
+
if repos_behind:
|
|
1244
|
+
try:
|
|
1245
|
+
from ..personas.yocto.bblayers import extract_layer_paths
|
|
1246
|
+
from ..personas.yocto.layers import git_toplevel
|
|
1247
|
+
# Find bblayers.conf
|
|
1248
|
+
bblayers_path = None
|
|
1249
|
+
for cand in ["conf/bblayers.conf", "build/conf/bblayers.conf"]:
|
|
1250
|
+
full = os.path.join(path, cand)
|
|
1251
|
+
if os.path.isfile(full):
|
|
1252
|
+
bblayers_path = full
|
|
1253
|
+
break
|
|
1254
|
+
if bblayers_path:
|
|
1255
|
+
layers = extract_layer_paths(bblayers_path)
|
|
1256
|
+
# Map repo -> list of tracked layer paths
|
|
1257
|
+
repo_layers: dict = {}
|
|
1258
|
+
for layer in layers:
|
|
1259
|
+
layer = os.path.realpath(layer)
|
|
1260
|
+
repo = git_toplevel(layer)
|
|
1261
|
+
if repo:
|
|
1262
|
+
repo_layers.setdefault(repo, []).append(layer)
|
|
1263
|
+
# For repos that are behind and have tracked layers,
|
|
1264
|
+
# compute per-layer filtered counts
|
|
1265
|
+
tracked_total = 0
|
|
1266
|
+
has_layer_info = False
|
|
1267
|
+
for repo, (behind, rbranch) in repos_behind.items():
|
|
1268
|
+
tracked = repo_layers.get(repo)
|
|
1269
|
+
if tracked and len(tracked) < _count_subdirs(repo):
|
|
1270
|
+
# Multi-layer repo with partial tracking
|
|
1271
|
+
has_layer_info = True
|
|
1272
|
+
from ..personas.yocto.layers import get_upstream_layer_counts
|
|
1273
|
+
lcounts = get_upstream_layer_counts(repo, rbranch, tracked)
|
|
1274
|
+
tracked_total += sum(lcounts.values())
|
|
1275
|
+
else:
|
|
1276
|
+
# Single-layer or all layers tracked — full count applies
|
|
1277
|
+
tracked_total += behind
|
|
1278
|
+
if has_layer_info:
|
|
1279
|
+
summary["tracked_behind"] = tracked_total
|
|
1280
|
+
except Exception:
|
|
1281
|
+
pass
|
|
1282
|
+
|
|
1226
1283
|
# Last updated — most recent HEAD reflog mtime across repos
|
|
1227
1284
|
latest_reflog = 0.0
|
|
1228
1285
|
for repo in git_repos:
|
|
@@ -138,6 +138,22 @@ def _find_repo(repos, repo_name, defaults):
|
|
|
138
138
|
return find_repo_by_identifier(repos, repo_name, defaults)
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def _scan_repo_layers(repo: str) -> list:
|
|
142
|
+
"""Scan a repo for all directories containing conf/layer.conf."""
|
|
143
|
+
layers = []
|
|
144
|
+
try:
|
|
145
|
+
for entry in os.scandir(repo):
|
|
146
|
+
if entry.is_dir(follow_symlinks=False) and not entry.name.startswith("."):
|
|
147
|
+
if os.path.isfile(os.path.join(entry.path, "conf", "layer.conf")):
|
|
148
|
+
layers.append(entry.path)
|
|
149
|
+
except OSError:
|
|
150
|
+
pass
|
|
151
|
+
# Also check the repo root itself
|
|
152
|
+
if os.path.isfile(os.path.join(repo, "conf", "layer.conf")):
|
|
153
|
+
layers.insert(0, repo)
|
|
154
|
+
return layers
|
|
155
|
+
|
|
156
|
+
|
|
141
157
|
def query_repos(project: str, discover: bool = False,
|
|
142
158
|
include_hidden: bool = False) -> dict:
|
|
143
159
|
"""Return list of repo dicts (same shape as /api/repos)."""
|
|
@@ -1223,19 +1239,39 @@ def query_prune_backups(repo_name: str, project: str) -> dict:
|
|
|
1223
1239
|
|
|
1224
1240
|
|
|
1225
1241
|
def query_repo_layers(repo_name: str, project: str) -> dict:
|
|
1226
|
-
"""Return layers for a repo."""
|
|
1242
|
+
"""Return layers for a repo (including untracked layers with upstream counts)."""
|
|
1227
1243
|
repos, defaults, repo_layers_map, configured, *_ = _resolve_repos(project)
|
|
1228
1244
|
repo = _find_repo(repos, repo_name, defaults)
|
|
1229
1245
|
if not repo:
|
|
1230
1246
|
return {"error": f"Repo not found: {repo_name}"}
|
|
1231
1247
|
|
|
1232
|
-
|
|
1248
|
+
# Merge tracked and scanned layers
|
|
1249
|
+
tracked = repo_layers_map.get(repo, [])
|
|
1250
|
+
all_layers = _scan_repo_layers(repo) if repo else []
|
|
1251
|
+
tracked_set = set(os.path.realpath(l) for l in tracked)
|
|
1252
|
+
merged = list(tracked)
|
|
1253
|
+
for l in all_layers:
|
|
1254
|
+
if os.path.realpath(l) not in tracked_set:
|
|
1255
|
+
merged.append(l)
|
|
1256
|
+
|
|
1257
|
+
# Compute per-layer upstream counts
|
|
1258
|
+
from .explore import current_branch
|
|
1259
|
+
from ..personas.yocto.layers import get_upstream_layer_counts
|
|
1260
|
+
branch = current_branch(repo) if repo else ""
|
|
1261
|
+
layer_counts = {}
|
|
1262
|
+
if branch and branch != "(detached)" and merged:
|
|
1263
|
+
try:
|
|
1264
|
+
layer_counts = get_upstream_layer_counts(repo, branch, merged)
|
|
1265
|
+
except Exception:
|
|
1266
|
+
pass
|
|
1267
|
+
|
|
1233
1268
|
result = []
|
|
1234
|
-
for lp in
|
|
1269
|
+
for lp in merged:
|
|
1235
1270
|
result.append({
|
|
1236
1271
|
"name": os.path.basename(lp),
|
|
1237
1272
|
"path": lp,
|
|
1238
1273
|
"configured": lp in configured,
|
|
1274
|
+
"upstream_count": layer_counts.get(lp, 0),
|
|
1239
1275
|
})
|
|
1240
1276
|
return {"layers": result}
|
|
1241
1277
|
|
|
@@ -583,7 +583,13 @@ const DashboardView = {
|
|
|
583
583
|
const parts = [`${s.repo_count} repos`];
|
|
584
584
|
if (s.dirty_count) parts.push(`<span style="color:var(--red)">\u270e${s.dirty_count}</span>`);
|
|
585
585
|
if (s.ahead) parts.push(`<span style="color:var(--accent)">\u2191${s.ahead}</span>`);
|
|
586
|
-
if (s.behind)
|
|
586
|
+
if (s.behind) {
|
|
587
|
+
let behindStr = '\u2193' + s.behind;
|
|
588
|
+
if (s.tracked_behind != null && s.tracked_behind < s.behind) {
|
|
589
|
+
behindStr += ' <span style="color:var(--text-muted);font-size:var(--font-size-xs)">(' + s.tracked_behind + ' tracked)</span>';
|
|
590
|
+
}
|
|
591
|
+
parts.push('<span style="color:var(--yellow)">' + behindStr + '</span>');
|
|
592
|
+
}
|
|
587
593
|
if (!s.dirty_count && !s.ahead && !s.behind) parts.push(`<span style="color:var(--green)">\u2713</span>`);
|
|
588
594
|
return parts.join(' ');
|
|
589
595
|
},
|
|
@@ -19,6 +19,8 @@ const ExploreView = {
|
|
|
19
19
|
_discoverMode: false,
|
|
20
20
|
_hiddenMode: false,
|
|
21
21
|
_configuredLayers: new Set(),
|
|
22
|
+
_allLayersRepos: new Set(), // repos toggled to show all layers
|
|
23
|
+
_allLayersCache: {}, // repo name -> [{name, path, configured}]
|
|
22
24
|
|
|
23
25
|
keyBindings: [
|
|
24
26
|
{key: 'j/k', desc: 'Navigate rows'},
|
|
@@ -246,10 +248,35 @@ const ExploreView = {
|
|
|
246
248
|
// Layer child rows (if expanded and has layers)
|
|
247
249
|
const hasBranchChildren = this._branchesMode && isExpanded && this._branchCache[name] && this._branchCache[name].length > 0;
|
|
248
250
|
if (hasLayers && isExpanded) {
|
|
249
|
-
const layerCounts = repo.layer_upstream_counts || {};
|
|
250
|
-
|
|
251
|
+
const layerCounts = Object.assign({}, repo.layer_upstream_counts || {});
|
|
252
|
+
const showingAll = this._allLayersRepos.has(name);
|
|
253
|
+
const allLayers = showingAll ? (this._allLayersCache[name] || []) : null;
|
|
254
|
+
|
|
255
|
+
// Merge upstream counts from all-layers fetch
|
|
256
|
+
if (allLayers) {
|
|
257
|
+
allLayers.forEach(al => {
|
|
258
|
+
if (al.upstream_count > 0 && !layerCounts[al.path]) {
|
|
259
|
+
layerCounts[al.path] = al.upstream_count;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Build display list: tracked layers, then untracked if toggled
|
|
265
|
+
const displayLayers = [];
|
|
266
|
+
layers.forEach(l => displayLayers.push({ path: l, fromAll: false }));
|
|
267
|
+
if (allLayers) {
|
|
268
|
+
const trackedSet = new Set(layers);
|
|
269
|
+
allLayers.forEach(al => {
|
|
270
|
+
if (!trackedSet.has(al.path)) {
|
|
271
|
+
displayLayers.push({ path: al.path, fromAll: true });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
displayLayers.forEach((entry, li) => {
|
|
277
|
+
const layer = entry.path;
|
|
251
278
|
const layerName = layer.split('/').pop();
|
|
252
|
-
const isLast = li ===
|
|
279
|
+
const isLast = li === displayLayers.length - 1 && !hasBranchChildren;
|
|
253
280
|
const lcount = layerCounts[layer] || 0;
|
|
254
281
|
const lcountHtml = lcount > 0
|
|
255
282
|
? `<span style="color:var(--yellow)">\u2193${lcount}</span>`
|
|
@@ -259,8 +286,8 @@ const ExploreView = {
|
|
|
259
286
|
? `<button class="btn-icon" data-action="remove-layer" data-layer-path="${esc(layer)}" title="Remove layer" style="color:var(--text-muted)">−</button>`
|
|
260
287
|
: `<button class="btn-icon" data-action="add-layer" data-layer-path="${esc(layer)}" title="Add layer" style="color:var(--green)">+</button>`;
|
|
261
288
|
const configDot = isConfigured
|
|
262
|
-
? '<span style="color:var(--green)" title="
|
|
263
|
-
: '<span style="color:var(--text-muted)" title="Not
|
|
289
|
+
? '<span style="color:var(--green)" title="In bblayers.conf">\u25cf</span>'
|
|
290
|
+
: '<span style="color:var(--text-muted)" title="Not in bblayers.conf">\u25cb</span>';
|
|
264
291
|
const layerTr = document.createElement('tr');
|
|
265
292
|
layerTr.className = 'explore-layer-row' + (isLast ? ' tree-last' : '');
|
|
266
293
|
layerTr.dataset.parentRepo = name;
|
|
@@ -277,7 +304,6 @@ const ExploreView = {
|
|
|
277
304
|
layerTr.addEventListener('click', (e) => {
|
|
278
305
|
if (e.target.closest('[data-action]')) return;
|
|
279
306
|
App.state.selectedRepo = name;
|
|
280
|
-
// Store relative layer path (e.g. "meta-core") for the API
|
|
281
307
|
const repoPath = repo.path || '';
|
|
282
308
|
App.state.selectedLayer = (repoPath && layer.startsWith(repoPath + '/'))
|
|
283
309
|
? layer.substring(repoPath.length + 1)
|
|
@@ -307,6 +333,33 @@ const ExploreView = {
|
|
|
307
333
|
});
|
|
308
334
|
}
|
|
309
335
|
});
|
|
336
|
+
|
|
337
|
+
// "Show all layers" / "Show tracked only" toggle row
|
|
338
|
+
if (!this._discoverMode) {
|
|
339
|
+
const toggleTr = document.createElement('tr');
|
|
340
|
+
toggleTr.className = 'explore-layer-row tree-last';
|
|
341
|
+
toggleTr.style.cursor = 'pointer';
|
|
342
|
+
const toggleLabel = showingAll ? 'Show tracked only' : 'Show all layers';
|
|
343
|
+
const toggleIcon = showingAll ? '\u25b4' : '\u25be';
|
|
344
|
+
const vEmpty = this._verboseMode ? '<td></td>' : '';
|
|
345
|
+
toggleTr.innerHTML = `<td class="tree-line"></td><td colspan="1"><span class="explore-all-layers-toggle" data-repo="${esc(name)}" style="color:var(--accent);font-size:var(--font-size-sm)">${toggleIcon} ${toggleLabel}</span></td>${vEmpty}<td></td><td></td><td></td><td></td>`;
|
|
346
|
+
tbody.appendChild(toggleTr);
|
|
347
|
+
toggleTr.querySelector('.explore-all-layers-toggle').addEventListener('click', (e) => {
|
|
348
|
+
e.stopPropagation();
|
|
349
|
+
const rn = e.target.dataset.repo;
|
|
350
|
+
if (this._allLayersRepos.has(rn)) {
|
|
351
|
+
this._allLayersRepos.delete(rn);
|
|
352
|
+
this._renderRepos(this._repoData);
|
|
353
|
+
} else {
|
|
354
|
+
// Fetch all layers for this repo
|
|
355
|
+
API.getRepoLayers(rn, this._project()).then(data => {
|
|
356
|
+
this._allLayersCache[rn] = data.layers || [];
|
|
357
|
+
this._allLayersRepos.add(rn);
|
|
358
|
+
this._renderRepos(this._repoData);
|
|
359
|
+
}).catch(err => App.showToast(err.message, 'error'));
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
310
363
|
}
|
|
311
364
|
|
|
312
365
|
// Branch child rows (if expanded in branches mode)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|