blob: e32375d72c01fd521d8afd8ccaadaac68e6d1e5b [file] [edit]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import sys
from subcommands import add as add_cmd
from subcommands import list as list_cmd
from subcommands import locate as locate_cmd
from subcommands import pool_add as pool_add_cmd
from subcommands import pool_list as pool_list_cmd
from subcommands import pool_remove as pool_remove_cmd
from subcommands import remove as remove_cmd
from worktree_pool import WorktreePool
def main() -> None:
parser = argparse.ArgumentParser(
prog="fx worktree",
description="Manage Fuchsia worktrees for parallel development.",
epilog=(
"Worktree Pool Lifecycle & Command Comparison:\n"
" Unlike standard git worktrees, 'fx worktree' maintains a pool of reusable checkouts\n"
" with 2 distinct states: free (available) and leased (in active use).\n"
"\n"
" • Physical Disk Management (pool add / pool remove):\n"
" pool add Create a new physical checkout on disk under .jiri_root/worktrees/<name>\n"
" and register it in the pool as 'free'.\n"
" pool remove Permanently delete a worktree directory and its build artifacts from disk.\n"
"\n"
" • Active Task Allocation (add / remove):\n"
" add Temporarily allocate a 'free' worktree for active use (e.g. by an AI agent),\n"
" marking it 'leased' to prevent concurrent modifications by other tasks.\n"
" remove End active use of a worktree, restore backed-up GN build arguments, and return\n"
" the checkout to the pool as 'free'."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
subparsers = parser.add_subparsers(dest="subcommand", required=True)
# Subcommand group 'pool'
parser_pool = subparsers.add_parser(
"pool",
help="Manage physical worktree checkouts in the pool",
description="Administrative commands for provisioning and maintaining physical checkouts on disk.",
)
pool_subparsers = parser_pool.add_subparsers(
dest="pool_subcommand", required=True
)
parser_pool_list = pool_subparsers.add_parser(
"list",
help="List all physical worktrees in the pool",
description="List all physical worktrees in the pool along with state and physical paths.",
)
parser_pool_add = pool_subparsers.add_parser(
"add",
help="Add a new physical worktree checkout to the pool",
)
parser_pool_add.add_argument(
"name", nargs="?", help="Optional physical name"
)
parser_pool_add.add_argument("--set", action="append", help="Run 'fx set'")
parser_pool_remove = pool_subparsers.add_parser(
"remove",
help="Remove a physical worktree from the pool",
)
parser_pool_remove.add_argument("name", help="Name of physical worktree")
parser_pool_remove.add_argument(
"--force", action="store_true", help="Force removal"
)
# Subcommand 'locate'
parser_locate = subparsers.add_parser(
"locate",
help="Print the absolute path to a worktree directory",
description="Locate a worktree by name and print its absolute path on disk.",
)
parser_locate.add_argument("name", help="Name of the worktree")
# Subcommand 'list'
subparsers.add_parser(
"list",
help="List all active worktrees",
description="List all active worktrees and their git branches.",
)
# Subcommand 'add'
parser_add = subparsers.add_parser(
"add",
help="Add an active worktree checkout for development",
)
parser_add.add_argument("name", help="Name of worktree / branch")
parser_add.add_argument(
"--sync", action="store_true", help="Sync after adding"
)
parser_add.add_argument(
"--pool-name", help="Specific pool slot to allocate"
)
parser_add.add_argument("--json", action="store_true", help="Output JSON")
# Subcommand 'remove'
parser_remove = subparsers.add_parser(
"remove",
help="Remove an active worktree and return it to the pool",
)
parser_remove.add_argument("name", help="Name of worktree to remove")
args = parser.parse_args()
pool = WorktreePool()
try:
if args.subcommand == "pool":
if args.pool_subcommand == "list":
pool_list_cmd.run(pool)
elif args.pool_subcommand == "add":
pool_add_cmd.run(args, pool)
elif args.pool_subcommand == "remove":
pool_remove_cmd.run(args, pool)
elif args.subcommand == "locate":
locate_cmd.run(args, pool)
elif args.subcommand == "list":
list_cmd.run(args, pool)
elif args.subcommand == "add":
add_cmd.run(args, pool)
elif args.subcommand == "remove":
remove_cmd.run(args, pool)
else:
print(f"Unknown subcommand: {args.subcommand}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()