Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 2b8d9f2

Browse files
author
David Bushong
committed
feat: support "main" as default branch
non-breaking as it still falls back on "master"
1 parent c7c5ca6 commit 2b8d9f2

15 files changed

+332
-245
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
node_modules/
44
npm-debug.log
55
/tmp
6+
/.vscode

README.md

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
# Branch Workflow CLI
22

33
A cli that provides a set of `git wf` subcommands which simplify dealing with
4-
feature branches & GitHub pull requests. Does not require a GH API token, as
4+
feature branches & GitHub pull requests. Does not require a GH API token, as
55
it just opens your browser to complete Pull Request operations.
66

7-
* creates named feature branches which track their intended "parent" (`start`)
8-
* opens pull requests against the intended parent branch (`pr`)
9-
* cleans up when done (`done`)
10-
* aborts abandoned branches cleanly (`abort`)
11-
* renames branches locally & on server (`rename`)
12-
* additional optional release management commands (`cut-release`, `qa`,
13-
`hotfix`, `merge-back`)
7+
- creates named feature branches which track their intended "parent" (`start`)
8+
- opens pull requests against the intended parent branch (`pr`)
9+
- cleans up when done (`done`)
10+
- aborts abandoned branches cleanly (`abort`)
11+
- renames branches locally & on server (`rename`)
12+
- additional optional release management commands (`cut-release`, `qa`,
13+
`hotfix`, `merge-back`)
14+
15+
## "master" vs "main"
16+
17+
Below we use the term `main` to refer to your mainline branch; if you have a
18+
`main` branch in your local checkout, we'll assume that's the one you're using.
19+
If not, we'll assume you're using `master`.
1420

1521
## Installation
1622

@@ -31,9 +37,9 @@ $ git wf --help
3137
## Commands
3238

3339
The `start`, `pr`, `abort`, `rename`, and `done` commands can be used on **any**
34-
project that has a master branch.
40+
project that has a master or main branch.
3541

36-
All of the other commands will enforce the existence and use of the `master`,
42+
All of the other commands will enforce the existence and use of the `main`,
3743
`release`, and `hotfix` branch naming scheme.
3844

3945
### `git wf start [--fork] <name>` - starts a new feature branch
@@ -42,18 +48,18 @@ Given you are currently on branch `<parent>`
4248

4349
1. Updates the branch you currently have checked out with `git pull`
4450
1. Creates a new feature branch named `<name>` locally with
45-
`git checkout -b <name>`
51+
`git checkout -b <name>`
4652
1. If you specified `--fork` or already have a remote named `fork`:
47-
1. verifies you have a remote named `fork`
48-
1. if you don't, verifies that `<yourusername>/<reponame>` exists on github,
49-
and if not prompts you to create it
50-
1. if you do have a github fork, creates the `fork` remote for you
51-
1. Pushes your feature branch to `fork` as a branch named
52-
`feature/<parent>/<name>` with
53-
`git push -u fork <name>:feature/<parent><name>`
53+
1. verifies you have a remote named `fork`
54+
1. if you don't, verifies that `<yourusername>/<reponame>` exists on github,
55+
and if not prompts you to create it
56+
1. if you do have a github fork, creates the `fork` remote for you
57+
1. Pushes your feature branch to `fork` as a branch named
58+
`feature/<parent>/<name>` with
59+
`git push -u fork <name>:feature/<parent><name>`
5460
1. If you didn't, pushes your feature branch to `origin` as a branch named
55-
`<yourusername>/feature/<parent>/<name>` with
56-
`git push -u origin <name>:<yourusername>/feature/<parent>/<name>`
61+
`<yourusername>/feature/<parent>/<name>` with
62+
`git push -u origin <name>:<yourusername>/feature/<parent>/<name>`
5763

5864
### `git wf rename <newname>` - renames a feature branch
5965

@@ -62,17 +68,17 @@ branch run this command, passing a new name, it will:
6268

6369
1. Fetch the latest commits from the remote
6470
1. Create a new remote branch named correctly, based on the fetched
65-
version of the old remote branch (no new commits from local)
71+
version of the old remote branch (no new commits from local)
6672
1. Create a new local branch with the new name, based on the current
67-
local branch
73+
local branch
6874
1. Make the former the upstream of the latter
6975
1. Delete the old local branch
7076
1. Delete the old remote branch
7177

7278
### `git wf abort` - aborts a feature
7379

7480
If you decide you don't like your new feature, you may PERMANENTLY delete it,
75-
locally and remotely, using `git wf abort`. This will:
81+
locally and remotely, using `git wf abort`. This will:
7682

7783
1. Commit any working tree changes as a commit with message "WIP"
7884
1. Save the SHA of whatever the final commit was
@@ -95,11 +101,11 @@ Given you are currently on a feature branch named `<name>`
95101
1. Deletes the feature branch with `git branch -d <name>`
96102
1. Cleans up the corresponding remote branch with `git remote prune origin`
97103

98-
### `git wf cut-release [branch]` - PRs starting a fresh release from master
104+
### `git wf cut-release [branch]` - PRs starting a fresh release from main
99105

100106
1. Runs `git wf merge-back` (see below)
101-
1. Opens a PR, as per `git wf pr` to merge `branch` (default: `master`) to
102-
`release`
107+
1. Opens a PR, as per `git wf pr` to merge `branch` (default: `main`) to
108+
`release`
103109

104110
### `git wf qa [branch]` - Tags build of _branch_
105111

@@ -108,7 +114,7 @@ Given you are currently on a feature branch named `<name>`
108114
1. Switches to `[branch]` with `git checkout [branch]`
109115
1. Updates with `git pull --no-rebase`
110116
1. Tags `HEAD` of `[branch]` as `build-YYYY.mm.dd_HH.MM.SS` with
111-
`git tag build-...`
117+
`git tag build-...`
112118
1. Pushes tag with `git push origin tag build-...`
113119

114120
### `git wf hotfix <build-tag>` - Moves the hotfix branch to given tag
@@ -118,58 +124,58 @@ Given you are currently on a feature branch named `<name>`
118124
1. Fast-forward merges `hotfix` to given build tag
119125
1. Pushes `hotfix` branch
120126

121-
### `git wf merge-back` - Merges all changes back from master ← release ← hotfix
127+
### `git wf merge-back` - Merges all changes back from main ← release ← hotfix
122128

123129
1. Switches to `hotfix` branch
124130
1. Pulls latest updates
125131
1. Merges `hotfix` branch to `release` branch - if there are conflicts, it
126-
creates a feature branch for you to clean up the results, and submit a PR.
127-
If not, pushes the merged branch.
128-
1. As before, but this time merging `release` onto `master`
132+
creates a feature branch for you to clean up the results, and submit a PR.
133+
If not, pushes the merged branch.
134+
1. As before, but this time merging `release` onto `main`
129135

130136
## Example Flow
131137

132138
Here's a narrative sequence of events in the life of a project:
133139

134-
* The project starts with branches `master`, `release`, and `hotfix` all
135-
pointing at the same place
136-
* On branch master, you `git wf start widget-fix`
137-
* Now on branch `widget-fix`, you make some commits, decide it's ready to PR,
138-
and run `git wf pr`
139-
* The PR is tested, accepted, and merged, and at some point, while on branch
140-
`widget-fix`, you run `git wf done`, which cleans it up
141-
* You start a new features, `git wf start bad-ideea`, make a few commits, then
142-
realize you named it wrong, so you `git wf rename bad-idea` - which is fine
143-
until you realize you don't want it at all, so you `git wf abort` and it's
144-
all gone.
145-
* A few more good features go in, and it's time to `git wf cut-release` -
146-
now your `release` branch is pointing up-to-date with `master`, and people
147-
can resume adding features to `master`
148-
* It's time to QA your upcoming release, so you `git wf qa release` which
149-
creates a `build-...` tag
150-
* Your shiny new `build-...` tag is available for deploying
151-
however you do that, so you deploy it, QA it, and eventually release it
152-
to production.
153-
* Everything's progressing along, there's new stuff on `master`, maybe a
154-
new release has even been cut to `release`, when you realize there's
155-
a problem on production, so you run `git wf hotfix build-...` with the
156-
build tag that's currently on production. Your `hotfix` branch is now
157-
ready for fixes.
158-
* From the `hotfix` branch, you `git wf start urgent-thingy` and now you're
159-
on a feature branch off of `hotfix` - you make your commits to fix the
160-
bug and `git wf pr`
161-
* People review and approve your PR, it's merged to the `hotfix` branch, you
162-
`git wf done` to cleanup
163-
* `git wf qa hotfix` creates a new `build-...` tag off of the `hotfix` branch,
164-
which can be QAed, then (quickly!) deployed to production
165-
* Now's a good time to run `git wf merge-back`, which will take those commits
166-
sitting on `hotfix` and merge them back onto the `release` branch you had
167-
in progress. This goes cleanly, so it just does it for you.
168-
* Then it goes to merge `release` back onto `master`, but uh-oh there are some
169-
conflicts by now, because someone fixed the problem a different way on
170-
`master`. No worries, `git wf` will detect that, create a feature branch
171-
to resolve the conflicts, let you clean up the merge on that branch, and
172-
then you `git wf pr` and it will open a PR to review the resolution.
140+
- The project starts with branches `main`, `release`, and `hotfix` all
141+
pointing at the same place
142+
- On branch main, you `git wf start widget-fix`
143+
- Now on branch `widget-fix`, you make some commits, decide it's ready to PR,
144+
and run `git wf pr`
145+
- The PR is tested, accepted, and merged, and at some point, while on branch
146+
`widget-fix`, you run `git wf done`, which cleans it up
147+
- You start a new features, `git wf start bad-ideea`, make a few commits, then
148+
realize you named it wrong, so you `git wf rename bad-idea` - which is fine
149+
until you realize you don't want it at all, so you `git wf abort` and it's
150+
all gone.
151+
- A few more good features go in, and it's time to `git wf cut-release` -
152+
now your `release` branch is pointing up-to-date with `main`, and people
153+
can resume adding features to `main`
154+
- It's time to QA your upcoming release, so you `git wf qa release` which
155+
creates a `build-...` tag
156+
- Your shiny new `build-...` tag is available for deploying
157+
however you do that, so you deploy it, QA it, and eventually release it
158+
to production.
159+
- Everything's progressing along, there's new stuff on `main`, maybe a
160+
new release has even been cut to `release`, when you realize there's
161+
a problem on production, so you run `git wf hotfix build-...` with the
162+
build tag that's currently on production. Your `hotfix` branch is now
163+
ready for fixes.
164+
- From the `hotfix` branch, you `git wf start urgent-thingy` and now you're
165+
on a feature branch off of `hotfix` - you make your commits to fix the
166+
bug and `git wf pr`
167+
- People review and approve your PR, it's merged to the `hotfix` branch, you
168+
`git wf done` to cleanup
169+
- `git wf qa hotfix` creates a new `build-...` tag off of the `hotfix` branch,
170+
which can be QAed, then (quickly!) deployed to production
171+
- Now's a good time to run `git wf merge-back`, which will take those commits
172+
sitting on `hotfix` and merge them back onto the `release` branch you had
173+
in progress. This goes cleanly, so it just does it for you.
174+
- Then it goes to merge `release` back onto `main`, but uh-oh there are some
175+
conflicts by now, because someone fixed the problem a different way on
176+
`main`. No worries, `git wf` will detect that, create a feature branch
177+
to resolve the conflicts, let you clean up the merge on that branch, and
178+
then you `git wf pr` and it will open a PR to review the resolution.
173179

174180
At every stage, you don't need to stop your forward progress, forget which your
175181
next planned release was, or anything else as you add new features and hotfix

git-wf.1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ Options:
99

1010
Commands:
1111
abort Close a feature branch without it being merged [CAREFUL]
12-
cut-release [branch] Move branch (default: master) commits onto release branch
12+
cut-release [branch] Move branch (default: main|master) commits onto release branch
1313
done Cleanup current merged, PRed feature branch
1414
hotfix <buildTag> Move branch hotfix to given build tag
15-
merge-back Merges all changes back from master ← release ← hotfix
15+
merge-back Merges all changes back from main|master ← release ← hotfix
1616
pr [options] Open a PR to merge current feature branch
1717
qa [options] [branch] Tag given (or current) branch as a build
1818
rename Rename local and remote current feature branch

lib/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function wrapAction(cmd, fn) {
6161
}
6262
if (opts.parent.no) deps.forceBool = false;
6363
verifySetup(cmd, deps)
64-
.then(() => fn({ deps, opts, args }))
64+
.then(main => fn({ deps, opts, args, main }))
6565
.catch(
6666
/** @param {Error} err */ err => {
6767
const justMessage = !err.stack || err instanceof UIError;

lib/commands/cut-release.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ const { action: mergeBackAction } = require('./merge-back');
3939
const { ghURL, assertNoFork } = require('../common');
4040

4141
/** @type {import('../typedefs').ActionFn} */
42-
async function cutReleaseAction({ deps: { git, log }, args: [branch], opts }) {
42+
async function cutReleaseAction({
43+
deps: { git, log },
44+
args: [branch],
45+
opts,
46+
main,
47+
}) {
4348
await assertNoFork(git, 'cut-release');
4449

45-
if (!branch) branch = 'master';
50+
if (!branch) branch = main;
4651

4752
log('Ensuring all changes are merged back');
48-
await mergeBackAction({ deps: { git, log }, opts, args: [] });
53+
await mergeBackAction({ deps: { git, log }, opts, args: [], main });
4954

5055
log(`Creating PR to fast-forward merge ${branch} onto release`);
5156
const prURL = await ghURL(git, `/compare/release...${branch}`, {
@@ -62,7 +67,9 @@ module.exports = {
6267
command(prog, wrapAction) {
6368
prog
6469
.command('cut-release [branch]')
65-
.description('Move branch (default: master) commits onto release branch')
70+
.description(
71+
'Move branch (default: main|master) commits onto release branch'
72+
)
6673
.action(wrapAction(cutReleaseAction));
6774
},
6875
};

lib/commands/merge-back.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,22 @@
3535
const { action: startAction } = require('./start');
3636
const { UIError, cmdLine, assertNoFork } = require('../common');
3737

38+
/**
39+
* @typedef {import('../typedefs').MainBranch} MainBranch
40+
*/
41+
3842
/**
3943
* @param {import('../typedefs').CmdDeps} deps
4044
* @param {string} from
45+
* @param {MainBranch} main
4146
*/
42-
async function createFeatureMerge({ git, log }, from) {
47+
async function createFeatureMerge({ git, log }, from, main) {
4348
await git.reset('hard');
4449
await startAction({
4550
deps: { git, log },
4651
args: [`merge-${from}`],
4752
opts: { parent: {} },
53+
main,
4854
});
4955
await git.merge([from]).catch(() => {});
5056
throw new UIError('When conflicts are resolved, commit and `wf pr`');
@@ -64,8 +70,9 @@ async function switchAndPull(git, branch) {
6470
* @param {import('../typedefs').CmdDeps} deps
6571
* @param {string} from
6672
* @param {string} to
73+
* @param {MainBranch} main
6774
*/
68-
async function tryMerge(deps, from, to) {
75+
async function tryMerge(deps, from, to, main) {
6976
const { git, log } = deps;
7077
log(`${to}${from}`);
7178
await switchAndPull(git, to);
@@ -80,7 +87,7 @@ async function tryMerge(deps, from, to) {
8087
if (/\nCONFLICT /.test(output)) throw new Error(output);
8188
} catch (err) {
8289
log('Automated merge failed; creating feature branch for resolution');
83-
await createFeatureMerge(deps, from); // will throw
90+
await createFeatureMerge(deps, from, main); // will throw
8491
}
8592

8693
log(`Merged cleanly; committing & pushing results to ${to} branch`);
@@ -92,22 +99,20 @@ async function tryMerge(deps, from, to) {
9299
}
93100

94101
/** @type {import('../typedefs').ActionFn} */
95-
async function mergeBackAction({ deps }) {
102+
async function mergeBackAction({ deps, main }) {
96103
const { git, log } = deps;
97104

98105
await assertNoFork(git, 'merge-back');
99106

100107
const origBranch = (await git.branchLocal()).current;
101108

102109
await switchAndPull(git, 'hotfix');
103-
await tryMerge(deps, 'hotfix', 'release');
104-
await tryMerge(deps, 'release', 'master');
110+
await tryMerge(deps, 'hotfix', 'release', main);
111+
await tryMerge(deps, 'release', main, main);
105112

106113
log('merge-back is clean');
107114

108-
if (origBranch !== 'master') await git.checkout(origBranch);
109-
110-
return true;
115+
if (origBranch !== main) await git.checkout(origBranch);
111116
}
112117

113118
/** @type {import('../typedefs').Action} */
@@ -116,7 +121,9 @@ module.exports = {
116121
command(prog, wrapAction) {
117122
prog
118123
.command('merge-back')
119-
.description('Merges all changes back from master ← release ← hotfix')
124+
.description(
125+
'Merges all changes back from main|master ← release ← hotfix'
126+
)
120127
.action(wrapAction(mergeBackAction));
121128
},
122129
};

lib/commands/qa.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ function genBuildTag() {
4646
}
4747

4848
/** @type {import('../typedefs').ActionFn} */
49-
async function qaAction({ deps: { git, log }, args: [branch], opts }) {
49+
async function qaAction({ deps: { git, log }, args: [branch], opts, main }) {
5050
await assertNoFork(git, 'qa');
5151

5252
if (branch) await git.checkout(branch);
5353
else branch = (await git.branchLocal()).current;
5454

5555
if (branch === 'release' && opts.mergeBack) {
5656
log('Requiring clean merge-back for release qa');
57-
await mergeBackAction({ deps: { git, log }, opts, args: [] });
57+
await mergeBackAction({ deps: { git, log }, opts, args: [], main });
5858
} else {
5959
log(`Pulling latest commits for '${branch}'`);
6060
// @ts-ignore

0 commit comments

Comments
 (0)