├── .eslintrc.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bin └── git-delete-squashed.js └── package.json /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - not-an-aardvark 3 | - plugin:node/recommended 4 | plugins: 5 | - node 6 | rules: 7 | no-console: off 8 | no-process-exit: off 9 | require-jsdoc: error 10 | valid-jsdoc: error 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.4 (2021-12-30) 4 | 5 | * Delete merged and squashed from other branches. ([#11](https://github.com/not-an-aardvark/git-delete-squashed/issues/11)) ([201ad37](https://github.com/not-an-aardvark/git-delete-squashed/commit/201ad377384848c208dceb42d152115bbe38061a)) 6 | * Fix for zsh ([#10](https://github.com/not-an-aardvark/git-delete-squashed/issues/10)) ([913c21b](https://github.com/not-an-aardvark/git-delete-squashed/commit/913c21b99f932eb6ab4757f2489691e9716db8a4)) 7 | 8 | ## v1.0.3 (2017-06-23) 9 | 10 | * Docs: add shell script alternative to readme ([59bb490](https://github.com/not-an-aardvark/git-delete-squashed/commit/59bb49097171f41d148be215eea25c94d2adec4e)) 11 | * Chore: refactor to use `git cherry` ([a1babc2](https://github.com/not-an-aardvark/git-delete-squashed/commit/a1babc21dc21b53fd472821eef0729af7f7d8cf2)) 12 | * Fix: use git diff-tree rather than git diff (fixes [#2](https://github.com/not-an-aardvark/git-delete-squashed/issues/2)) ([8d2c524](https://github.com/not-an-aardvark/git-delete-squashed/commit/8d2c524c94e050a2c72f5f6996a1bfce5dc4ecbb)) 13 | 14 | ## v1.0.2 (2017-06-21) 15 | 16 | * Fix: Cap the number of processes running at a time (fixes [#1](https://github.com/not-an-aardvark/git-delete-squashed/issues/1)) ([f4d4011](https://github.com/not-an-aardvark/git-delete-squashed/commit/f4d4011cf4c6a0416d45e10181dd74856c731438)) 17 | * Chore: Remove unnecessary --no-pager ([f472e2f](https://github.com/not-an-aardvark/git-delete-squashed/commit/f472e2f8014c360188ce2f929b76510b44854f89)) 18 | * Chore: fix misleading variable name ([b2aaa14](https://github.com/not-an-aardvark/git-delete-squashed/commit/b2aaa14b180bd2fa4f8c2f1d03913e0ee9048d46)) 19 | * Chore: simplify promise-chaining logic ([8dac43b](https://github.com/not-an-aardvark/git-delete-squashed/commit/8dac43bba4045b9a5f59880e4620487b26b31606)) 20 | * Chore: remove unnecessary abstractions ([9ece0d7](https://github.com/not-an-aardvark/git-delete-squashed/commit/9ece0d75055143e9b50c26bf05942a4ce247edc6)) 21 | 22 | ## v1.0.1 (2017-04-06) 23 | 24 | * Docs: create a changelog ([1aaf81c](https://github.com/not-an-aardvark/git-delete-squashed/commit/1aaf81c1c085eb49062de1313cbc9e1adcdc7c5d)) 25 | * Fix: use git patch-id rather than comparing raw diffs ([4effe5c](https://github.com/not-an-aardvark/git-delete-squashed/commit/4effe5c3735ad32f54270c859839502b739d9cee)) 26 | * Chore: use spawn() for git instead of exec() ([17aa59a](https://github.com/not-an-aardvark/git-delete-squashed/commit/17aa59ac8b7a1d8dbfa0aef8cfbcb706bd8b5a8f)) 27 | * Docs: fix issues link in package.json ([9a8600d](https://github.com/not-an-aardvark/git-delete-squashed/commit/9a8600d52e7656c81342a58929797a3fbf4217d6)) 28 | * Chore: remove unnecessary branch list formatting ([c82397f](https://github.com/not-an-aardvark/git-delete-squashed/commit/c82397ff0468e61154f386cbbe41a5755be4212e)) 29 | 30 | ## v1.0.0 (2017-03-30) 31 | 32 | * New: Initial commit ([e2c99b6](https://github.com/not-an-aardvark/git-delete-squashed/commit/e2c99b6a10343fb2a9310719824972f2f10cd7b7)) 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2017 Teddy Katz 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-delete-squashed 2 | 3 | This is a tool that deletes all of your git branches that have been "squash-merged" into master. 4 | 5 | This is useful if you work on a project that squashes branches into master. After your branch is squashed and merged, you can use this tool to clean up the local branch. 6 | 7 | ## Usage 8 | 9 | ### sh 10 | 11 | To run as a shellscript, simply copy the following command (setting up an alias is recommended). There's no need to clone the repo. 12 | 13 | ```bash 14 | # Change $TARGET_BRANCH to your targeted branch, e.g. change from `master` to `main` to delete branches squashed into `main`. 15 | TARGET_BRANCH=master && git checkout -q $TARGET_BRANCH && git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do mergeBase=$(git merge-base $TARGET_BRANCH $branch) && [[ $(git cherry $TARGET_BRANCH $(git commit-tree $(git rev-parse $branch\^{tree}) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done 16 | # OR you can put this function in a global git alias and call it like this 17 | # `git delete-squashed` OR `git delete-squashed main` 18 | git config --global alias.delete-squashed '!f() { local targetBranch=${1:-master} && git checkout -q $targetBranch && git branch --merged | grep -v "\*" | xargs -n 1 git branch -d && git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do mergeBase=$(git merge-base $targetBranch $branch) && [[ $(git cherry $targetBranch $(git commit-tree $(git rev-parse $branch^{tree}) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done; }; f' 19 | ``` 20 | 21 | ### Node.js 22 | 23 | You can also install the tool as a Node.js package from NPM. (The package code is in this repo.) 24 | 25 | Additionally, you can specify an alternate branch to check for squashed merges, as well. This is useful for different names of trunk branches like `main` or `develop`. 26 | 27 | ```bash 28 | $ npm install --global git-delete-squashed 29 | $ git-delete-squashed 30 | $ # Specify a different branch name like so 31 | $ git-delete-squashed main 32 | ``` 33 | 34 | ## Details 35 | 36 | To determine if a branch is squash-merged, git-delete-squashed creates a temporary dangling squashed commit with [`git commit-tree`](https://git-scm.com/docs/git-commit-tree). Then it uses [`git cherry`](https://git-scm.com/docs/git-cherry) to check if the squashed commit has already been applied to `master`. If so, it deletes the branch. 37 | -------------------------------------------------------------------------------- /bin/git-delete-squashed.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const childProcess = require('child_process'); 6 | const Promise = require('bluebird'); 7 | const DEFAULT_BRANCH_NAME = 'master'; 8 | const RUN_WITH_NODE = process.argv[0].includes('node'); 9 | const selectedBranchName = process.argv[RUN_WITH_NODE ? 2 : 1] || DEFAULT_BRANCH_NAME; 10 | 11 | /** 12 | * Calls `git` with the given arguments from the CWD 13 | * @param {string[]} args A list of arguments 14 | * @returns {Promise} The output from `git` 15 | */ 16 | function git (args) { 17 | return new Promise((resolve, reject) => { 18 | const child = childProcess.spawn('git', args); 19 | 20 | let stdout = ''; 21 | let stderr = ''; 22 | 23 | child.stdout.on('data', data => stdout += data); 24 | child.stderr.on('data', data => stderr += data); 25 | 26 | child.on('close', exitCode => exitCode ? reject(stderr) : resolve(stdout)); 27 | }).then(stdout => stdout.replace(/\n$/, '')); 28 | } 29 | 30 | git(['for-each-ref', 'refs/heads/', '--format=%(refname:short)']) 31 | .then(branchListOutput => branchListOutput.split('\n')) 32 | .tap(branchNames => { 33 | if (branchNames.indexOf(selectedBranchName) === -1) { 34 | throw `fatal: no branch named '${selectedBranchName}' found in this repo`; 35 | } 36 | }).filter(branchName => 37 | // Get the common ancestor with the branch and master 38 | Promise.join( 39 | git(['merge-base', selectedBranchName, branchName]), 40 | git(['rev-parse', `${branchName}^{tree}`]), 41 | (ancestorHash, treeId) => git(['commit-tree', treeId, '-p', ancestorHash, '-m', `Temp commit for ${branchName}`]) 42 | ) 43 | .then(danglingCommitId => git(['cherry', selectedBranchName, danglingCommitId])) 44 | .then(output => output.startsWith('-')) 45 | ) 46 | .tap(branchNamesToDelete => branchNamesToDelete.length && git(['checkout', selectedBranchName])) 47 | .mapSeries(branchName => git(['branch', '-D', branchName])) 48 | .mapSeries(stdout => console.log(stdout)) 49 | .catch(err => console.error(err.cause || err)); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-delete-squashed", 3 | "version": "1.0.4", 4 | "description": "Delete branches that have been squashed and merged into master", 5 | "scripts": { 6 | "lint": "eslint bin/", 7 | "test": "npm run lint" 8 | }, 9 | "bin": { 10 | "git-delete-squashed": "bin/git-delete-squashed.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/not-an-aardvark/git-delete-squashed.git" 15 | }, 16 | "keywords": [ 17 | "git" 18 | ], 19 | "author": "Teddy Katz", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/not-an-aardvark/git-delete-squashed/issues" 23 | }, 24 | "engines": { 25 | "node": ">=4.0.0" 26 | }, 27 | "homepage": "https://github.com/not-an-aardvark/git-delete-squashed#readme", 28 | "dependencies": { 29 | "bluebird": "^3.5.0" 30 | }, 31 | "devDependencies": { 32 | "eslint": "^3.18.0", 33 | "eslint-config-not-an-aardvark": "^2.0.0", 34 | "eslint-plugin-node": "^4.2.2" 35 | } 36 | } 37 | --------------------------------------------------------------------------------