Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions src/stacky/stacky.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,34 @@ def cmd_branch_new(stack: StackBranchSet, args):
run(CmdArgs(["git", "update-ref", "refs/stack-parent/{}".format(name), b.commit, ""]))


def cmd_branch_commit(stack: StackBranchSet, args):
"""Create a new branch and commit all changes with the provided message"""
global CURRENT_BRANCH

# First create the new branch (same logic as cmd_branch_new)
b = stack.stack[CURRENT_BRANCH]
assert b.commit
name = args.name
create_branch(name)
run(CmdArgs(["git", "update-ref", "refs/stack-parent/{}".format(name), b.commit, ""]))

# Update global CURRENT_BRANCH since we just checked out the new branch
CURRENT_BRANCH = BranchName(name)

# Reload the stack to include the new branch
load_stack_for_given_branch(stack, CURRENT_BRANCH)

# Now commit all changes with the provided message (or open editor if no message)
do_commit(
stack,
message=args.message,
amend=False,
allow_empty=False,
edit=True,
add_all=args.add_all,
)


def cmd_branch_checkout(stack: StackBranchSet, args):
branch_name = args.name
if branch_name is None:
Expand Down Expand Up @@ -1660,6 +1688,157 @@ def cmd_land(stack: StackBranchSet, args):
cout("\n✓ Success! Run `stacky update` to update local state.\n", fg="green")


def cmd_inbox(stack: StackBranchSet, args):
"""List all active GitHub pull requests for the current user"""
fields = [
"number",
"title",
"headRefName",
"baseRefName",
"state",
"url",
"createdAt",
"updatedAt",
"author",
"reviewDecision",
"reviewRequests"
]

# Get all open PRs authored by the current user
my_prs_data = json.loads(
run_always_return(
CmdArgs(
[
"gh",
"pr",
"list",
"--json",
",".join(fields),
"--state",
"open",
"--author",
"@me"
]
)
)
)

# Get all open PRs where current user is requested as reviewer
review_prs_data = json.loads(
run_always_return(
CmdArgs(
[
"gh",
"pr",
"list",
"--json",
",".join(fields),
"--state",
"open",
"--search",
"review-requested:@me"
]
)
)
)

# Categorize my PRs based on review status
waiting_on_me = []
waiting_on_review = []
approved = []

for pr in my_prs_data:
if pr["reviewDecision"] == "APPROVED":
approved.append(pr)
elif pr["reviewRequests"] and len(pr["reviewRequests"]) > 0:
waiting_on_review.append(pr)
else:
# No pending review requests, likely needs changes or author action
waiting_on_me.append(pr)

# Sort all lists by updatedAt in descending order (most recent first)
waiting_on_me.sort(key=lambda pr: pr["updatedAt"], reverse=True)
waiting_on_review.sort(key=lambda pr: pr["updatedAt"], reverse=True)
approved.sort(key=lambda pr: pr["updatedAt"], reverse=True)
review_prs_data.sort(key=lambda pr: pr["updatedAt"], reverse=True)

def display_pr_list(prs, color="white"):
for pr in prs:
if args.compact:
# Compact format with only PR number clickable: "#123 Title (branch) Updated: date"
# Create clickable link for just the PR number
pr_number_text = f"#{pr['number']}"
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
cout("{} ", clickable_number)
cout("{} ", pr["title"], fg="white")
cout("({}) ", pr["headRefName"], fg="gray")
cout("Updated: {}\n", pr["updatedAt"][:10], fg="gray")
else:
# Full format with clickable PR number
pr_number_text = f"#{pr['number']}"
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
cout("{} ", clickable_number)
cout("{}\n", pr["title"], fg=color)
cout(" {} -> {}\n", pr["headRefName"], pr["baseRefName"], fg="gray")
cout(" {}\n", pr["url"], fg="blue")
cout(" Updated: {}, Created: {}\n\n", pr["updatedAt"][:10], pr["createdAt"][:10], fg="gray")

# Display categorized authored PRs
if waiting_on_me:
cout("Your PRs - Waiting on You:\n", fg="red")
display_pr_list(waiting_on_me, "white")
if args.compact:
cout("\n")
else:
cout("\n")

if waiting_on_review:
cout("Your PRs - Waiting on Review:\n", fg="yellow")
display_pr_list(waiting_on_review, "white")
if args.compact:
cout("\n")
else:
cout("\n")

if approved:
cout("Your PRs - Approved:\n", fg="green")
display_pr_list(approved, "white")
if args.compact:
cout("\n")
else:
cout("\n")

if not my_prs_data:
cout("No active pull requests authored by you.\n", fg="green")

# Display PRs waiting for review
if review_prs_data:
cout("Pull Requests Awaiting Your Review:\n", fg="yellow")
for pr in review_prs_data:
if args.compact:
# Compact format with only PR number clickable: "#123 Title (branch) Updated: date"
# Create clickable link for just the PR number
pr_number_text = f"#{pr['number']}"
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
cout("{} ", clickable_number)
cout("{} ", pr["title"], fg="white")
cout("({}) ", pr["headRefName"], fg="gray")
cout("by {} ", pr["author"]["login"], fg="gray")
cout("Updated: {}\n", pr["updatedAt"][:10], fg="gray")
else:
# Full format with clickable PR number
pr_number_text = f"#{pr['number']}"
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
cout("{} ", clickable_number)
cout("{}\n", pr["title"], fg="white")
cout(" {} -> {}\n", pr["headRefName"], pr["baseRefName"], fg="gray")
cout(" Author: {}\n", pr["author"]["login"], fg="gray")
cout(" {}\n", pr["url"], fg="blue")
cout(" Updated: {}, Created: {}\n\n", pr["updatedAt"][:10], pr["createdAt"][:10], fg="gray")
else:
cout("No pull requests awaiting your review.\n", fg="yellow")


def main():
logging.basicConfig(format=_LOGGING_FORMAT, level=logging.INFO)
try:
Expand Down Expand Up @@ -1726,6 +1905,12 @@ def main():
branch_new_parser.add_argument("name", help="Branch name")
branch_new_parser.set_defaults(func=cmd_branch_new)

branch_commit_parser = branch_subparsers.add_parser("commit", help="Create a new branch and commit all changes")
branch_commit_parser.add_argument("name", help="Branch name")
branch_commit_parser.add_argument("-m", help="Commit message", dest="message")
branch_commit_parser.add_argument("-a", action="store_true", help="Add all files to commit", dest="add_all")
branch_commit_parser.set_defaults(func=cmd_branch_commit)

branch_checkout_parser = branch_subparsers.add_parser("checkout", aliases=["co"], help="Checkout a branch")
branch_checkout_parser.add_argument("name", help="Branch name", nargs="?")
branch_checkout_parser.set_defaults(func=cmd_branch_checkout)
Expand Down Expand Up @@ -1838,6 +2023,11 @@ def main():
checkout_parser = subparsers.add_parser("sco", help="Checkout a branch in this stack")
checkout_parser.set_defaults(func=cmd_stack_checkout)

# inbox
inbox_parser = subparsers.add_parser("inbox", help="List all active GitHub pull requests for the current user")
inbox_parser.add_argument("--compact", "-c", action="store_true", help="Show compact view")
inbox_parser.set_defaults(func=cmd_inbox)

args = parser.parse_args()
logging.basicConfig(format=_LOGGING_FORMAT, level=LOGLEVELS[args.log_level], force=True)

Expand Down