Skip to content

Instantly share code, notes, and snippets.

@amjames
Created August 14, 2026 14:28
Show Gist options
  • Select an option

  • Save amjames/d07a373b722a548b64ec0d3cb5b04e46 to your computer and use it in GitHub Desktop.

Select an option

Save amjames/d07a373b722a548b64ec0d3cb5b04e46 to your computer and use it in GitHub Desktop.
ghstack user tutorial notebook
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "d514f893-1b19-45a0-aa31-8eabee075e6b",
"metadata": {},
"source": [
"# Hands-on tutorial for [`ghstack`]\n",
"\n",
"Large monolothic pull requests (PRs) are hard to review and land since they are touching a lot of files in the repository. One could split the PR in multiple smaller ones and merge them one-by-one. This significantly slows down the developing speed since the dependency of the PRs makes it neccessary to only send a new PR if the previous one was merged. A possible answer to this problem is [`ghstack`] by [Edward Yang](http://ezyang.com/) that is heavily used in the [PyTorch repository](https://github.com/pytorch/pytorch). \n",
"\n",
"This is a hands-on tutorial for [`ghstack`] that answers the following questions:\n",
"\n",
"1. How do I use [`ghstack`]?\n",
"2. How do I update an outdated stack?\n",
"3. How do I add PRs to a stack?\n",
"4. How do I remove PRs from a stack?\n",
"\n",
"For more background information: \n",
"* [this presentation](https://drive.google.com/file/d/1wwoGZGDP9J31WjsK9ii35yVsj5_w2etq) by [Peter Bell](https://github.com/peterbell10)\n",
"* [this podcast](https://pytorch-dev-podcast.simplecast.com/episodes/stacked-diffs-and-ghstack) by [Edward Yang](http://ezyang.com/).\n",
"\n",
"Tools\n",
"* [ghstack-tui](https://github.com/guilhermeleobas/ghstack-tui) Helpful to manage multiple stacks at the same time, know what is actionable etc. \n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "markdown",
"id": "0a7aa4f7-08f1-495b-9917-26a8dc8bd8e7",
"metadata": {},
"source": [
"## Prerequisites"
]
},
{
"cell_type": "markdown",
"id": "eec71402-fd92-4d99-addd-a62da037c7be",
"metadata": {},
"source": [
"As the name implies, [`ghstack`] is a tool for [GitHub]. To interact with [GitHub] directly and observe [`ghstack`]'s changes, we use the [GitHub CLI] here. All steps can also performed manually.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[GitHub]: https://github.com\n",
"[GitHub CLI]: https://github.com/cli/cli"
]
},
{
"cell_type": "markdown",
"id": "e71babeb-2aa1-4a42-b98c-9df0dc9440a6",
"metadata": {},
"source": [
"To begin we create a new repository locally as well as on [GitHub].\n",
"\n",
"[GitHub]: https://github.com"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "07ff1dfb-03fd-405c-9395-467e00f44f26",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b]11;?\u001b\\\u001b[6n\u001b[0;32m✓\u001b[0m Created repository amjames/ghstack-tutorial on github.com\n",
" https://github.com/amjames/ghstack-tutorial\n",
"/Users/ajay/playground/ghstack/ghstack-tutorial\n"
]
}
],
"source": [
"!gh repo create ghstack-tutorial --public --clone\n",
"_ = %pushd ghstack-tutorial"
]
},
{
"cell_type": "markdown",
"id": "79369185-9650-4b6c-b61b-564282aabc6f",
"metadata": {},
"source": [
"Unfortunately, we can't view pull requests (PRs) directly with the [GitHub CLI] due to encoding issue, but we use a workaround using [`IPython`](https://ipython.org/) [magics](https://ipython.readthedocs.io/en/stable/interactive/magics.html).\n",
"\n",
"[GitHub CLI]: https://github.com/cli/cli"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "54bd0dd9-6460-442f-a221-075796eeaa67",
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import Markdown\n",
"\n",
"def view_pr(number):\n",
" meta = !gh pr view $number\n",
" idx = meta.index(\"--\")\n",
" return Markdown(\"\\n\".join(line for line in meta[idx + 1:] if line ))"
]
},
{
"cell_type": "markdown",
"id": "6ea65838-fc51-4d5c-8ca3-48c2cfffce2a",
"metadata": {},
"source": [
"Finally, we add some commits to our default branch."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4fdfcf28-51cd-476b-9eba-044316ab0223",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[main (root-commit) f31bd9a] a\n",
" 1 file changed, 1 insertion(+)\n",
" create mode 100644 busy.txt\n",
"[main 9f06c23] b\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"[main aa5891a] c\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"Enumerating objects: 9, done.\n",
"Counting objects: 100% (9/9), done.\n",
"Delta compression using up to 8 threads\n",
"Compressing objects: 100% (3/3), done.\n",
"Writing objects: 100% (9/9), 580 bytes | 580.00 KiB/s, done.\n",
"Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)\n",
"remote: Resolving deltas: 100% (1/1), done.\u001b[K\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" * [new branch] main -> main\n",
"branch 'main' set up to track 'origin/main'.\n",
"* \u001b[33maa5891a\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmain\u001b[m\u001b[33m, \u001b[m\u001b[1;31morigin/main\u001b[m\u001b[33m)\u001b[m c\n",
"* \u001b[33m9f06c23\u001b[m b\n",
"* \u001b[33mf31bd9a\u001b[m a\n"
]
}
],
"source": [
"!touch busy.txt\n",
"!git add busy.txt\n",
"!for x in {a..c}; do echo $x > busy.txt && git commit -am $x; done\n",
"!git push --set-upstream origin main\n",
"!git log --all --decorate --oneline --graph"
]
},
{
"cell_type": "markdown",
"id": "c3af01ed-c981-455f-b5a8-a4bbedbaf45e",
"metadata": {},
"source": [
"## How do I use [`ghstack`]?\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "markdown",
"id": "02e43e3b-8a51-47c1-9fbd-6b3fd91241c7",
"metadata": {},
"source": [
"The most important thing to remember when using [`ghstack`] is that a commit and a PR are equivalent. That means to create a stack of three PRs, you need three local commits.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f7f853bb-ac63-42a6-be5b-2dbadb49db94",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Switched to a new branch 'my-feature-branch'\n",
"[my-feature-branch 9db30e0] 1\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"[my-feature-branch 5617c8d] 2\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"[my-feature-branch 6bcde0d] 3\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"* \u001b[33m6bcde0d\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmy-feature-branch\u001b[m\u001b[33m)\u001b[m 3\n",
"* \u001b[33m5617c8d\u001b[m 2\n",
"* \u001b[33m9db30e0\u001b[m 1\n",
"* \u001b[33maa5891a\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/main\u001b[m\u001b[33m, \u001b[m\u001b[1;32mmain\u001b[m\u001b[33m)\u001b[m c\n",
"* \u001b[33m9f06c23\u001b[m b\n",
"* \u001b[33mf31bd9a\u001b[m a\n"
]
}
],
"source": [
"!git checkout -b my-feature-branch\n",
"!for x in {1..3}; do echo \"c${x}\" > busy.txt && git commit -am \"${x}\"; done\n",
"!git log --all --decorate --oneline --graph"
]
},
{
"cell_type": "markdown",
"id": "638de1fc-1258-430f-ba87-37468c112fa8",
"metadata": {},
"source": [
"To create the corresponding PRs, you only need to run [`ghstack`]. Note that you need to configure its access to your GitHub account the first time you run it.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e8f1c7fe-6246-4ed9-9de6-d894e4ddda5f",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD 6bcde0d50a9392b649b0ad14b8a547e782be4d15 5617c8d1a296a0b3dae623a266a768a4fd8b69ac 9db30e09823b2f0b7e03d90e76cb6598fe8b7dd1 '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git for-each-ref refs/remotes/origin/gh/amjames '--format=%(refname)'\n",
"$ git merge-base aa5891a267b8074f62a72e7e78e7ba42720275cb origin/main\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p aa5891a267b8074f62a72e7e78e7ba42720275cb 53f9d12659faef4f741a5779023de942545cf978\n",
"$ git commit-tree -p 2b8cf387344e0f4320d9a3105ed2599176364b31 d567f9188c7c29e9b553281e520dd5fb955b33de\n",
"$ git for-each-ref refs/remotes/origin/gh/amjames '--format=%(refname)'\n",
"$ git merge-base 9db30e09823b2f0b7e03d90e76cb6598fe8b7dd1 origin/main\n",
"$ git commit-tree -p aa5891a267b8074f62a72e7e78e7ba42720275cb d567f9188c7c29e9b553281e520dd5fb955b33de\n",
"$ git commit-tree -p bf3dc727c12160edea5cf2017ec424f3f32caa56 10f31f3bf39d79216292b28bbd5a05dc8107e220\n",
"$ git for-each-ref refs/remotes/origin/gh/amjames '--format=%(refname)'\n",
"$ git merge-base 5617c8d1a296a0b3dae623a266a768a4fd8b69ac origin/main\n",
"$ git commit-tree -p aa5891a267b8074f62a72e7e78e7ba42720275cb 10f31f3bf39d79216292b28bbd5a05dc8107e220\n",
"$ git commit-tree -p 9cc3d5f162bcebdec4cd5a13053b079e8c22b502 aaace8e120828249b5a2d230eedc925ab9693354\n",
"$ git push origin --no-verify 2b8cf387344e0f4320d9a3105ed2599176364b31:refs/heads/gh/amjames/1/base 3e171978d3c27c3e41e4577ff97c1d4ff51a5bba:refs/heads/gh/amjames/1/head bf3dc727c12160edea5cf2017ec424f3f32caa56:refs/heads/gh/amjames/2/base b93d5fbe830f66b80f0778a198a27aa6d6553cd9:refs/heads/gh/amjames/2/head 9cc3d5f162bcebdec4cd5a13053b079e8c22b502:refs/heads/gh/amjames/3/base d20bb78ede647bfd6cdfd37970cf11a813c433d1:refs/heads/gh/amjames/3/head\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" * [new branch] 2b8cf387344e0f4320d9a3105ed2599176364b31 -> gh/amjames/1/base\n",
" * [new branch] 3e171978d3c27c3e41e4577ff97c1d4ff51a5bba -> gh/amjames/1/head\n",
" * [new branch] bf3dc727c12160edea5cf2017ec424f3f32caa56 -> gh/amjames/2/base\n",
" * [new branch] b93d5fbe830f66b80f0778a198a27aa6d6553cd9 -> gh/amjames/2/head\n",
" * [new branch] 9cc3d5f162bcebdec4cd5a13053b079e8c22b502 -> gh/amjames/3/base\n",
" * [new branch] d20bb78ede647bfd6cdfd37970cf11a813c433d1 -> gh/amjames/3/head\n",
"Opened PR #1\n",
"Opened PR #2\n",
"Opened PR #3\n",
"$ git commit-tree -p aa5891a267b8074f62a72e7e78e7ba42720275cb d567f9188c7c29e9b553281e520dd5fb955b33de\n",
"$ git commit-tree -p a7270353e101aab84cc96a79ddfe12aedcbc39cf 10f31f3bf39d79216292b28bbd5a05dc8107e220\n",
"$ git commit-tree -p 75a8fbe105b0f5b26f31d247f9844d3d14a710b3 aaace8e120828249b5a2d230eedc925ab9693354\n",
"$ git push origin --no-verify +a7270353e101aab84cc96a79ddfe12aedcbc39cf:refs/heads/gh/amjames/1/orig +75a8fbe105b0f5b26f31d247f9844d3d14a710b3:refs/heads/gh/amjames/2/orig +ca109ab4c8522bac4ad65039dfbabbf642f93be0:refs/heads/gh/amjames/3/orig\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" * [new branch] a7270353e101aab84cc96a79ddfe12aedcbc39cf -> gh/amjames/1/orig\n",
" * [new branch] 75a8fbe105b0f5b26f31d247f9844d3d14a710b3 -> gh/amjames/2/orig\n",
" * [new branch] ca109ab4c8522bac4ad65039dfbabbf642f93be0 -> gh/amjames/3/orig\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Created https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Created https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Created https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft ca109ab4c8522bac4ad65039dfbabbf642f93be0\n",
"[ghstack timing] fetch: 730ms\n",
"[ghstack timing] parse_revs: 52ms\n",
"[ghstack timing] prepare_updates: 4454ms\n",
"[ghstack timing] push_updates: 2440ms\n",
"[ghstack timing] finalize: 320ms\n",
"[ghstack timing] total: 7997ms\n"
]
}
],
"source": [
"!ghstack"
]
},
{
"cell_type": "markdown",
"id": "bd42a1e4-347b-4841-b029-42cbbfd56259",
"metadata": {},
"source": [
"Before we look at the PRs, lets look at our repository structure first:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "00b4dec1-33f0-40bd-8923-c2115de701c1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* \u001b[33mca109ab\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmy-feature-branch\u001b[m\u001b[33m, \u001b[m\u001b[1;31morigin/gh/amjames/3/orig\u001b[m\u001b[33m)\u001b[m 3\n",
"* \u001b[33m75a8fbe\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/orig\u001b[m\u001b[33m)\u001b[m 2\n",
"* \u001b[33ma727035\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/orig\u001b[m\u001b[33m)\u001b[m 1\n",
"\u001b[31m|\u001b[m * \u001b[33md20bb78\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33m9cc3d5f\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33m3e17197\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33m2b8cf38\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33mb93d5fb\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33mbf3dc72\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"* \u001b[33maa5891a\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/main\u001b[m\u001b[33m, \u001b[m\u001b[1;32mmain\u001b[m\u001b[33m)\u001b[m c\n",
"* \u001b[33m9f06c23\u001b[m b\n",
"* \u001b[33mf31bd9a\u001b[m a\n"
]
}
],
"source": [
"!git log --all --decorate --oneline --graph"
]
},
{
"cell_type": "markdown",
"id": "da42d0eb-f5b6-40b6-8b95-a9a017bd4274",
"metadata": {},
"source": [
"As you can see [`ghstack`] created a bunch of branches. Fortunately, we don't need to worry about this and can have a look at the stack on [GitHub].\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[GitHub]: https://github.com"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "a3af54da-6826-49d8-a8d1-68733b697a26",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* __->__ #3\n",
"* #2\n",
"* #1"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(3)"
]
},
{
"cell_type": "markdown",
"id": "a8d32d80-bb8b-4598-83fd-ecf41e48507a",
"metadata": {},
"source": [
"Each PR comment has the above structure. The individual PRs are displayed in a list and the current PR is highlighted in bold. The title of the PR corresponds to the first line of the commit message. If you add a longer description in the commit message, it will be displayed below the list."
]
},
{
"cell_type": "markdown",
"id": "caf656d7-f9ca-4188-ae69-a5ca1a004ed1",
"metadata": {},
"source": [
"## How do I update an outdated stack?"
]
},
{
"cell_type": "markdown",
"id": "9c89e3b5-475d-49d5-bd44-fbd0812ff552",
"metadata": {},
"source": [
"It is a common scenario someone else's PR gets merged before yours resulting in merge conflicts. "
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "48ac14b0-71e8-487d-93bb-d1063ce66c84",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Switched to branch 'main'\n",
"Your branch is up to date with 'origin/main'.\n",
"[main 8b7d969] d\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"Enumerating objects: 5, done.\n",
"Counting objects: 100% (5/5), done.\n",
"Writing objects: 100% (3/3), 243 bytes | 243.00 KiB/s, done.\n",
"Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" aa5891a..8b7d969 main -> main\n",
"Switched to branch 'my-feature-branch'\n",
"* \u001b[33m8b7d969\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/main\u001b[m\u001b[33m, \u001b[m\u001b[1;32mmain\u001b[m\u001b[33m)\u001b[m d\n",
"\u001b[31m|\u001b[m * \u001b[33mca109ab\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmy-feature-branch\u001b[m\u001b[33m, \u001b[m\u001b[1;31morigin/gh/amjames/3/orig\u001b[m\u001b[33m)\u001b[m 3\n",
"\u001b[31m|\u001b[m * \u001b[33m75a8fbe\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/orig\u001b[m\u001b[33m)\u001b[m 2\n",
"\u001b[31m|\u001b[m * \u001b[33ma727035\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/orig\u001b[m\u001b[33m)\u001b[m 1\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33md20bb78\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33m9cc3d5f\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33m3e17197\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33m2b8cf38\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33mb93d5fb\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/head\u001b[m\u001b[33m)\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m * \u001b[33mbf3dc72\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m \n",
"* \u001b[33maa5891a\u001b[m c\n",
"* \u001b[33m9f06c23\u001b[m b\n",
"* \u001b[33mf31bd9a\u001b[m a\n"
]
}
],
"source": [
"!git checkout main\n",
"!echo 'd' > busy.txt && git commit -am 'd'\n",
"!git push\n",
"!git checkout my-feature-branch\n",
"!git log --all --decorate --oneline --graph"
]
},
{
"cell_type": "markdown",
"id": "c6fe98fa-f29a-40b2-b79f-f3807d1ec2d0",
"metadata": {},
"source": [
"To resolve these conflicts when working with [`ghstack`], we need to [`rebase`] (**not** [`merge`]) onto the updated branch and fix the merge conflicts.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[`merge`]: https://git-scm.com/docs/git-merge\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "eeffe2d4-9864-4994-a46c-c10184f1e22f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply a727035... 1\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply a727035... # 1\n",
"\u001b[K[detached HEAD d233aea] 1r to close the file... \n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply 75a8fbe... 2\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply 75a8fbe... # 2\n",
"\u001b[K[detached HEAD 5376b9c] 2r to close the file... \n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply ca109ab... 3\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply ca109ab... # 3\n",
"\u001b[K[detached HEAD adf87b5] 3r to close the file... \n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"\u001b[KSuccessfully rebased and updated refs/heads/my-feature-branch.\n"
]
}
],
"source": [
"!git rebase main\n",
"# The process you would go through is this\n",
"# 1. Git rebase fails indicating conflicts when applying 1 on top of d. \n",
"# commit 1 edits busy.txt contents from \"c\" -> \"c1\"\n",
"# 2. Now the main branch has content \"d\" adding our change would make it \"d1\", \n",
"# so we make that edit you would then:\n",
"# 2.a git add busy.txt\n",
"# 2.b git rebase --continue\n",
"# 2.c Editor opens, you don't edit the commit msg, just quit\n",
"# 3. When you quit the editor then commit 1 is done being applied as part of the rebase\n",
"# 4. It continues to adding commit 2, which also conflicts, repeat step 2a,b,c above replacing 1 with 2. \n",
"# 5. It continues to adding commit 3, which also conflicts, repeat step 2a,b,c above replacing 1 with 3.\n",
"\n",
"# Below `echo \"d${x}\"` is simulating fixing the conflics\n",
"# git add is the same\n",
"# git rebase --continue is prefixed with an envar to block the editor from opening, \n",
"# otherwise we would need an interactive terminal, which you will normally have, but we don't in this notebook. \n",
"!for x in {1..3}; do echo \"d${x}\" > busy.txt && git add busy.txt && GIT_EDITOR=true git rebase --continue; done\n",
"\n",
"# NB: You may find it helpful to set up an alias eg `ghsrbc` is one I use which stands for ghstack rebase continue\n",
"# I have it aliased to the final sequence about `GIT_EDITOR=true git rebase --continue` to specifically skip the edit msg dialog. \n",
"# git rebase does not accept a --no-edit flag, it really would not make any sense except in this particular case using --continue. "
]
},
{
"cell_type": "markdown",
"id": "8159a862-a0fd-4322-b750-daf011ae6cd2",
"metadata": {},
"source": [
"One thing that we ignored up to now is that [`ghstack`] adds some metadata to the commit messages:\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6f2ca6fc-faee-4059-8ba0-104e921c2223",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3\n",
"\n",
"ghstack-source-id: aaace8e120828249b5a2d230eedc925ab9693354\n",
"Pull-Request: https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n"
]
}
],
"source": [
"!git log -1 --pretty=%B"
]
},
{
"cell_type": "markdown",
"id": "73b8b5e1-50a7-4c97-9a93-dd37485f5e65",
"metadata": {},
"source": [
"**Do not edit** these lines manually unless you know what you are doing. This is why we are using the [`--no-edit`] flag throughout this tutorial when fixing merge conflicts, which commits the changes, but leaves the message as is.\n",
"\n",
"[`--no-edit`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---no-edit"
]
},
{
"cell_type": "markdown",
"id": "6276a04b-b9de-40c0-b501-46bf87ecfd5e",
"metadata": {},
"source": [
"After a successful [`rebase`], we can simply run [`ghstack`] again to publish our changes.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "7069a0e8-fe99-4382-a279-4ed952fe0a9a",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD adf87b5bd32b2b41bb015eeb98a634499f5582b1 5376b9c83704c31bfd22bd0afd203951a90503fd d233aeaed397ba98dd095ccab09a5b6fb69eb24a '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/head\n",
"$ git rev-list --header -1 origin/gh/amjames/1/base\n",
"$ git merge-base 8b7d9691a30cbab719da353c5af6336deb86af0e origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e 2b8cf387344e0f4320d9a3105ed2599176364b31\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p 2b8cf387344e0f4320d9a3105ed2599176364b31 -p 8b7d9691a30cbab719da353c5af6336deb86af0e 3c81c65ca6222fc33337867125c7d7d72a40cf99\n",
"$ git commit-tree -p 3e171978d3c27c3e41e4577ff97c1d4ff51a5bba -p debddbdbdd5ca774369ce1323017df3b4e1f6cbe 2594a830dece1135a5905f680311685ce52ffeb4\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/head\n",
"$ git rev-list --header -1 origin/gh/amjames/2/base\n",
"$ git merge-base d233aeaed397ba98dd095ccab09a5b6fb69eb24a origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e bf3dc727c12160edea5cf2017ec424f3f32caa56\n",
"$ git commit-tree -p bf3dc727c12160edea5cf2017ec424f3f32caa56 -p 8b7d9691a30cbab719da353c5af6336deb86af0e 2594a830dece1135a5905f680311685ce52ffeb4\n",
"$ git commit-tree -p b93d5fbe830f66b80f0778a198a27aa6d6553cd9 -p 2ee207ad472a1f5d14e5574c83a1ee318b594201 aa9dacf3c5524b568ed376c4f5100152b68c9d1f\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/head\n",
"$ git rev-list --header -1 origin/gh/amjames/3/base\n",
"$ git merge-base 5376b9c83704c31bfd22bd0afd203951a90503fd origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e 9cc3d5f162bcebdec4cd5a13053b079e8c22b502\n",
"$ git commit-tree -p 9cc3d5f162bcebdec4cd5a13053b079e8c22b502 -p 8b7d9691a30cbab719da353c5af6336deb86af0e aa9dacf3c5524b568ed376c4f5100152b68c9d1f\n",
"$ git commit-tree -p d20bb78ede647bfd6cdfd37970cf11a813c433d1 -p 2ff95eba3d23ecd59f67f9ec8b232fe88c976001 208075489ff2294cb3b6f1d5c5edc9167b95595b\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 2594a830dece1135a5905f680311685ce52ffeb4\n",
"$ git commit-tree -p 8301ba6ab4be0972acc3f315dca6519f1580d779 aa9dacf3c5524b568ed376c4f5100152b68c9d1f\n",
"$ git commit-tree -p 735a73e5d0f22c4293c86b8aa5daecf836003f54 208075489ff2294cb3b6f1d5c5edc9167b95595b\n",
"$ git push origin --no-verify +8301ba6ab4be0972acc3f315dca6519f1580d779:refs/heads/gh/amjames/1/orig debddbdbdd5ca774369ce1323017df3b4e1f6cbe:refs/heads/gh/amjames/1/base df114606ee08f65f6cca55809d01cb99e5e55742:refs/heads/gh/amjames/1/head +735a73e5d0f22c4293c86b8aa5daecf836003f54:refs/heads/gh/amjames/2/orig 2ee207ad472a1f5d14e5574c83a1ee318b594201:refs/heads/gh/amjames/2/base e076b5e7a541280034688738fa5e84f8f941797d:refs/heads/gh/amjames/2/head +1a7b45bb8b0cd4d1b1c90160b8bbec7d7740b814:refs/heads/gh/amjames/3/orig 2ff95eba3d23ecd59f67f9ec8b232fe88c976001:refs/heads/gh/amjames/3/base 715e3494d9deba18b277951fc003265c27e58a63:refs/heads/gh/amjames/3/head\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" 2b8cf38..debddbd debddbdbdd5ca774369ce1323017df3b4e1f6cbe -> gh/amjames/1/base\n",
" 3e17197..df11460 df114606ee08f65f6cca55809d01cb99e5e55742 -> gh/amjames/1/head\n",
" + a727035...8301ba6 8301ba6ab4be0972acc3f315dca6519f1580d779 -> gh/amjames/1/orig (forced update)\n",
" bf3dc72..2ee207a 2ee207ad472a1f5d14e5574c83a1ee318b594201 -> gh/amjames/2/base\n",
" b93d5fb..e076b5e e076b5e7a541280034688738fa5e84f8f941797d -> gh/amjames/2/head\n",
" + 75a8fbe...735a73e 735a73e5d0f22c4293c86b8aa5daecf836003f54 -> gh/amjames/2/orig (forced update)\n",
" 9cc3d5f..2ff95eb 2ff95eba3d23ecd59f67f9ec8b232fe88c976001 -> gh/amjames/3/base\n",
" d20bb78..715e349 715e3494d9deba18b277951fc003265c27e58a63 -> gh/amjames/3/head\n",
" + ca109ab...1a7b45b 1a7b45bb8b0cd4d1b1c90160b8bbec7d7740b814 -> gh/amjames/3/orig (forced update)\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft 1a7b45bb8b0cd4d1b1c90160b8bbec7d7740b814\n",
"[ghstack timing] fetch: 642ms\n",
"[ghstack timing] parse_revs: 51ms\n",
"[ghstack timing] prepare_updates: 1290ms\n",
"[ghstack timing] push_updates: 1881ms\n",
"[ghstack timing] finalize: 78ms\n",
"[ghstack timing] total: 3942ms\n"
]
}
],
"source": [
"!ghstack"
]
},
{
"cell_type": "markdown",
"id": "35b20819-cd37-49bc-9a47-49f9ea79cb69",
"metadata": {},
"source": [
"Notice in the log above we updated all three pre-existing PRs, we did not create a new one for commit D which we added directly to main. \n",
"Generally when you rebase you will update each PR, even if you do not have conflicts to fix. When do you have conflicts to fix then naturally those commits are updated. "
]
},
{
"cell_type": "markdown",
"id": "6f4a8172-1737-4db9-b26f-75fe985efa7d",
"metadata": {},
"source": [
"## How do I add changes to a PR?"
]
},
{
"cell_type": "markdown",
"id": "37b46073-8ff0-467e-8000-19b9ee3914ba",
"metadata": {},
"source": [
"If someone suggests changes to a PR, we need a way to change it. Recall that for [`ghstack`] a PR and a commit are equivalent. Thus, to add changes to a PR, we need to fix the commit inplace with the [`--amend`] flag.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend"
]
},
{
"cell_type": "markdown",
"id": "43bcf969-b811-4e50-b4c6-bb4366277985",
"metadata": {},
"source": [
"If we need to fix the most recent commit, we can add our changes and commit with [`--amend`] directly.\n",
"\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend"
]
},
{
"cell_type": "markdown",
"id": "95628d9a-62b7-40e9-98f7-c0c1a9a33ec8",
"metadata": {},
"source": [
"Recall: We are on our feature branch, at commit 3 which is associated with PR 3 on GH"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "91cdb8e1-3400-4f38-addd-0a087a725225",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* \u001b[33m1a7b45b\u001b[m\u001b[33m (\u001b[m\u001b[1;36mHEAD\u001b[m\u001b[33m -> \u001b[m\u001b[1;32mmy-feature-branch\u001b[m\u001b[33m, \u001b[m\u001b[1;31morigin/gh/amjames/3/orig\u001b[m\u001b[33m)\u001b[m 3\n",
"* \u001b[33m735a73e\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/orig\u001b[m\u001b[33m)\u001b[m 2\n",
"* \u001b[33m8301ba6\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/orig\u001b[m\u001b[33m)\u001b[m 1\n",
"\u001b[31m|\u001b[m * \u001b[33me076b5e\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/head\u001b[m\u001b[33m)\u001b[m [UPDATE] Update\n",
"\u001b[31m|\u001b[m \u001b[32m|\u001b[m\u001b[33m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[32m|\u001b[m * \u001b[33m2ee207a\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/2/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m \u001b[32m|\u001b[m \u001b[34m|\u001b[m\u001b[31m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[32m|\u001b[m\u001b[31m_\u001b[m\u001b[34m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m\u001b[32m|\u001b[m \u001b[34m|\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[34m|\u001b[m \u001b[33mb93d5fb\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m \u001b[34m|\u001b[m\u001b[34m/\u001b[m \n",
"\u001b[31m|\u001b[m * \u001b[33mbf3dc72\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m * \u001b[33m715e349\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/head\u001b[m\u001b[33m)\u001b[m [UPDATE] Update\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[36m|\u001b[m\u001b[1;31m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[36m|\u001b[m * \u001b[33m2ff95eb\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/3/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[36m|\u001b[m \u001b[1;32m|\u001b[m\u001b[31m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m\u001b[31m_\u001b[m\u001b[36m|\u001b[m\u001b[31m_\u001b[m\u001b[1;32m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m\u001b[35m|\u001b[m \u001b[36m|\u001b[m \u001b[1;32m|\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m * \u001b[1;32m|\u001b[m \u001b[33md20bb78\u001b[m [INITIAL] Update\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[1;32m|\u001b[m\u001b[1;32m/\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m * \u001b[33m9cc3d5f\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m\u001b[35m/\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m * \u001b[33mdf11460\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/head\u001b[m\u001b[33m)\u001b[m [UPDATE] Update\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[1;34m|\u001b[m\u001b[1;35m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[1;34m|\u001b[m * \u001b[33mdebddbd\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/gh/amjames/1/base\u001b[m\u001b[33m)\u001b[m Update (base update)\n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m \u001b[1;34m|\u001b[m \u001b[1;36m|\u001b[m\u001b[31m\\\u001b[m \n",
"\u001b[31m|\u001b[m \u001b[35m|\u001b[m\u001b[31m_\u001b[m\u001b[1;34m|\u001b[m\u001b[31m_\u001b[m\u001b[1;36m|\u001b[m\u001b[31m/\u001b[m \n",
"\u001b[31m|\u001b[m\u001b[31m/\u001b[m\u001b[35m|\u001b[m \u001b[1;34m|\u001b[m \u001b[1;36m|\u001b[m \n",
"* \u001b[35m|\u001b[m \u001b[1;34m|\u001b[m \u001b[1;36m|\u001b[m \u001b[33m8b7d969\u001b[m\u001b[33m (\u001b[m\u001b[1;31morigin/main\u001b[m\u001b[33m, \u001b[m\u001b[1;32mmain\u001b[m\u001b[33m)\u001b[m d\n",
"\u001b[35m|\u001b[m\u001b[35m/\u001b[m \u001b[1;34m/\u001b[m \u001b[1;36m/\u001b[m \n",
"\u001b[35m|\u001b[m * \u001b[1;36m/\u001b[m \u001b[33m3e17197\u001b[m [INITIAL] Update\n",
"\u001b[35m|\u001b[m \u001b[1;36m|\u001b[m\u001b[1;36m/\u001b[m \n",
"\u001b[35m|\u001b[m * \u001b[33m2b8cf38\u001b[m Update (base update)\n",
"\u001b[35m|\u001b[m\u001b[35m/\u001b[m \n",
"* \u001b[33maa5891a\u001b[m c\n",
"* \u001b[33m9f06c23\u001b[m b\n",
"* \u001b[33mf31bd9a\u001b[m a\n"
]
}
],
"source": [
"!git log --all --decorate --oneline --graph"
]
},
{
"cell_type": "markdown",
"id": "951f7135-3d36-4e6d-af4d-b6f2d2a1d656",
"metadata": {},
"source": [
"If we only need to update PR/commit 3, then we can just make our changes here and commit with [`--amend`] \n",
"\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "93f3cd2a-2e91-464b-a9db-62fa8d3ebe4d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[my-feature-branch b8b6fea] 3\n",
" Date: Thu Aug 13 14:19:22 2026 -0500\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n"
]
}
],
"source": [
"!echo 'd3.0' > busy.txt && git commit -a --amend --no-edit"
]
},
{
"cell_type": "markdown",
"id": "452ea545-f4f0-487d-ac22-e7f73fc9b182",
"metadata": {},
"source": [
"Unfortunately, we can't reach commits further down the stack with [`--amend`]. The solution to this is an [`-i`] / [`--interactive`](https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---interactive) [`rebase`]\n",
"\n",
"Interactive rebases are interactive so simulating them is not so feasible. Read closely in the next few blocks some of the steps will be for you to run in a terminal not actually executed by the notebook.\n",
"\n",
"First lets create a change we want to apply to some other commit than the most recent one. \n",
"\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend\n",
"[`-i`]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt--i"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "8097fe70-5ca0-4f34-96f6-e39bdd4c37f4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[my-feature-branch 283c130] FIXUP: APPLY ME TO 2\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n"
]
}
],
"source": [
"!echo 'd2.0' > busy.txt && git commit -a -m \"FIXUP: APPLY ME TO 2\" "
]
},
{
"cell_type": "markdown",
"id": "d1623a32-1d1b-404f-bb89-d5aed69a1381",
"metadata": {},
"source": [
"I like to use messages like above, particularly if I am making a lot of changes and will need to apply them to different commits. Whenever it is not disruptive it is best practice to apply these kinds of fixup changes directly to the commit they should land on, before moving on.\n",
"\n",
"We start an interactive rebase, the simple form gives an argument which is a ref in the past, and implied is that HEAD is the end of the range. \n",
"\n",
"`git rebase -i main`: rebase plan will show pick for all commits between main and HEAD, since we just did a rebase onto main that means this is just our range of commits. \n",
"\n",
"We can also express the start of the rebase range as an offset backward from HEAD. The syntax for this is `HEAD~N` means N commits before HEAD. I will use a pattern where I choose N = S + F + 1, with S being the number of PRs/commits on my stack, F being the number of Fixup commits I want have on the head of the local branch to be moved into palace. +1 Gives me the base commit on main or viable/strict which serves as a reference point so I can keep my bearings in global history, see if I need to rebease etc. \n",
"\n",
"I will use the first form for this example"
]
},
{
"cell_type": "markdown",
"id": "0f635f50-5c94-4db3-a8a8-df44f7024faa",
"metadata": {},
"source": [
"*In your terminal*: \n",
"\n",
" `git rebase -i main` "
]
},
{
"cell_type": "markdown",
"id": "a476f753-cc67-4449-8e26-74e8da34d266",
"metadata": {},
"source": [
"Running the above command outside of this notebook opens up an editor with a todo list which we can adapt to fit our needs. The default todo list looks like this (with different commit hashes):"
]
},
{
"cell_type": "markdown",
"id": "08152ff0-f74e-4fd6-908c-3e8ed09a5d22",
"metadata": {},
"source": [
"*In your editor*:\n",
"```\n",
"pick 5424294 # 1\n",
"pick d40985e # 2\n",
"pick 1a92199 # 3\n",
"pick 34b9597 # FIXUP apply me to 2\n",
"```\n",
"**NB**: On the recording I was incorrect, ghstack orders commits in the reverse order of the git rebase plan, so the last line in the file describes the most recent commit to be picked. On github the first line in the PR description describes the last PR on the stack. "
]
},
{
"cell_type": "markdown",
"id": "f69e2953-e6a7-4482-bff1-749093c4a40a",
"metadata": {},
"source": [
"First lets do an edit on a previous commit, we can leave the fixup in place for now. \n",
"\n",
"Without modification this will result in the same behavior as if we run [`rebase`] without the [`-i`] flag. If we want to `edit` a commit further down in the stack (or up the plan), the easiest way is to change the `pick` command into `edit`.\n",
"\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase\n",
"[`-i`]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt--i"
]
},
{
"cell_type": "markdown",
"id": "f6598616-bff4-4240-a62b-0c91f947a36d",
"metadata": {},
"source": [
"*In your editor*:\n",
"```\n",
"edit 5424294 1\n",
"pick d40985e 2\n",
"pick 1a92199 3\n",
"pick 34b9597 # FIXUP apply me to 2\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "2541700c-c820-4530-b3e3-88205f3c114c",
"metadata": {},
"source": [
"After you save and exit the editor this will apply the first commit and stop **afterwards** giving us the chance to [`--amend`] it. This is different when we are fixing conflicts on the rebase we did a bit earlier. In that case we failed to apply the commit, and could not ammend the commit since we had not yet applied it.\n",
"\n",
"You should check the output in the terminal to indicate where you are, if a conflict prevented the first commit from applying we may need to handle that, then run continue, then do the edits we wanted. In this case that won't be needed. \n",
"\n",
"You can use the cell below to do that edit, and ammend the commit.\n",
"\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "df98415d-acf4-432f-bb3f-de6ea42f0ecc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[detached HEAD 9826ac6] 1\n",
" Date: Thu Aug 13 14:19:22 2026 -0500\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n"
]
}
],
"source": [
"!echo 'd1.0' > busy.txt && git commit -a --amend --no-edit"
]
},
{
"cell_type": "markdown",
"id": "20cdb9e7-eae4-440d-a896-c5aefd361a3f",
"metadata": {},
"source": [
"After the change is applied we can [`--continue`] the [`rebase`]. This leads to a merge conflict here. For simplicity, we accept our previous commit. Note that the meaning of [`--ours`] and [`--theirs`] is swapped during a [`rebase`].\n",
"\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase\n",
"[`--continue`]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---continue\n",
"[`--ours`]: https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---ours\n",
"[`--theirs`]: https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---theirs"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "f22e7edb-b5a5-46b5-b109-77db203369d8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply 735a73e... 2\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply 735a73e... # 2\n",
"Updated 1 path from the index\n",
"[detached HEAD df2b5d3] 2\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"\u001b[KSuccessfully rebased and updated refs/heads/my-feature-branch.\n"
]
}
],
"source": [
"!git rebase --continue\n",
"!git checkout --theirs .\n",
"!git commit -a --no-edit\n",
"!git rebase --continue"
]
},
{
"cell_type": "markdown",
"id": "3d0194a0-46f7-4e60-a519-d10f37ad0fc3",
"metadata": {},
"source": [
"The above cell continued into applying pick for commit two, which failed due to conflicts, we resolved by picking the `--theirs` version, commited that, then continued again. The final two commits applied cleanly so we are done!\n",
"\n",
"After we successfully added all our changes, if we run [`ghstack`] again that will update PR1, but it will also create a PR for the FIXUP commit we have not cleaned up yet, so we are going to handle that first.\n",
"\n",
"We can do another interactive rebase. \n",
"\n",
"*In your terminal*: \n",
"`git rebase -i main`\n",
"\n",
"*In your editor*: \n",
"\n",
"```\n",
"pick 78489c8 # 1\n",
"pick a55af21 # 2\n",
"pick f1cbd9c # 3\n",
"pick 6adb198 # FIXUP: APPLY ME TO 2\n",
"```\n",
"\n",
"Note that since we did a rebase the commit hashes have changed.\n",
"\n",
"We want to edit the above to apply the last commit as a fixup to commit 2\n",
"\n",
"```\n",
"pick 78489c8 # 1\n",
"pick a55af21 # 2\n",
"fixup 6adb198 # FIXUP: APPLY ME TO 2\n",
"pick f1cbd9c # 3\n",
"```\n",
"\n",
"The following is a snippet from the comment added to the rebase plan to show how to use it. We move out commit \"FIXUP: ...\" immediately after pick commit two. Fixup is descirbed as like \"squash\" but keep only the previous commit's log message. We want that since a55af21 (your hash will be different) holds the ghstack-source-id in its commit message that we don't want to destroy. \n",
"\n",
"```\n",
"# s, squash <commit> = use commit, but meld into previous commit\n",
"# f, fixup [-C | -c] <commit> = like \"squash\" but keep only the previous\n",
"# commit's log message, unless -C is used, in which case\n",
"# keep only this commit's message; -c is same as -C but\n",
"# opens the editor\n",
"```\n",
"\n",
"Save and quit and the rebase will run the plan\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "markdown",
"id": "e39378ec-5bdc-4523-ac7b-9a8d09555f6f",
"metadata": {},
"source": [
"We have a conflict on applying the fixup commit which is essentially the same thing as the conflict we fixed after our manual edit in the last rebase. We can do the same thing to resolve and continue. If we did not care about the commit message we could pick the fixup commit and just drop the other one to get the state we want, but we need to preseve the commit message. "
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "50ebbf51-7f39-4631-8285-c3571a97b062",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[detached HEAD eb9c6a5] 2\n",
" Date: Thu Aug 13 14:20:08 2026 -0500\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply 1740a4c... 3\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply 1740a4c... # 3\n",
"\u001b[K[detached HEAD f94f5f0] 3r to close the file... \n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"\u001b[KSuccessfully rebased and updated refs/heads/my-feature-branch.\n"
]
}
],
"source": [
"!echo 'd2.0' > busy.txt && git commit -a --amend --no-edit\n",
"!GIT_EDITOR=true git rebase --continue\n",
"# Our simple example with a single line in a text file leads to lots of conflicts \n",
"# so we hit another one to resolve when applying the last commit.\n",
"!echo 'd3.0' > busy.txt && git add busy.txt\n",
"!GIT_EDITOR=true git rebase --continue"
]
},
{
"cell_type": "markdown",
"id": "98ae61ac-94b2-4a2f-97a6-3599630bcee5",
"metadata": {},
"source": [
"Now we can run ghstack to update our PRs."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "74420c04-f025-4cd0-b103-fa2b83f43781",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD f94f5f0d9a5324e1aed698349c3e4c09fe46b46d eb9c6a5ab3d44e1133c36dec7568b2a864c76a2c 9826ac6ae1d74e6421ed290f0646cdd54f60fd6b '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/head\n",
"$ git rev-list --header -1 origin/gh/amjames/1/base\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p df114606ee08f65f6cca55809d01cb99e5e55742 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/head\n",
"$ git rev-list --header -1 origin/gh/amjames/2/base\n",
"$ git merge-base 9826ac6ae1d74e6421ed290f0646cdd54f60fd6b origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e 2ee207ad472a1f5d14e5574c83a1ee318b594201\n",
"$ git commit-tree -p 2ee207ad472a1f5d14e5574c83a1ee318b594201 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p e076b5e7a541280034688738fa5e84f8f941797d -p dc45b95074cd087004fa771debc0c8ec5153665b 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/head\n",
"$ git rev-list --header -1 origin/gh/amjames/3/base\n",
"$ git merge-base eb9c6a5ab3d44e1133c36dec7568b2a864c76a2c origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e 2ff95eba3d23ecd59f67f9ec8b232fe88c976001\n",
"$ git commit-tree -p 2ff95eba3d23ecd59f67f9ec8b232fe88c976001 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git commit-tree -p 715e3494d9deba18b277951fc003265c27e58a63 -p 9d692b959f65319ffe3c807f2b828b98daa1e74b 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p dca59395decc4979919676c7cbf9f8c671bd4397 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git commit-tree -p 17da921e132ffdc6a699113439c54ac02aff19ab 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git push origin --no-verify +dca59395decc4979919676c7cbf9f8c671bd4397:refs/heads/gh/amjames/1/orig ae83ef5a51f1fd381b4d4034036b01972e7caa0e:refs/heads/gh/amjames/1/head +17da921e132ffdc6a699113439c54ac02aff19ab:refs/heads/gh/amjames/2/orig dc45b95074cd087004fa771debc0c8ec5153665b:refs/heads/gh/amjames/2/base b5f842a546db541eaa64f203a72f7ae216b62d1c:refs/heads/gh/amjames/2/head +f4d6218d5b704dbeadb84001cf3623e7826a0725:refs/heads/gh/amjames/3/orig 9d692b959f65319ffe3c807f2b828b98daa1e74b:refs/heads/gh/amjames/3/base 22b5079adb2c58f9ccdeb501d79af703b948dd21:refs/heads/gh/amjames/3/head\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" df11460..ae83ef5 ae83ef5a51f1fd381b4d4034036b01972e7caa0e -> gh/amjames/1/head\n",
" + 8301ba6...dca5939 dca59395decc4979919676c7cbf9f8c671bd4397 -> gh/amjames/1/orig (forced update)\n",
" 2ee207a..dc45b95 dc45b95074cd087004fa771debc0c8ec5153665b -> gh/amjames/2/base\n",
" e076b5e..b5f842a b5f842a546db541eaa64f203a72f7ae216b62d1c -> gh/amjames/2/head\n",
" + 735a73e...17da921 17da921e132ffdc6a699113439c54ac02aff19ab -> gh/amjames/2/orig (forced update)\n",
" 2ff95eb..9d692b9 9d692b959f65319ffe3c807f2b828b98daa1e74b -> gh/amjames/3/base\n",
" 715e349..22b5079 22b5079adb2c58f9ccdeb501d79af703b948dd21 -> gh/amjames/3/head\n",
" + 1a7b45b...f4d6218 f4d6218d5b704dbeadb84001cf3623e7826a0725 -> gh/amjames/3/orig (forced update)\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft f4d6218d5b704dbeadb84001cf3623e7826a0725\n",
"[ghstack timing] fetch: 677ms\n",
"[ghstack timing] parse_revs: 62ms\n",
"[ghstack timing] prepare_updates: 1119ms\n",
"[ghstack timing] push_updates: 2297ms\n",
"[ghstack timing] finalize: 161ms\n",
"[ghstack timing] total: 4317ms\n"
]
}
],
"source": [
"!ghstack"
]
},
{
"cell_type": "markdown",
"id": "0c457f3f-206c-4118-b220-93faea89dc52",
"metadata": {},
"source": [
"**Try it on your own**: Go back to the start of this manual section. Pick a different edit (eg. append .1 instead of .0), you can create a new fixup commit and a new edit to make during the interactive rebase. You can do the edit and fixup application in one interactive rebase plan (merge the plan edits decribed above)."
]
},
{
"cell_type": "markdown",
"id": "837e536f-4efc-4149-8595-21fef109c0f4",
"metadata": {},
"source": [
"## How do I add a PR to a stack?"
]
},
{
"cell_type": "markdown",
"id": "12bcb653-7311-4475-9dd3-608d8bbd204a",
"metadata": {},
"source": [
"If the new PR should be on top of the current stack, you can simply add a new commit."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "82215348-6906-4eff-bca8-93d6f4c2b050",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[my-feature-branch a28b096] 4\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n"
]
}
],
"source": [
"!echo 'd4' > busy.txt && git commit -am '4'"
]
},
{
"cell_type": "markdown",
"id": "cf8133b5-1e74-4912-9307-5e7f7e06363a",
"metadata": {},
"source": [
"If the new PR needs be within the current stack, we again need to use an interactive [`rebase`] to get to the point in the stack where the PR should be inserted. This time we use the `break` command to indicate where the [`rebase`] should stop.\n",
"\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase"
]
},
{
"cell_type": "markdown",
"id": "2dac2d2c-731c-400f-b72c-80d74901982e",
"metadata": {},
"source": [
" git rebase -i main"
]
},
{
"cell_type": "markdown",
"id": "1a71784e-19eb-45e3-9296-ed9274753ff7",
"metadata": {},
"source": [
"```\n",
"pick fda2e0e 1\n",
"break\n",
"pick 4be099c 2\n",
"pick e0358e1 3\n",
"pick ae7848d 4\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "5fe31d98-7c3e-45b9-983c-9801138bd3a8",
"metadata": {},
"source": [
"At this point we simply add the new commit. Note that we **don't** use [`--amend`] here, since we don't want to fix the current commit inplace, but rather want to add a new one.\n",
"\n",
"[`--amend`]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "bfeaafa6-76f3-47b7-90e4-78fe775fa309",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[detached HEAD 19fb28b] 1.5\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n"
]
}
],
"source": [
"!echo 'd1.5' > busy.txt && git commit -am '1.5'"
]
},
{
"cell_type": "markdown",
"id": "6a6f1b61-c38f-4291-83f5-cdf4ebd06766",
"metadata": {},
"source": [
"Afterwards we can [`--continue`] the rebase. For simplicity, we are resolving the merge conflict by accepting our previous commit.\n",
"\n",
"[`--continue`]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---continue"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "39968850-eeea-4217-85dd-556285f1dee7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Auto-merging busy.txt\n",
"CONFLICT (content): Merge conflict in busy.txt\n",
"error: could not apply 17da921... 2\n",
"\u001b[33mhint: Resolve all conflicts manually, mark them as resolved with\u001b[m\n",
"\u001b[33mhint: \"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\u001b[m\n",
"\u001b[33mhint: You can instead skip this commit: run \"git rebase --skip\".\u001b[m\n",
"\u001b[33mhint: To abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\u001b[m\n",
"\u001b[33mhint: Disable this message with \"git config set advice.mergeConflict false\"\u001b[m\n",
"Could not apply 17da921... # 2\n",
"Updated 1 path from the index\n",
"[detached HEAD c50b475] 2\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"\u001b[KSuccessfully rebased and updated refs/heads/my-feature-branch.\n"
]
}
],
"source": [
"!git rebase --continue\n",
"!git checkout --theirs .\n",
"!git commit -a --no-edit\n",
"!git rebase --continue"
]
},
{
"cell_type": "markdown",
"id": "2f968c4c-8a2f-4458-b291-6dc7ea287f8e",
"metadata": {},
"source": [
"After we successfully added new commits, we run [`ghstack`] to turn them into PRs in our stack.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "27e9a6c7-650d-48c7-85a3-404c44203646",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD cb0312051474648c5cca1794da85489bd5ae6dd0 d185c32fa99a7ba287ce0073e834ad8dab2318c6 c50b4754f0a2a4c40ae5484bef0cfa4da443a226 19fb28bd749723b4ba8744de423e893fca97239b dca59395decc4979919676c7cbf9f8c671bd4397 '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/head\n",
"$ git rev-list --header -1 origin/gh/amjames/1/base\n",
"$ git for-each-ref refs/remotes/origin/gh/amjames '--format=%(refname)'\n",
"$ git merge-base dca59395decc4979919676c7cbf9f8c671bd4397 origin/main\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p 8a97bdf82ac0ef4085b270a56598868e33d2ed0f 55bfa6f29723085067e771d7ab4aecf9273301a5\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/head\n",
"$ git rev-list --header -1 origin/gh/amjames/2/base\n",
"$ git merge-base 19fb28bd749723b4ba8744de423e893fca97239b origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e dc45b95074cd087004fa771debc0c8ec5153665b\n",
"$ git commit-tree -p dc45b95074cd087004fa771debc0c8ec5153665b 55bfa6f29723085067e771d7ab4aecf9273301a5\n",
"$ git commit-tree -p b5f842a546db541eaa64f203a72f7ae216b62d1c -p d80e3f6bb2853ac1d7ee474a93c13ebdc4419b5a 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/head\n",
"$ git rev-list --header -1 origin/gh/amjames/3/base\n",
"$ git for-each-ref refs/remotes/origin/gh/amjames '--format=%(refname)'\n",
"$ git merge-base d185c32fa99a7ba287ce0073e834ad8dab2318c6 origin/main\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git commit-tree -p b537ea4a7b12cc4eca3381f4b1e3b7143d6c26a0 6b52aa55f01b77ce582f9df4605da4241cebfe1d\n",
"$ git push origin --no-verify 8a97bdf82ac0ef4085b270a56598868e33d2ed0f:refs/heads/gh/amjames/4/base 52cb3f69c46928744fea31f1ca51f89b24962cac:refs/heads/gh/amjames/4/head b537ea4a7b12cc4eca3381f4b1e3b7143d6c26a0:refs/heads/gh/amjames/5/base fc2f7850aff54c0dbccea1dd65aa213b8d170e8b:refs/heads/gh/amjames/5/head\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" * [new branch] 8a97bdf82ac0ef4085b270a56598868e33d2ed0f -> gh/amjames/4/base\n",
" * [new branch] 52cb3f69c46928744fea31f1ca51f89b24962cac -> gh/amjames/4/head\n",
" * [new branch] b537ea4a7b12cc4eca3381f4b1e3b7143d6c26a0 -> gh/amjames/5/base\n",
" * [new branch] fc2f7850aff54c0dbccea1dd65aa213b8d170e8b -> gh/amjames/5/head\n",
"Opened PR #4\n",
"Opened PR #5\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p f77ead971ce193111945ceca55b6da47c5e3f18c 55bfa6f29723085067e771d7ab4aecf9273301a5\n",
"$ git commit-tree -p c41ba97bb0dc9f417a5152121f423afb983a949f 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git commit-tree -p 7c9c3042629e156ac17c49b5bdfe8b8875311b6e 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git commit-tree -p 6c06d038be2305fa27fc24b818212be9de0c2da6 6b52aa55f01b77ce582f9df4605da4241cebfe1d\n",
"$ git push origin --no-verify +f77ead971ce193111945ceca55b6da47c5e3f18c:refs/heads/gh/amjames/1/orig +c41ba97bb0dc9f417a5152121f423afb983a949f:refs/heads/gh/amjames/4/orig +7c9c3042629e156ac17c49b5bdfe8b8875311b6e:refs/heads/gh/amjames/2/orig d80e3f6bb2853ac1d7ee474a93c13ebdc4419b5a:refs/heads/gh/amjames/2/base b2b00e8c4f45c7a092c0ad08fa94d2ce312470b1:refs/heads/gh/amjames/2/head +6c06d038be2305fa27fc24b818212be9de0c2da6:refs/heads/gh/amjames/3/orig +8fa5a7873d90f218b08b02de88a2061447af9107:refs/heads/gh/amjames/5/orig\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" + dca5939...f77ead9 f77ead971ce193111945ceca55b6da47c5e3f18c -> gh/amjames/1/orig (forced update)\n",
" dc45b95..d80e3f6 d80e3f6bb2853ac1d7ee474a93c13ebdc4419b5a -> gh/amjames/2/base\n",
" b5f842a..b2b00e8 b2b00e8c4f45c7a092c0ad08fa94d2ce312470b1 -> gh/amjames/2/head\n",
" + 17da921...7c9c304 7c9c3042629e156ac17c49b5bdfe8b8875311b6e -> gh/amjames/2/orig (forced update)\n",
" + f4d6218...6c06d03 6c06d038be2305fa27fc24b818212be9de0c2da6 -> gh/amjames/3/orig (forced update)\n",
" * [new branch] c41ba97bb0dc9f417a5152121f423afb983a949f -> gh/amjames/4/orig\n",
" * [new branch] 8fa5a7873d90f218b08b02de88a2061447af9107 -> gh/amjames/5/orig\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/4\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/5\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Created https://github.com/amjames/ghstack-tutorial/pull/4\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/3\n",
" - Created https://github.com/amjames/ghstack-tutorial/pull/5\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/5\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/5\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft 8fa5a7873d90f218b08b02de88a2061447af9107\n",
"[ghstack timing] fetch: 704ms\n",
"[ghstack timing] parse_revs: 54ms\n",
"[ghstack timing] prepare_updates: 5770ms\n",
"[ghstack timing] push_updates: 2537ms\n",
"[ghstack timing] finalize: 80ms\n",
"[ghstack timing] total: 9144ms\n"
]
}
],
"source": [
"!ghstack"
]
},
{
"cell_type": "markdown",
"id": "c0a002dd-8b11-45f3-bf45-412594df4e90",
"metadata": {},
"source": [
"If we now have a look at stack, we can see our new commits. We can also see that the new PRs have been created in the logs above"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "e4ee6643-7904-436c-830a-a21069b0ad8f",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* #5\n",
"* __->__ #3\n",
"* #2\n",
"* #4\n",
"* #1"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(3)"
]
},
{
"cell_type": "markdown",
"id": "78a45280-9c54-4af2-b325-2b35e91cb75d",
"metadata": {},
"source": [
"## How do I remove a PR to a stack?"
]
},
{
"cell_type": "markdown",
"id": "3a8a59f0-7221-4b11-82ed-8ee5655dbc45",
"metadata": {},
"source": [
"Removing a PR from a stack is as easy as removing the commit from the branch. If we want to remove the most recent commit, we can use [`reset`]:\n",
"\n",
"[`reset`]: https://git-scm.com/docs/git-reset"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "10142a86-476b-4486-868e-a5fc89b35c30",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"HEAD is now at 6c06d03 3\n"
]
}
],
"source": [
"!git reset --hard HEAD~1"
]
},
{
"cell_type": "markdown",
"id": "6ae3285b-1729-49b6-bd17-eab363fba971",
"metadata": {},
"source": [
"At this point, you know what I'm going to say about PRs further down the stack: we need to interactively [`rebase`] again. This time we change the command `pick` to `drop`:\n",
"\n",
"[`rebase`]: https://git-scm.com/docs/git-rebase"
]
},
{
"cell_type": "markdown",
"id": "92110f5d-2919-4e9d-8448-9b8d5ea4cad7",
"metadata": {},
"source": [
" git rebase -i main"
]
},
{
"cell_type": "markdown",
"id": "11f420a7-1c9c-46f2-bcb3-773856b08edb",
"metadata": {},
"source": [
"```\n",
"pick fda2e0e 1\n",
"drop 82eac92 1.5\n",
"pick 4be099c 2\n",
"pick 4be099c 3\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "3ff87172-14e0-42d7-af07-616e6cd267f7",
"metadata": {},
"source": [
"We resolve the resulting merge conflict by again accepting our former commit."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "cd05aaed-6935-4383-9945-d16f6a833c00",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Updated 1 path from the index\n",
"[detached HEAD 8a2292a] 2\n",
" 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"\u001b[KSuccessfully rebased and updated refs/heads/my-feature-branch.\n"
]
}
],
"source": [
"!git checkout --theirs .\n",
"!git commit -a --no-edit\n",
"!git rebase --continue"
]
},
{
"cell_type": "markdown",
"id": "a73f34a5-f432-4c6a-b0cb-15892563811e",
"metadata": {},
"source": [
"As before, we run [`ghstack`] to publish our changes. \n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "7344295c-7ae1-4a4c-b576-072f47406c4b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD a49705913d7c7f0c5bc146e9776d2c771fd35be3 8a2292aea827a7b52865369a0cf7720552a87aa7 f77ead971ce193111945ceca55b6da47c5e3f18c '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/head\n",
"$ git rev-list --header -1 origin/gh/amjames/1/base\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/head\n",
"$ git rev-list --header -1 origin/gh/amjames/2/base\n",
"$ git merge-base f77ead971ce193111945ceca55b6da47c5e3f18c origin/main\n",
"$ git merge-base --is-ancestor 8b7d9691a30cbab719da353c5af6336deb86af0e d80e3f6bb2853ac1d7ee474a93c13ebdc4419b5a\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p d80e3f6bb2853ac1d7ee474a93c13ebdc4419b5a 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p b2b00e8c4f45c7a092c0ad08fa94d2ce312470b1 -p aecc3d39889aff75f8c7c3f859dcc0c738efadc3 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/head\n",
"$ git rev-list --header -1 origin/gh/amjames/3/base\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p c758860a9a87b2e866b999f4806b5f01df4f0883 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git commit-tree -p 4c0a845b83a6f73dd923988224ef5d6b4c614400 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git push origin --no-verify +c758860a9a87b2e866b999f4806b5f01df4f0883:refs/heads/gh/amjames/1/orig +4c0a845b83a6f73dd923988224ef5d6b4c614400:refs/heads/gh/amjames/2/orig aecc3d39889aff75f8c7c3f859dcc0c738efadc3:refs/heads/gh/amjames/2/base 3f1b93a6f982b356a4cc8238461513035c7730f5:refs/heads/gh/amjames/2/head +cf01fde7604a84cf39d09a23c96cdb0dd001f159:refs/heads/gh/amjames/3/orig\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" + f77ead9...c758860 c758860a9a87b2e866b999f4806b5f01df4f0883 -> gh/amjames/1/orig (forced update)\n",
" d80e3f6..aecc3d3 aecc3d39889aff75f8c7c3f859dcc0c738efadc3 -> gh/amjames/2/base\n",
" b2b00e8..3f1b93a 3f1b93a6f982b356a4cc8238461513035c7730f5 -> gh/amjames/2/head\n",
" + 7c9c304...4c0a845 4c0a845b83a6f73dd923988224ef5d6b4c614400 -> gh/amjames/2/orig (forced update)\n",
" + 6c06d03...cf01fde cf01fde7604a84cf39d09a23c96cdb0dd001f159 -> gh/amjames/3/orig (forced update)\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Updated https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft cf01fde7604a84cf39d09a23c96cdb0dd001f159\n",
"[ghstack timing] fetch: 725ms\n",
"[ghstack timing] parse_revs: 55ms\n",
"[ghstack timing] prepare_updates: 1223ms\n",
"[ghstack timing] push_updates: 3427ms\n",
"[ghstack timing] finalize: 94ms\n",
"[ghstack timing] total: 5524ms\n"
]
}
],
"source": [
"!ghstack "
]
},
{
"cell_type": "markdown",
"id": "ae588e34-d1e8-4367-bd15-c8560b25c2eb",
"metadata": {},
"source": [
"The log above shows nothing related to 4 and 5 which we dropped, so that makes sense but note how PR 3 and PR 1 have been skipped. \n",
"\n",
"If we look at PR 3 we can see that it has not been updated the stack there shows the dropped PRs still. "
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "d3a652ef-b728-4dc5-b28f-f1ff0a3dda25",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* #5\n",
"* __->__ #3\n",
"* #2\n",
"* #1\n",
"* #4"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(3)"
]
},
{
"cell_type": "markdown",
"id": "afc82cf8-b4a4-4344-94a2-f9182ee632a3",
"metadata": {},
"source": [
"There were no changes to be added to these commits so ghstack skipped them including the PR description. "
]
},
{
"cell_type": "markdown",
"id": "dcf7d56d-25ac-4482-9a0e-fa1dc6ae8ac0",
"metadata": {},
"source": [
"We need the `--no-skip` flag force [`ghstack`] to update the PRs although the underlying commits haven't changed, and the `--update-fields` flag will update the PR description. Generally the PR description will contain the commit message, followed by the ghstack info. The ghstack info is regenerated and updated when changes are detected. The part of the PR description that comes from the commit message will not be updated ever without thisflag. If we had carefully ammended the commit message (remember not to disturb the ghstack info in the footer) those edits would be made as well. We didn't have any so we are just looking for the ghstack info section to reflect that PR 4 and 5 have been dropped. \n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "dc3eac0e-1ab4-4fcf-bf6c-9a7652b80749",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using GitHub credentials from gh CLI for github.com\n",
"$ git remote get-url --push origin\n",
"$ git rev-parse --git-dir\n",
"$ git cat-file -e HEAD:.github/ghstack_direct\n",
"fatal: path '.github/ghstack_direct' does not exist in 'HEAD'\n",
"$ git fetch --prune origin '+refs/heads/gh/amjames/*:refs/remotes/origin/gh/amjames/*'\n",
"$ git rev-list --header --topo-order --boundary HEAD '^origin/main'\n",
"$ git rev-list --boundary --header --topo-order HEAD cf01fde7604a84cf39d09a23c96cdb0dd001f159 4c0a845b83a6f73dd923988224ef5d6b4c614400 c758860a9a87b2e866b999f4806b5f01df4f0883 '^origin/main'\n",
"$ git rev-parse --show-toplevel\n",
"$ git config --default /Users/ajay/playground/ghstack/ghstack-tutorial/.git/hooks --get core.hooksPath\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/1/head\n",
"$ git rev-list --header -1 origin/gh/amjames/1/base\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/2/head\n",
"$ git rev-list --header -1 origin/gh/amjames/2/base\n",
"$ git rev-list --max-count=1 --header origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/orig\n",
"$ git rev-list --header -1 origin/gh/amjames/3/head\n",
"$ git rev-list --header -1 origin/gh/amjames/3/base\n",
"$ git config --get commit.gpgsign\n",
"$ git commit-tree -p 8b7d9691a30cbab719da353c5af6336deb86af0e 7008c1a07c67b33398f75d5311d6315f65785434\n",
"$ git commit-tree -p 889a31ddf04ce871dcbfb2e2e93d4660c8ed657b 3680ad9fe70876977436e25e5ccf4682eb205d31\n",
"$ git commit-tree -p be4b9d51a58dd1ef472a06af47b32e30e4da7d79 51bb5de51b8be29eea5733d759c944a061000ee8\n",
"$ git push origin --no-verify +889a31ddf04ce871dcbfb2e2e93d4660c8ed657b:refs/heads/gh/amjames/1/orig +be4b9d51a58dd1ef472a06af47b32e30e4da7d79:refs/heads/gh/amjames/2/orig +cb4713e66a10cd65bdb1d5940311564f5852415d:refs/heads/gh/amjames/3/orig\n",
"To github.com:amjames/ghstack-tutorial.git\n",
" + c758860...889a31d 889a31ddf04ce871dcbfb2e2e93d4660c8ed657b -> gh/amjames/1/orig (forced update)\n",
" + 4c0a845...be4b9d5 be4b9d51a58dd1ef472a06af47b32e30e4da7d79 -> gh/amjames/2/orig (forced update)\n",
" + cf01fde...cb4713e cb4713e66a10cd65bdb1d5940311564f5852415d -> gh/amjames/3/orig (forced update)\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/1\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/2\n",
"# Updating https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"# Summary of changes (ghstack 0.17.0)\n",
"\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/1\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/2\n",
" - Skipped https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"Meta employees can import your changes by running \n",
"(on a Meta machine):\n",
"\n",
" ghimport -s https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"If you want to work on this diff stack on another machine:\n",
"\n",
" ghstack checkout https://github.com/amjames/ghstack-tutorial/pull/3\n",
"\n",
"$ git rev-parse HEAD\n",
"$ git reset --soft cb4713e66a10cd65bdb1d5940311564f5852415d\n",
"[ghstack timing] fetch: 668ms\n",
"[ghstack timing] parse_revs: 58ms\n",
"[ghstack timing] prepare_updates: 1040ms\n",
"[ghstack timing] push_updates: 2291ms\n",
"[ghstack timing] finalize: 89ms\n",
"[ghstack timing] total: 4146ms\n"
]
}
],
"source": [
"!ghstack --update-fields --no-skip"
]
},
{
"cell_type": "markdown",
"id": "433ff034-b4a2-40bf-b6df-be9c59c961ad",
"metadata": {},
"source": [
"If we now have a look at stack, we can see the previosuly added commits are removed again."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "68f9428a-478f-4e76-90e4-bca39802cfab",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* __->__ #3\n",
"* #2\n",
"* #1"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(3)"
]
},
{
"cell_type": "markdown",
"id": "d0e6e826-aafb-4af1-957a-01493f9ab67a",
"metadata": {},
"source": [
"Note that [`ghstack`] does **not** close dropped PRs automatically, since removing them from our local branch means that [`ghstack`] is not going to touch them at all when we run it over this branch.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "674713fd-49de-4e5f-93fc-a59bfcfcb013",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* #5\n",
"* #3\n",
"* #2\n",
"* __->__ #4\n",
"* #1"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(4)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "87f78d82-d814-4111-be83-4b2f91ab941b",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.17.0) (oldest at bottom):\n",
"* __->__ #5\n",
"* #3\n",
"* #2\n",
"* #4\n",
"* #1"
],
"text/plain": [
"<IPython.core.display.Markdown object>"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"view_pr(5)"
]
},
{
"cell_type": "markdown",
"id": "52f4947d-d932-425c-a53d-6ea8a4a7b403",
"metadata": {},
"source": [
"You can close them if you don't want them, if they are meant to be moved to a new stack you can ghstack checkout PR 5, and drop commits for PR 1, 2, 3, or cherry pick commits for 4, 5 only onto a new branch. You can try setting up a second stack with PRs 4 and 5 if you like. "
]
},
{
"cell_type": "markdown",
"id": "78590926-b41a-4f89-a6c5-4fa0e39eecef",
"metadata": {},
"source": [
"## Tips/Advice\n",
"* Remember to run lintrunner on each commit before updating, use the pre-commit hook \n",
"* Check the logs to see the created, updated, skipped summaries\n",
"* Remember rebasing your working branch will lead to updates on the next ghstack run for all PRs (unless rebase was a no op)\n",
"* Use `git commit --ammend` when you want to add changes to the last PR on the stack, `git rebase -i` for changes further down the stack.\n",
"* If you are unsure if you are breaking the world you can always abort a rebase\n",
"* When dropping PRs, remember to use no-skip to force update the PR map on any prs that done need code changes pushed.\n",
"* When dropping PRs, remember that ghstack abandons prs it does not clean them up, you should close them if you don't want to keep them around.\n",
"* If you make a mistake an open a PR you didn't mean to, close it promptly, it happens. "
]
},
{
"cell_type": "markdown",
"id": "185635f0-aafc-48a3-b03c-5723d5c95f01",
"metadata": {},
"source": [
"## Conclusion"
]
},
{
"cell_type": "markdown",
"id": "330814a4-646c-473b-b504-76976018a4d7",
"metadata": {},
"source": [
"If you embrace the paradigm that one commit is equivalent to one PR [`ghstack`] is a very simple but powerful tool for stacked PRs on [GitHub]. In the role of someone that is only submitting PRs and not landing them, we mostly ran [`ghstack`] without any additional parameters during this tutorial. There are parts of the CLI that don't make sense for you to use unles you are landing code, or work at meta, but there are other parts of the CLI which you might find helpful. You can sync local commit messages to match updated PR descriptions on GH or the reverse, you can open PRs as draft. [`ghstack`] `--help` to learn more!\n",
"\n",
"On the downside, one must be comfortable with rewriting `git` history. This can be quite a learning curve, but if one gets a handle on it, it is fairly straight forward.\n",
"\n",
"[`ghstack`]: https://github.com/ezyang/ghstack\n",
"[GitHub]: https://github.com"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
# gh repo delete needs manual input with your gh username so don't run this script but run the steps to "start over"
rm -rf ./ghstack-tutorial
gh repo delete ghstack-tutorial
#!/usr/bin/env bash
uv venv --clear
source .venv/bin/activate
uv pip install jupyter
# Your choice, install in the venv with the next command,
# the notebook assumes this has been done.
uv pip install ghstack
jupyter notebook ghstack-tutorial.ipynb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment