Skip to content

Commit f961e71

Browse files
authored
Adding new command stacky inbox to show PR inbox (#4)
1 parent eb4b6e4 commit f961e71

File tree

2 files changed

+163
-4
lines changed

2 files changed

+163
-4
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ Stacky doesn't use any git or Github APIs. It expects `git` and `gh` cli command
3333
## Usage
3434
`stacky` stores all information locally, within your git repository
3535
Syntax is as follows:
36-
- `stacky info`: show all stacks , add `-pr` if you want to see GitHub PR numbers (slows things down a bit)
36+
- `stacky info`: show all stacks , add `-pr` if you want to see GitHub PR numbers (slows things down a bit)
37+
- `stacky inbox [--compact]`: show all active GitHub pull requests for the current user, organized by status (waiting on you, waiting on review, approved, and PRs awaiting your review). Use `--compact` or `-c` for a condensed one-line-per-PR view with clickable PR numbers.
3738
- `stacky branch`: per branch commands (shortcut: `stacky b`)
3839
- `stacky branch up` (`stacky b u`): move down the stack (towards `master`)
39-
- `stacky branch down` (`stacky b d`): move down the stack (towards `master`)
40+
- `stacky branch down` (`stacky b d`): move down the stack (towards `master`)
4041
- `stacky branch new <name>`: create a new branch on top of the current one
4142
- `stacky branch commit <name> [-m <message>] [-a]`: create a new branch and commit changes in one command
4243
- `stacky commit [-m <message>] [--amend] [--allow-empty] [-a]`: wrapper around `git commit` that syncs everything upstack
@@ -57,12 +58,12 @@ The indicators (`*`, `~`, `!`) mean:
5758
```
5859
$ stacky --help
5960
usage: stacky [-h] [--color {always,auto,never}]
60-
{continue,info,commit,amend,branch,b,stack,s,upstack,us,downstack,ds,update,import,adopt,land,push,sync,checkout,co,sco} ...
61+
{continue,info,commit,amend,branch,b,stack,s,upstack,us,downstack,ds,update,import,adopt,land,push,sync,checkout,co,sco,inbox} ...
6162
6263
Handle git stacks
6364
6465
positional arguments:
65-
{continue,info,commit,amend,branch,b,stack,s,upstack,us,downstack,ds,update,import,adopt,land,push,sync,checkout,co,sco}
66+
{continue,info,commit,amend,branch,b,stack,s,upstack,us,downstack,ds,update,import,adopt,land,push,sync,checkout,co,sco,inbox}
6667
continue Continue previously interrupted command
6768
info Stack info
6869
commit Commit
@@ -72,12 +73,14 @@ positional arguments:
7273
upstack (us) Operations on the current upstack
7374
downstack (ds) Operations on the current downstack
7475
update Update repo
76+
import Import Graphite stack
7577
adopt Adopt one branch
7678
land Land bottom-most PR on current stack
7779
push Alias for downstack push
7880
sync Alias for stack sync
7981
checkout (co) Checkout a branch
8082
sco Checkout a branch in this stack
83+
inbox List all active GitHub pull requests for the current user
8184
8285
optional arguments:
8386
-h, --help show this help message and exit

src/stacky/stacky.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,157 @@ def cmd_land(stack: StackBranchSet, args):
16881688
cout("\n✓ Success! Run `stacky update` to update local state.\n", fg="green")
16891689

16901690

1691+
def cmd_inbox(stack: StackBranchSet, args):
1692+
"""List all active GitHub pull requests for the current user"""
1693+
fields = [
1694+
"number",
1695+
"title",
1696+
"headRefName",
1697+
"baseRefName",
1698+
"state",
1699+
"url",
1700+
"createdAt",
1701+
"updatedAt",
1702+
"author",
1703+
"reviewDecision",
1704+
"reviewRequests"
1705+
]
1706+
1707+
# Get all open PRs authored by the current user
1708+
my_prs_data = json.loads(
1709+
run_always_return(
1710+
CmdArgs(
1711+
[
1712+
"gh",
1713+
"pr",
1714+
"list",
1715+
"--json",
1716+
",".join(fields),
1717+
"--state",
1718+
"open",
1719+
"--author",
1720+
"@me"
1721+
]
1722+
)
1723+
)
1724+
)
1725+
1726+
# Get all open PRs where current user is requested as reviewer
1727+
review_prs_data = json.loads(
1728+
run_always_return(
1729+
CmdArgs(
1730+
[
1731+
"gh",
1732+
"pr",
1733+
"list",
1734+
"--json",
1735+
",".join(fields),
1736+
"--state",
1737+
"open",
1738+
"--search",
1739+
"review-requested:@me"
1740+
]
1741+
)
1742+
)
1743+
)
1744+
1745+
# Categorize my PRs based on review status
1746+
waiting_on_me = []
1747+
waiting_on_review = []
1748+
approved = []
1749+
1750+
for pr in my_prs_data:
1751+
if pr["reviewDecision"] == "APPROVED":
1752+
approved.append(pr)
1753+
elif pr["reviewRequests"] and len(pr["reviewRequests"]) > 0:
1754+
waiting_on_review.append(pr)
1755+
else:
1756+
# No pending review requests, likely needs changes or author action
1757+
waiting_on_me.append(pr)
1758+
1759+
# Sort all lists by updatedAt in descending order (most recent first)
1760+
waiting_on_me.sort(key=lambda pr: pr["updatedAt"], reverse=True)
1761+
waiting_on_review.sort(key=lambda pr: pr["updatedAt"], reverse=True)
1762+
approved.sort(key=lambda pr: pr["updatedAt"], reverse=True)
1763+
review_prs_data.sort(key=lambda pr: pr["updatedAt"], reverse=True)
1764+
1765+
def display_pr_list(prs, color="white"):
1766+
for pr in prs:
1767+
if args.compact:
1768+
# Compact format with only PR number clickable: "#123 Title (branch) Updated: date"
1769+
# Create clickable link for just the PR number
1770+
pr_number_text = f"#{pr['number']}"
1771+
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
1772+
cout("{} ", clickable_number)
1773+
cout("{} ", pr["title"], fg="white")
1774+
cout("({}) ", pr["headRefName"], fg="gray")
1775+
cout("Updated: {}\n", pr["updatedAt"][:10], fg="gray")
1776+
else:
1777+
# Full format with clickable PR number
1778+
pr_number_text = f"#{pr['number']}"
1779+
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
1780+
cout("{} ", clickable_number)
1781+
cout("{}\n", pr["title"], fg=color)
1782+
cout(" {} -> {}\n", pr["headRefName"], pr["baseRefName"], fg="gray")
1783+
cout(" {}\n", pr["url"], fg="blue")
1784+
cout(" Updated: {}, Created: {}\n\n", pr["updatedAt"][:10], pr["createdAt"][:10], fg="gray")
1785+
1786+
# Display categorized authored PRs
1787+
if waiting_on_me:
1788+
cout("Your PRs - Waiting on You:\n", fg="red")
1789+
display_pr_list(waiting_on_me, "white")
1790+
if args.compact:
1791+
cout("\n")
1792+
else:
1793+
cout("\n")
1794+
1795+
if waiting_on_review:
1796+
cout("Your PRs - Waiting on Review:\n", fg="yellow")
1797+
display_pr_list(waiting_on_review, "white")
1798+
if args.compact:
1799+
cout("\n")
1800+
else:
1801+
cout("\n")
1802+
1803+
if approved:
1804+
cout("Your PRs - Approved:\n", fg="green")
1805+
display_pr_list(approved, "white")
1806+
if args.compact:
1807+
cout("\n")
1808+
else:
1809+
cout("\n")
1810+
1811+
if not my_prs_data:
1812+
cout("No active pull requests authored by you.\n", fg="green")
1813+
1814+
# Display PRs waiting for review
1815+
if review_prs_data:
1816+
cout("Pull Requests Awaiting Your Review:\n", fg="yellow")
1817+
for pr in review_prs_data:
1818+
if args.compact:
1819+
# Compact format with only PR number clickable: "#123 Title (branch) Updated: date"
1820+
# Create clickable link for just the PR number
1821+
pr_number_text = f"#{pr['number']}"
1822+
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
1823+
cout("{} ", clickable_number)
1824+
cout("{} ", pr["title"], fg="white")
1825+
cout("({}) ", pr["headRefName"], fg="gray")
1826+
cout("by {} ", pr["author"]["login"], fg="gray")
1827+
cout("Updated: {}\n", pr["updatedAt"][:10], fg="gray")
1828+
else:
1829+
# Full format with clickable PR number
1830+
pr_number_text = f"#{pr['number']}"
1831+
clickable_number = f"\033]8;;{pr['url']}\033\\\033[96m{pr_number_text}\033[0m\033]8;;\033\\"
1832+
cout("{} ", clickable_number)
1833+
cout("{}\n", pr["title"], fg="white")
1834+
cout(" {} -> {}\n", pr["headRefName"], pr["baseRefName"], fg="gray")
1835+
cout(" Author: {}\n", pr["author"]["login"], fg="gray")
1836+
cout(" {}\n", pr["url"], fg="blue")
1837+
cout(" Updated: {}, Created: {}\n\n", pr["updatedAt"][:10], pr["createdAt"][:10], fg="gray")
1838+
else:
1839+
cout("No pull requests awaiting your review.\n", fg="yellow")
1840+
1841+
16911842
def main():
16921843
logging.basicConfig(format=_LOGGING_FORMAT, level=logging.INFO)
16931844
try:
@@ -1872,6 +2023,11 @@ def main():
18722023
checkout_parser = subparsers.add_parser("sco", help="Checkout a branch in this stack")
18732024
checkout_parser.set_defaults(func=cmd_stack_checkout)
18742025

2026+
# inbox
2027+
inbox_parser = subparsers.add_parser("inbox", help="List all active GitHub pull requests for the current user")
2028+
inbox_parser.add_argument("--compact", "-c", action="store_true", help="Show compact view")
2029+
inbox_parser.set_defaults(func=cmd_inbox)
2030+
18752031
args = parser.parse_args()
18762032
logging.basicConfig(format=_LOGGING_FORMAT, level=LOGLEVELS[args.log_level], force=True)
18772033

0 commit comments

Comments
 (0)