aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/wf.py
diff options
context:
space:
mode:
Diffstat (limited to 'wf.py')
-rwxr-xr-xwf.py75
1 files changed, 58 insertions, 17 deletions
diff --git a/wf.py b/wf.py
index 444cd37..f3b7f28 100755
--- a/wf.py
+++ b/wf.py
@@ -1194,7 +1194,8 @@ def worktree_done_lines(p: Project, args) -> list[str]:
return []
branch, main = config.git_branch(wt[1]), wt[2]
master = config.git_branch(main / ".git") or "master"
- files = " ".join(str(f.relative_to(main)) for f in (p.cfg.tasks, p.cfg.archive))
+ bmain = config.git_top(p.cfg.root) or main # books repo (private repo of a split project)
+ files = " ".join(str(f.relative_to(bmain)) for f in (p.cfg.tasks, p.cfg.archive))
warn = []
if not branch and (n := unmerged_count(wt[0], master)):
warn = [f"detached HEAD has {n} commit{'s' * (n != 1)} not in {master}: wf merge merges "
@@ -1224,14 +1225,19 @@ def cmd_merge(args) -> int:
branch = config.git_branch(gitdir)
master = config.git_branch(main / ".git") or "master"
cfg = config.load_at(start)
- files = [str(f.relative_to(main)) for f in (cfg.tasks, cfg.archive)]
+ bmain = config.git_top(cfg.root) or main # books repo: the private repo of a split project, else main
+ private = [str(Path(x).resolve().relative_to(bmain.resolve())) for x in getattr(args, "private", None) or []]
+ files = [str(f.relative_to(bmain)) for f in (cfg.tasks, cfg.archive)] + private
if git_run(top, "status", "--porcelain", "-uall").stdout.strip():
raise Failure("worktree has uncommitted changes: commit them first")
- if not branch and not unmerged_count(top, master):
+ had = unmerged_count(top, master)
+ if not branch and not had:
# after a merge: notes / follow-ups written since → bookkeeping commit only
- if not git_run(main, "status", "--porcelain", "--", *files).stdout.strip():
+ if not git_run(bmain, "status", "--porcelain", "--", *files).stdout.strip():
raise Failure("worktree is on a detached HEAD: nothing to merge")
- r = git_run(main, "commit", "-q", "-m", args.m or "bookkeeping", "--", *files)
+ if private and (r := git_run(bmain, "add", "--", *private)).returncode:
+ raise Failure(f"git add failed: {(r.stderr.strip() or 'git error').splitlines()[-1]}")
+ r = git_run(bmain, "commit", "-q", "-m", args.m or "bookkeeping", "--", *files)
if r.returncode:
raise Failure(f"bookkeeping commit failed: {(r.stderr.strip() or r.stdout.strip() or 'git error').splitlines()[-1]}")
print(f"committed {' '.join(files)}")
@@ -1241,21 +1247,28 @@ def cmd_merge(args) -> int:
git_run(top, "rebase", "--abort")
raise Failure(f"rebase onto {master} conflicts: git rebase {master}, resolve, verify, then wf merge again")
out.append(f"rebased onto {master}")
- args.merged_sha = git_run(top, "rev-parse", "--short", "HEAD").stdout.strip()
+ args.merged_sha = (git_run(top, "rev-parse", "--short", "HEAD").stdout.strip() if had or bmain == main
+ else "-")
r = git_run(main, "merge", "--ff-only", branch or git_run(top, "rev-parse", "HEAD").stdout.strip())
if r.returncode:
raise Failure(f"ff-merge into {master} failed: {(r.stderr.strip() or 'git error').splitlines()[-1]}")
out.append(f"fast-forwarded {master}")
- if git_run(main, "status", "--porcelain", "--", *files).stdout.strip():
- msg = args.m or (f"{branch.rsplit('/', 1)[-1]} done" if branch else "bookkeeping")
- r = git_run(main, "commit", "-q", "-m", msg, "--", *files)
+ if private and (r := git_run(bmain, "add", "--", *private)).returncode:
+ raise Failure(f"git add failed: {(r.stderr.strip() or 'git error').splitlines()[-1]}")
+ if git_run(bmain, "status", "--porcelain", "--", *files).stdout.strip():
+ msg = (getattr(args, "private_msg", None) if private else None) or args.m \
+ or (f"{branch.rsplit('/', 1)[-1]} done" if branch else "bookkeeping")
+ if bmain != main and args.merged_sha != "-":
+ msg += f" (code {args.merged_sha})"
+ r = git_run(bmain, "commit", "-q", "-m", msg, "--", *files)
if r.returncode:
raise Failure(f"bookkeeping commit failed: {(r.stderr.strip() or r.stdout.strip() or 'git error').splitlines()[-1]}")
out.append(f"committed {' '.join(files)}")
+ args.books_sha = git_run(bmain, "rev-parse", "--short", "HEAD").stdout.strip()
git_run(top, "switch", "-q", "--detach", master)
if branch:
git_run(top, "branch", "-q", "-d", branch)
- if not args.no_push and _push_home(main):
+ if not args.no_push and any([_push_home(main), bmain != main and _push_home(bmain)]):
out.append("pushed home")
out.append(f"merged {branch or 'detached HEAD'} into {master}")
print("\n".join(out))
@@ -1304,15 +1317,28 @@ def cmd_finish(args) -> int:
top = Path(git_run(start, "rev-parse", "--show-toplevel").stdout.strip() or start)
paths = [(start / f).resolve() for f in args.paths]
books = [] if wt else [cfg.tasks.resolve(), cfg.archive.resolve()]
+ btop = (config.git_top(cfg.root) or cfg.root).resolve()
+ split = bool(wt) and btop != wt[2].resolve() # code worktree of a split project: books in another repo
+ proot, priv, code = cfg.root.resolve(), [], []
for f, path in zip(args.paths, paths):
+ if split and (path == proot or proot in path.parents):
+ if not path.exists() and git_run(btop, "ls-files", "--error-unmatch", "--", str(path)).returncode:
+ raise Failure(f"path {f} does not exist and is not tracked: nothing to commit")
+ priv.append(path)
+ continue
+ code.append(path)
if path != top and top not in path.parents:
+ if split:
+ raise Failure(f"path {f} is in neither repo ({top}, {cfg.root}): commit it in its own repo "
+ "first, then wf finish without it")
raise Failure(f"path {f} is outside this repo ({top}): commit it in its own repo first, "
"then wf finish without it")
- if wt and path in (top / cfg.tasks.relative_to(wt[2]), top / cfg.archive.relative_to(wt[2])):
+ if wt and not split and path in (top / cfg.tasks.relative_to(wt[2]), top / cfg.archive.relative_to(wt[2])):
raise Failure(f"path {f} = this worktree's copy of the books: wf finish writes the main tree's "
"and wf merge commits them; drop it")
if not path.exists() and git_run(top, "ls-files", "--error-unmatch", "--", str(path)).returncode:
raise Failure(f"path {f} does not exist and is not tracked: nothing to commit")
+ paths = code
if wt and paths and not git_run(top, "status", "--porcelain", "--", *map(str, paths)).stdout.strip():
raise Failure("nothing to commit in the --commit paths: drop them (wf finish without paths) or fix them")
if stray := _dirty_outside(top, paths + books):
@@ -1348,11 +1374,13 @@ def cmd_finish(args) -> int:
if not wt and not args.no_push and _push_home(top):
print("pushed home", flush=True)
if wt:
- ns = argparse.Namespace(project=args.project, m=None, no_push=args.no_push)
+ ns = argparse.Namespace(project=args.project, m=None, no_push=args.no_push,
+ private=priv, private_msg=args.commit)
rc = cmd_merge(ns)
if not rc and getattr(ns, "merged_sha", ""):
tool = getattr(args, "tool_commit", None)
- print(f"report: commit {ns.merged_sha}" + (f" tool {tool}" if tool else ""), flush=True)
+ books_sha = f" books {ns.books_sha}" if split else ""
+ print(f"report: commit {ns.merged_sha}{books_sha}" + (f" tool {tool}" if tool else ""), flush=True)
return rc
return 0
@@ -1360,10 +1388,25 @@ def cmd_finish(args) -> int:
def cmd_wip(args) -> int:
"""Wrap-up in one call (lane worktree): commit the given paths on the branch, note the state, status clear."""
start = Path(args.project or Path.cwd()).resolve()
- if not config.linked_worktree(start):
+ wt = config.linked_worktree(start)
+ if not wt:
raise Failure("wip runs inside a lane worktree")
top = Path(git_run(start, "rev-parse", "--show-toplevel").stdout.strip() or start)
- paths = [str((start / f).resolve()) for f in args.paths]
+ p = load_project(args)
+ id = p.resolve(args.id)
+ btop = (config.git_top(p.cfg.root) or p.cfg.root).resolve()
+ proot, priv, paths = p.cfg.root.resolve(), [], []
+ for f in args.paths: # split project: paths under the private root are committed there
+ path = (start / f).resolve()
+ (priv if btop != wt[2].resolve() and (path == proot or proot in path.parents) else paths).append(str(path))
+ if priv:
+ r = git_run(btop, "add", "--", *priv)
+ if r.returncode:
+ raise Failure(f"git add failed: {(r.stderr.strip() or 'git error').splitlines()[-1]}")
+ r = git_run(btop, "commit", "-q", "-m", f"{id} wip: {args.m}", "--", *priv)
+ if r.returncode:
+ raise Failure(f"commit failed: {(r.stderr.strip() or r.stdout.strip() or 'git error').splitlines()[-1]}")
+ print("committed " + " ".join(os.path.relpath(c, btop) for c in priv), flush=True)
if paths:
r = git_run(top, "add", "--", *paths)
if r.returncode:
@@ -1372,8 +1415,6 @@ def cmd_wip(args) -> int:
if r.returncode:
raise Failure(f"commit failed: {(r.stderr.strip() or r.stdout.strip() or 'git error').splitlines()[-1]}")
print("committed " + " ".join(os.path.relpath(c, top) for c in paths), flush=True)
- p = load_project(args)
- id = p.resolve(args.id)
with project_lock(p.cfg.root):
cmd_note(argparse.Namespace(project=args.project, id=id, line=args.m, dry_run=False))
cmd_status(argparse.Namespace(project=args.project, id=id, kind="clear", value=[], dry_run=False,