Skip to content

Commit

Permalink
Merge pull request #4 from Vpet95/feature/new-delete-option-to-deal-w…
Browse files Browse the repository at this point in the history
…ith-not-fully-merged

Added additional error handling options for delete and clean + some fixes
  • Loading branch information
Vpet95 committed Jan 17, 2023
2 parents 79695ea + 6e5f2cf commit 29c00ac
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 18 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,10 @@ The `gitscconf.json` file contains a section for the delete command, allowing yo
- `onTicketNotFound` - determines what action to take in the event that the Shortcut ticket corresponding to the branch cannot be found, possible values include:
- `abort` - cancel deletion. For the `clean` command, this means stopping on the first branch with a missing Shortcut ticket. This is the default for `delete`.
- `delete` - proceed with the delete anyway, the user will have one last chance to change their mind at the y/n prompt, unless `prompt` is turned off
- `skip` - skip the current branch; for `delete` this means ending execution (NOOP); the `clean` command will move on to attempt the delete the next branch, if there is another. This is the default for `clean`.
- `skip` - skip the current branch; for `delete` this means ending execution (NOOP); the `clean` command will move on to attempt the delete the next
branch, if there is another. This is the default for `clean`.
- `onNotFullyMerged` - determines what action to take in the event that a delete receives a "error: The branch '[branch name]' is not fully merged." response. Possible values are `abort`, `delete`, or `skip`. In this case, the `delete` value will re-attempt the delete with force.
- `onError` - a catch-all for errors other than the mentioned 'not fully merged' error. Possible values are: `abort`, `delete`, or `skip`, with identical behavior to `onNotFullyMerged`. Open an issue if more specific/tailored behavior is desired for a certain kind of error.
- `prompt` - boolean, whether to prompt the user before deleting the branch (default: `true`); note: validation will still occur if `prompt` is `false`.
- `remote` - whether to delete remote branches in addition to local (default: `false`)
- `filters`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-sc-cli",
"version": "2.0.1",
"version": "2.1.1",
"description": "A CLI tool that combines your git and Shortcut workflows. Search for Shortcut tickets, make branches, clean up your local branch list, etc.",
"main": "index.js",
"scripts": {},
Expand Down
68 changes: 59 additions & 9 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ function handleTicketNotFound(onTicketNotFound, storyId) {
return NOTFOUND_ABORT;
case "delete":
wrapLog(
`${commonCopy} proceeding with branch deletion due to configured 'onStoryNotFound' value`,
`${commonCopy} proceeding with branch deletion due to configured 'onTicketNotFound' value`,
"warn"
);
return NOTFOUND_DELETE;
Expand Down Expand Up @@ -298,6 +298,14 @@ async function validateDeleteConditionsAndPrompt(
)
: "y";

// really useful in cases where the user is doing a clean command, with hundreds of local branches
// and finds out last minute that they had prompt: true, and want an easy way to cancel the entire
// command, not just the current delete iteration
if (resp === null) {
console.log("Operation fully canceled");
process.exit();
}

if (resp.length === 0 || resp.toLowerCase() === "n") {
console.log("Ok, canceled");
return false;
Expand Down Expand Up @@ -347,6 +355,24 @@ export const storyIdToBranchNames = (storyId) => {
return branchName;
};

const handleDeleteError = (status, onError, forceDeleteFn, errorMessages) => {
console.error(status.output);

switch (onError) {
case "abort":
if (errorMessages.abort) console.error(errorMessages.abort);
process.exit();
case "skip":
if (errorMessages.skip) console.error(errorMessages.skip);
return false;
case "delete":
if (errorMessages.delete) console.error(errorMessages.delete);
forceDeleteFn();
}

return true;
};

export const deleteBranch = async (
branchName,
storyId,
Expand Down Expand Up @@ -389,14 +415,38 @@ export const deleteBranch = async (
}`
);

git.delete(
{
branchName: branchName,
remoteName,
force: shouldForce,
},
assertSuccess
);
const result = git.delete({
branchName: branchName,
remoteName,
force: shouldForce,
});

if (!result.success) {
const isNotFullyMerged = result.output.includes("is not fully merged");

return handleDeleteError(
result,
isNotFullyMerged ? options.onNotFullyMerged : options.onError,
() => {
git.delete({
branchName: branchName,
remoteName,
force: true,
});
},
{
abort: `Aborting due to ${
isNotFullyMerged ? "onNotFullyMerged" : "onError"
} setting`,
skip: isNotFullyMerged
? `Branch '${branchName}' is not fully merged. Skipping due to onNotFullyMerged setting.`
: `Skipping due to onError setting.`,
delete: `Force deleting due to ${
isNotFullyMerged ? "onNotFullyMerged" : "onError"
} setting`,
}
);
}

return true;
};
Expand Down
12 changes: 8 additions & 4 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,18 @@ const deleteBranchFilterSchema = Joi.object({
.min(1),
});

const badResultHandlerSchema = Joi.string()
.valid("abort", "delete", "skip")
.insensitive()
.required();

const purgeSchema = Joi.object({
force: Joi.boolean(),
remote: Joi.boolean(),
filters: deleteBranchFilterSchema,
onTicketNotFound: Joi.string()
.valid("abort", "delete", "skip")
.insensitive()
.required(),
onTicketNotFound: badResultHandlerSchema,
onNotFullyMerged: badResultHandlerSchema,
onError: badResultHandlerSchema,
prompt: Joi.boolean(),
});

Expand Down
6 changes: 5 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const VERSION_MAJOR = 2;
export const VERSION_MINOR = 0;
export const VERSION_MINOR = 1;
export const VERSION_PATCH = 1;
export const PROGRAM_VERSION = `${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}`;
export const SEMVER_REGEX = /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/;
Expand Down Expand Up @@ -59,13 +59,17 @@ export const DEFAULT_OPTIONS = {
force: false,
remote: false,
onTicketNotFound: "abort",
onNotFullyMerged: "abort",
onError: "abort",
filters: structuredClone(branchDeletionFilters),
prompt: true,
},
clean: {
force: false,
remote: false,
onTicketNotFound: "skip",
onNotFullyMerged: "skip",
onError: "skip",
filters: structuredClone(branchDeletionFilters),
prompt: true,
},
Expand Down
8 changes: 7 additions & 1 deletion src/git-lib/git-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@ export default class GitClient {
);

// bail out early - there was an error and no handler was provided, or handler didn't exit
if (!result.success || remoteOnly) return result;
// if the remote doesn't exist, it's not much of a problem
if (
remoteOnly ||
(!result.success &&
!result.output.includes("remote ref does not exist"))
)
return result;
}

// it's safe to overwrite result here - the output will contain the necessary context to debug
Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const multiSelectionPrompt = (
export const extractStoryIdFromBranchName = (branchName, branchNameRegex) => {
const result = branchName.match(branchNameRegex);

return result?.groups.ticketId;
return result?.groups?.ticketId;
};

export const underline = (str, customLength = null) =>
Expand Down

0 comments on commit 29c00ac

Please sign in to comment.