├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── README.md ├── docs ├── Base.md ├── Config.md ├── Feature.md ├── Hotfix.md └── Release.md ├── jasmine.json ├── package-lock.json ├── package.json ├── spec ├── tests │ ├── Base-spec.js │ ├── Feature-spec.js │ ├── Hotfix-spec.js │ ├── Release-spec.js │ ├── index-spec.js │ └── utils │ │ └── Promisify-spec.js └── utils │ ├── NodeGit.js │ └── RepoUtils.js └── src ├── Base.js ├── Config.js ├── Feature.js ├── Hotfix.js ├── Release.js ├── constants ├── BranchConstants.js ├── ErrorMessageConstants.js └── index.js ├── index.js └── utils ├── AssignUtils.js ├── InjectIntermediateCallbackUtils.js ├── MergeUtils.js ├── Promisify.js ├── RepoUtils.js ├── TagUtils.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "8.16.0" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "jasmine": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "pit": true 10 | }, 11 | "rules": { 12 | "indent": [2, 2], 13 | "no-console": 1, 14 | "no-const-assign": 2, 15 | "no-trailing-spaces": 2, 16 | "no-var": 2, 17 | "prefer-arrow-callback": 2, 18 | "prefer-const": 2, 19 | "quotes": [2, "single"], 20 | "semi": [2, "always"], 21 | "valid-jsdoc": 2 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Build directory 24 | build 25 | 26 | # Generated Test Repos 27 | spec/repos 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Test directories 2 | spec 3 | build/spec 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodegit-flow 2 | 3 | Adds gitflow methods to the [nodegit](https://github.com/nodegit/nodegit) module. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install --save nodegit-flow 9 | ``` 10 | 11 | ## Usage 12 | 13 | `nodegit-flow` is a drop in replacement for `nodegit`. All methods in `nodegit` are included in `nodegit-flow`. 14 | You must provide the `nodegit` module to `nodegit-flow`. 15 | 16 | ```javascript 17 | var nodegit = require('nodegit-flow')(require('nodegit')); // wrap nodegit in git flow methods 18 | ``` 19 | 20 | You can initialize an instance of `nodegit-flow` or use its static methods 21 | ```javascript 22 | var flow = nodegit.Flow.init(repo, config); 23 | 24 | flow.startRelease('1.0.0'); // Use a flow instance to start a release 25 | nodegit.Flow.startRelease(repo, '1.0.0'); // or the static equivalent 26 | ``` 27 | 28 | ## Methods 29 | 30 | * [Flow.finishFeature](#finishfeaturerepository-name-options) 31 | * [Flow.finishHotfix](#finishhotfixrepository-name-options) 32 | * [Flow.finishRelease](#finishreleaserepository-name-options) 33 | * [Flow.getConfig](#getconfigrepository) 34 | * [Flow.getConfigDefault](#getconfigdefault) 35 | * [Flow.getConfigRequiredKeys](#getconfigrequiredkeysrepository) 36 | * [Flow.getDevelopBranch](#getdevelopbranchrepository) 37 | * [Flow.getFeaturePrefix](#getfeatureprefixrepository) 38 | * [Flow.getHotfixPrefix](#gethotfixprefixrepository) 39 | * [Flow.getMasterBranch](#getmasterbranchrepository) 40 | * [Flow.getReleasePrefix](#getreleaseprefixrepository) 41 | * [Flow.getSupportPrefix](#getsupportprefixrepository) 42 | * [Flow.getVersionTagPrefix](#getversiontagprefixrepository) 43 | * [Flow.init](#initrepository-config) 44 | * [Flow.isInitialized](#isinitializedrepository) 45 | * [Flow.open](#openrepository) 46 | * [Flow.startFeature](#startfeaturerepository-name-options) 47 | * [Flow.startHotfix](#starthotfixrepository-name) 48 | * [Flow.startRelease](#startreleaserepository-name) 49 | * [Flow.validateConfig](#validateconfigconfig) 50 | 51 | ### finishFeature(repository, name, [options]) 52 | By default `finishFeature` will merge the feature branch into the develop branch and delete the feature branch. If successful, finishFeature will resolve with the merge commit. If a merge conflict occurs `finishFeature` will reject with the index of the conflict. 53 | 54 | `options` Object 55 | * `isRebase` Boolean default=`false` 56 | * `keepBranch` Boolean default=`false` 57 | 58 | Example: 59 | ```javascript 60 | NodeGit.Flow.finishFeature( 61 | repository, 62 | 'my-feature' 63 | ) 64 | .then((mergeCommit) => { 65 | console.log(mergeCommit.id()); // => the sha of the newly created commit 66 | }); 67 | ``` 68 | 69 | ### finishHotfix(repository, name, [options]) 70 | By default `finishHotfix` will merge the hotfix branch into the develop branch and the master branch, create a tag at the merge commit on master, and delete the hotfix branch. If successful, finishHotfix will resolve with the merge commit on develop. If a merge conflict occurs `finishHotfix` will reject with the index of the conflict. 71 | 72 | `options` Object 73 | * `keepBranch` Boolean default=`false` 74 | * `message` String tag annotation default=`''` 75 | 76 | Example: 77 | ```javascript 78 | NodeGit.Flow.finishHotfix( 79 | repository, 80 | 'my-hotfix' 81 | ) 82 | .then((mergeCommit) => { 83 | console.log(mergeCommit.id()); // => the sha of the newly created commit 84 | }); 85 | ``` 86 | 87 | ### finishRelease(repository, name, [options]) 88 | By default `finishRelease` will merge the release branch into the develop branch and the master branch, create a tag the points to the merge commit on master, and delete the release branch. If successful, finishRelease will resolve with the merge commit. If a merge conflict occurs `finishRelease` will reject with the index of the conflict. 89 | 90 | `options` Object 91 | * `isRebase` Boolean default=`false` 92 | * `keepBranch` Boolean default=`false` 93 | * `message` String tag annotation default=`''` 94 | 95 | Example: 96 | ```javascript 97 | NodeGit.Flow.finishRelease( 98 | repository, 99 | 'my-release' 100 | ) 101 | .then((mergeCommit) => { 102 | console.log(mergeCommit.id()); // => the sha of the newly created commit 103 | }); 104 | ``` 105 | 106 | ### getConfig(repository) 107 | Retrieves an object that contains the git config values that are relevant to git flow 108 | 109 | ### getConfigDefault() 110 | Returns the following object which is the standard git flow config object 111 | ```javascript 112 | { 113 | 'gitflow.branch.master': 'main', 114 | 'gitflow.branch.develop': 'develop', 115 | 'gitflow.prefix.feature': 'feature/', 116 | 'gitflow.prefix.release': 'release/', 117 | 'gitflow.prefix.hotfix': 'hotfix/', 118 | 'gitflow.prefix.versiontag': '' 119 | } 120 | ``` 121 | 122 | ### getConfigRequiredKeys() 123 | Returns the config keys that are required to use `nodegit-flow` 124 | 125 | ### getDevelopBranch(repository) 126 | Returns the value stored within a repos git config with the key of `gitflow.branch.develop` 127 | 128 | ### getHotfixPrefix(repository) 129 | Returns the value stored within a repos git config with the key of `gitflow.prefix.hotfix` 130 | 131 | ### getMasterBranch(repository) 132 | Returns the value stored within a repos git config with the key of `gitflow.branch.master` 133 | 134 | ### getFeaturePrefix(repository) 135 | Returns the value stored within a repos git config with the key of `gitflow.prefix.feature` 136 | 137 | ### getReleasePrefix(repository) 138 | Returns the value stored within a repos git config with the key of `gitflow.prefix.release` 139 | 140 | ### getSupportPrefix(repository) 141 | Returns the value stored within a repos git config with the key of `gitflow.prefix.support` 142 | 143 | ### getVersionTagPrefix(repository) 144 | Returns the value stored within a repos git config with the key of `gitflow.prefix.versiontag` 145 | 146 | ### init(repository, config) 147 | Sets the git flow config values for the given repo and returns a new instance of `nodegit-flow`. This new instance contains all of the static methods within the `NodeGit.Flow` object but does not require a repository to be passed in when using its methods. 148 | 149 | ### isInitialized(repository) 150 | Resolves to true or false depending on whether the repository has git flow initialized 151 | 152 | Example: 153 | ```javascript 154 | NodeGit.Flow.isInitialized(repository) 155 | .then((isInitialized) => { 156 | console.log(isInitialized); // => true or false depending the git config of the repo 157 | }); 158 | ``` 159 | 160 | ### open(repository) 161 | Resolves to a new instance of `nodegit-flow` if the repository has git flow initialized, otherwise reject with the reason 162 | 163 | Example: 164 | ```javascript 165 | NodeGit.Flow.open(repository) 166 | .then((flow) => { 167 | return flow.getMasterBranch(); 168 | }) 169 | .then((masterBranchName) => { 170 | console.log(masterBranchName); // => main 171 | }); 172 | ``` 173 | 174 | ### startFeature(repository, name, [options]) 175 | `options` Object 176 | * `sha` String 177 | 178 | `options` is an object with a `sha` that marks the starting commit of the feature. If no `sha` is passed in, the feature will start at the `develop` branch. 179 | 180 | The name of the feature branch is the `featurePrefix` set in the git config appended with the passed in `name` parameter; 181 | 182 | Example: 183 | ```javascript 184 | NodeGit.Flow.startFeature( 185 | repository, 186 | 'my-feature', 187 | {sha: 'a7b7a15c94df9528339fd86b9808ec2d9c645705'} 188 | ) 189 | .then((featureBranch) => { 190 | console.log(featureBranch.shorthand()); // => feature/my-feature 191 | }); 192 | ``` 193 | 194 | ### startHotfix(repository, name) 195 | The name of the hotfix branch is the `hotfixPrefix` set in the git config appended with the passed in `name` parameter; 196 | 197 | Example: 198 | ```javascript 199 | NodeGit.Flow.startHotfix( 200 | repository, 201 | '0.1.13' 202 | ) 203 | .then((hotfixBranch) => { 204 | console.log(hotfixBranch.shorthand()); // => hotfix/0.1.13 205 | }); 206 | ``` 207 | 208 | ### startRelease(repository, name [options]) 209 | `options` Object 210 | * `sha` String 211 | 212 | `options` is an object with a `sha` that marks the starting commit of the release. If no `sha` is passed in, the release will start at the `develop` branch. 213 | 214 | The name of the release branch is the `releasePrefix` set in the git config appended with the passed in `name` parameter; 215 | 216 | Example: 217 | ```javascript 218 | NodeGit.Flow.startRelease( 219 | repository, 220 | '0.2.0' 221 | ) 222 | .then((releaseBranch) => { 223 | console.log(releaseBranch.shorthand()); // => release/0.2.0 224 | }); 225 | ``` 226 | 227 | ### validateConfig(config) 228 | Validates that a config object has all of the required keys for nodegit-flow to work. 229 | 230 | Example: 231 | ```javascript 232 | const result = NodeGit.Flow.validateConfig({ 233 | 'gitflow.branch.master': 'main', 234 | 'gitflow.branch.develop': 'develop', 235 | 'gitflow.prefix.feature': 'feature/', 236 | 'gitflow.prefix.hotfix': 'hotfix/' 237 | }); 238 | console.log(result); // => gitflow config missing key(s): gitflow.prefix.release 239 | ``` 240 | -------------------------------------------------------------------------------- /docs/Base.md: -------------------------------------------------------------------------------- 1 | 2 | ## Base 3 | All of this class' functions are attached to `NodeGit.Flow` 4 | 5 | **Kind**: global class 6 | 7 | * [Base](#Base) 8 | * [.init(repo, gitflowConfig)](#Base.init) ⇒ Flow 9 | * [.isInitialized(repo)](#Base.isInitialized) ⇒ Boolean 10 | * [.open(repo)](#Base.open) ⇒ Flow 11 | 12 | 13 | ### Base.init(repo, gitflowConfig) ⇒ Flow 14 | Initializes the repo to use git flow 15 | 16 | **Kind**: static method of [Base](#Base) 17 | **Returns**: Flow - An instance of a flow object tied to the repository 18 | **Async**: 19 | 20 | | Param | Type | Description | 21 | | --- | --- | --- | 22 | | repo | Repository | The repository to initialize git flow in | 23 | | gitflowConfig | Object | The git flow configuration to use | 24 | 25 | 26 | ### Base.isInitialized(repo) ⇒ Boolean 27 | Check if the repo is using git flow 28 | 29 | **Kind**: static method of [Base](#Base) 30 | **Returns**: Boolean - Whether or not the repo has git flow initialized 31 | **Async**: 32 | 33 | | Param | Type | Description | 34 | | --- | --- | --- | 35 | | repo | Repository | The nodegit repository instance to check | 36 | 37 | 38 | ### Base.open(repo) ⇒ Flow 39 | Creates a Flow instance for a repo that already has git flow initialized 40 | 41 | **Kind**: static method of [Base](#Base) 42 | **Returns**: Flow - An instance of a flow object tied to the repository 43 | **Async**: 44 | 45 | | Param | Type | Description | 46 | | --- | --- | --- | 47 | | repo | Repository | The target nodegit repository | 48 | 49 | -------------------------------------------------------------------------------- /docs/Config.md: -------------------------------------------------------------------------------- 1 | 2 | ## Config 3 | All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | 5 | **Kind**: global class 6 | 7 | * [Config](#Config) 8 | * _instance_ 9 | * [.getConfig()](#Config+getConfig) ⇒ Object 10 | * [.getMasterBranch()](#Config+getMasterBranch) ⇒ String 11 | * [.getDevelopBranch()](#Config+getDevelopBranch) ⇒ String 12 | * [.getFeaturePrefix()](#Config+getFeaturePrefix) ⇒ String 13 | * [.getReleasePrefix()](#Config+getReleasePrefix) ⇒ String 14 | * [.getHotfixPrefix()](#Config+getHotfixPrefix) ⇒ String 15 | * [.getVersionTagPrefix()](#Config+getVersionTagPrefix) ⇒ String 16 | * _static_ 17 | * [.getConfigDefault()](#Config.getConfigDefault) ⇒ Object 18 | * [.getConfigRequiredKeys()](#Config.getConfigRequiredKeys) ⇒ Array 19 | * [.validateConfig(config)](#Config.validateConfig) ⇒ Number | String 20 | * [.getConfig(repo)](#Config.getConfig) ⇒ Object 21 | * [.getMasterBranch(The)](#Config.getMasterBranch) ⇒ String 22 | * [.getDevelopBranch(The)](#Config.getDevelopBranch) ⇒ String 23 | * [.getFeaturePrefix(The)](#Config.getFeaturePrefix) ⇒ String 24 | * [.getReleasePrefix(The)](#Config.getReleasePrefix) ⇒ String 25 | * [.getHotfixPrefix(The)](#Config.getHotfixPrefix) ⇒ String 26 | * [.getVersionTagPrefix(The)](#Config.getVersionTagPrefix) ⇒ String 27 | 28 | 29 | ### config.getConfig() ⇒ Object 30 | Gets the git flow related config values for the repository 31 | 32 | **Kind**: instance method of [Config](#Config) 33 | **Returns**: Object - An object of git flow config key/value pairs 34 | **Async**: 35 | 36 | ### config.getMasterBranch() ⇒ String 37 | Gets the config value for the git flow master branch 38 | 39 | **Kind**: instance method of [Config](#Config) 40 | **Returns**: String - The config value of the git flow master branch 41 | **Async**: 42 | 43 | ### config.getDevelopBranch() ⇒ String 44 | Gets the config value for the git flow develop branch 45 | 46 | **Kind**: instance method of [Config](#Config) 47 | **Returns**: String - The config value of the git flow develop branch 48 | **Async**: 49 | 50 | ### config.getFeaturePrefix() ⇒ String 51 | Gets the config value for the git flow feature prefix 52 | 53 | **Kind**: instance method of [Config](#Config) 54 | **Returns**: String - The config value of the git flow feature prefix 55 | **Async**: 56 | 57 | ### config.getReleasePrefix() ⇒ String 58 | Gets the config value for the git flow release prefix 59 | 60 | **Kind**: instance method of [Config](#Config) 61 | **Returns**: String - The config value of the git flow release prefix 62 | **Async**: 63 | 64 | ### config.getHotfixPrefix() ⇒ String 65 | Gets the config value for the git flow hotfix prefix 66 | 67 | **Kind**: instance method of [Config](#Config) 68 | **Returns**: String - The config value of the git flow hotfix prefix 69 | **Async**: 70 | 71 | ### config.getVersionTagPrefix() ⇒ String 72 | Gets the config value for the git flow version tag prefix 73 | 74 | **Kind**: instance method of [Config](#Config) 75 | **Returns**: String - The config value of the git flow version tag prefix 76 | **Async**: 77 | 78 | ### Config.getConfigDefault() ⇒ Object 79 | Get default git flow configuration values you can use for initializing. Note that the `initialize` function does 80 | not use any values the user did not explicitly pass in. 81 | 82 | **Kind**: static method of [Config](#Config) 83 | **Returns**: Object - An object of git flow config key/value pairs. 84 | 85 | ### Config.getConfigRequiredKeys() ⇒ Array 86 | Get a list of git flow config keys that are required for initializing git flow 87 | 88 | **Kind**: static method of [Config](#Config) 89 | **Returns**: Array - A list of config keys 90 | 91 | ### Config.validateConfig(config) ⇒ Number | String 92 | Checks a config object for all required git flow config keys. 93 | 94 | **Kind**: static method of [Config](#Config) 95 | **Returns**: Number | String - An error message, or 0 if all required keys are present. 96 | 97 | | Param | Type | Description | 98 | | --- | --- | --- | 99 | | config | Object | An object of git flow config key/value pairs to check | 100 | 101 | 102 | ### Config.getConfig(repo) ⇒ Object 103 | Gets the git flow related config values for the repository 104 | 105 | **Kind**: static method of [Config](#Config) 106 | **Returns**: Object - An object of git flow config key/value pairs 107 | **Async**: 108 | 109 | | Param | Type | Description | 110 | | --- | --- | --- | 111 | | repo | Repository | The nodegit repository to get the config values from | 112 | 113 | 114 | ### Config.getMasterBranch(The) ⇒ String 115 | Gets the config value for the git flow master branch 116 | 117 | **Kind**: static method of [Config](#Config) 118 | **Returns**: String - The config value of the git flow master branch 119 | **Async**: 120 | 121 | | Param | Type | Description | 122 | | --- | --- | --- | 123 | | The | Repository | nodegit repository to get the config value from | 124 | 125 | 126 | ### Config.getDevelopBranch(The) ⇒ String 127 | Gets the config value for the git flow develop branch 128 | 129 | **Kind**: static method of [Config](#Config) 130 | **Returns**: String - The config value of the git flow develop branch 131 | **Async**: 132 | 133 | | Param | Type | Description | 134 | | --- | --- | --- | 135 | | The | Repository | nodegit repository to get the config value from | 136 | 137 | 138 | ### Config.getFeaturePrefix(The) ⇒ String 139 | Gets the config value for the git flow feature prefix 140 | 141 | **Kind**: static method of [Config](#Config) 142 | **Returns**: String - The config value of the git flow feature prefix 143 | **Async**: 144 | 145 | | Param | Type | Description | 146 | | --- | --- | --- | 147 | | The | Repository | nodegit repository to get the config value from | 148 | 149 | 150 | ### Config.getReleasePrefix(The) ⇒ String 151 | Gets the config value for the git flow release prefix 152 | 153 | **Kind**: static method of [Config](#Config) 154 | **Returns**: String - The config value of the git flow release prefix 155 | **Async**: 156 | 157 | | Param | Type | Description | 158 | | --- | --- | --- | 159 | | The | Repository | nodegit repository to get the config value from | 160 | 161 | 162 | ### Config.getHotfixPrefix(The) ⇒ String 163 | Gets the config value for the git flow hotfix prefix 164 | 165 | **Kind**: static method of [Config](#Config) 166 | **Returns**: String - The config value of the git flow hotfix prefix 167 | **Async**: 168 | 169 | | Param | Type | Description | 170 | | --- | --- | --- | 171 | | The | Repository | nodegit repository to get the config value from | 172 | 173 | 174 | ### Config.getVersionTagPrefix(The) ⇒ String 175 | Gets the config value for the git flow version tag prefix 176 | 177 | **Kind**: static method of [Config](#Config) 178 | **Returns**: String - The config value of the git flow version tag prefix 179 | **Async**: 180 | 181 | | Param | Type | Description | 182 | | --- | --- | --- | 183 | | The | Repository | nodegit repository to get the config value from | 184 | 185 | -------------------------------------------------------------------------------- /docs/Feature.md: -------------------------------------------------------------------------------- 1 | 2 | ## Feature 3 | All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | 5 | **Kind**: global class 6 | 7 | * [Feature](#Feature) 8 | * _instance_ 9 | * [.startFeature(featureName, options)](#Feature+startFeature) ⇒ Branch 10 | * [.finishFeature(featureName, options)](#Feature+finishFeature) ⇒ Commit 11 | * _static_ 12 | * [.startFeature(repo, featureName, options)](#Feature.startFeature) ⇒ Branch 13 | * [.finishFeature(repo, featureName, options)](#Feature.finishFeature) ⇒ Commit 14 | 15 | 16 | ### feature.startFeature(featureName, options) ⇒ Branch 17 | Starts a git flow "feature" 18 | 19 | **Kind**: instance method of [Feature](#Feature) 20 | **Returns**: Branch - The nodegit branch for the feature 21 | **Async**: 22 | 23 | | Param | Type | Description | 24 | | --- | --- | --- | 25 | | featureName | String | The name of the feature to start | 26 | | options | Object | Options for start feature | 27 | 28 | 29 | 30 | ### feature.finishFeature(featureName, options) ⇒ Commit 31 | Finishes a git flow "feature" 32 | 33 | **Kind**: instance method of [Feature](#Feature) 34 | **Returns**: Commit - The commit created by finishing the feature 35 | **Async**: 36 | 37 | | Param | Type | Description | 38 | | --- | --- | --- | 39 | | featureName | String | The name of the feature to finish | 40 | | options | Object | Options for finish feature | 41 | 42 | **Options**: 43 | 44 | | Option | Type | Description | 45 | | --- | --- | --- | 46 | | keepBranch | Boolean | Keep the branch after merge | 47 | | isRebase | Boolean | Use Rebase instead of merge | 48 | | preRebaseCallback | Function | Callback that is fired before rebase occurs, only applicable if **isRebase** is truthy. If the callback returns a Promise, the **preRebaseCallback** promise must succeed before the rebase occurs. The **preRebaseCallback** is called with development branch name and the feature branch name. | 49 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs, only applicable if **isRebase** is falsy. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 50 | | postMergeCallback | Function | Callback fired after a successful merge occurs. | 51 | | beforeRebaseFinishCallback | Function | Callback that is fired right before a rebase is finished with metadata reflecting the rebase operation in full. See [Rebase Branches](http://www.nodegit.org/api/repository/#rebaseBranches) | 52 | 53 | 54 | ### Feature.startFeature(repo, featureName, options) ⇒ Branch 55 | Starts a git flow "feature" 56 | 57 | **Kind**: static method of [Feature](#Feature) 58 | **Returns**: Branch - The nodegit branch for the feature 59 | **Async**: 60 | 61 | | Param | Type | Description | 62 | | --- | --- | --- | 63 | | repo | Object | The repository to start a feature in | 64 | | featureName | String | The name of the feature to start | 65 | | options | Object | Options for start feature | 66 | 67 | 68 | ### Feature.finishFeature(repo, featureName, options) ⇒ Commit 69 | Finishes a git flow "feature" 70 | 71 | **Kind**: static method of [Feature](#Feature) 72 | **Returns**: Commit - The commit created by finishing the feature 73 | **Async**: 74 | 75 | | Param | Type | Description | 76 | | --- | --- | --- | 77 | | repo | Object | The repository to finish a feature in | 78 | | featureName | String | The name of the feature to finish | 79 | | options | Object | Options for finish feature | 80 | 81 | **Options**: 82 | 83 | | Option | Type | Description | 84 | | --- | --- | --- | 85 | | keepBranch | Boolean | Keep the branch after merge | 86 | | isRebase | Boolean | Use Rebase instead of merge | 87 | | preRebaseCallback | Function | Callback that is fired before rebase occurs, only applicable if **isRebase** is truthy. If the callback returns a Promise, the **preRebaseCallback** promise must succeed before the rebase occurs. The **preRebaseCallback** is called with development branch name and the feature branch name. | 88 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs, only applicable if **isRebase** is falsy. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 89 | | postMergeCallback | Function | Callback fired after a successful merge occurs. | 90 | | beforeRebaseFinishCallback | Function | Callback that is fired right before a rebase is finished with metadata reflecting the rebase operation in full. See [Rebase Branches](http://www.nodegit.org/api/repository/#rebaseBranches) | 91 | -------------------------------------------------------------------------------- /docs/Hotfix.md: -------------------------------------------------------------------------------- 1 | 2 | ## Hotfix 3 | All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | 5 | **Kind**: global class 6 | 7 | * [Hotfix](#Hotfix) 8 | * _instance_ 9 | * [.startHotfix(hotfixVersion, options)](#Hotfix+startHotfix) ⇒ Branch 10 | * [.finishHotfix(hotfixVersion, options)](#Hotfix+finishHotfix) ⇒ Commit 11 | * _static_ 12 | * [.startHotfix(repo, hotfixVersion, options)](#Hotfix.startHotfix) ⇒ Branch 13 | * [.finishHotfix(repo, hotfixVersion, options)](#Hotfix.finishHotfix) ⇒ Commit 14 | 15 | 16 | ### hotfix.startHotfix(hotfixVersion, options) ⇒ Branch 17 | Starts a git flow "hotfix" 18 | 19 | **Kind**: instance method of [Hotfix](#Hotfix) 20 | **Returns**: Branch - The nodegit branch for the hotfix 21 | **Async**: 22 | 23 | | Param | Type | Description | 24 | | --- | --- | --- | 25 | | hotfixVersion | String | The version of the hotfix to start | 26 | | options | Object | Options for start hotfix | 27 | 28 | 29 | ### hotfix.finishHotfix(hotfixVersion, options) ⇒ Commit 30 | Finishes a git flow "hotfix" 31 | 32 | **Kind**: instance method of [Hotfix](#Hotfix) 33 | **Returns**: Commit - The commit created by finishing the hotfix 34 | **Async**: 35 | 36 | | Param | Type | Description | 37 | | --- | --- | --- | 38 | | hotfixVersion | String | The version of the hotfix to finish | 39 | | options | Object | Options for finish hotfix | 40 | 41 | **Options**: 42 | 43 | | Option | Type | Description | 44 | | --- | --- | --- | 45 | | keepBranch | Boolean | Keep the branch after merge | 46 | | message | String | Tag will be created with this message | 47 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 48 | | beforeMergeCallback | Function | Callback fired immediately before a merge occurs. Is parameterized by targetBranchName and hotFixBranchName | 49 | | postDevelopMergeCallback | Function | Callback fired after a successful merge with development occurs. | 50 | | postMasterMergeCallback | Function | Callback fired after a successful merge with master occurs. | 51 | 52 | 53 | ### Hotfix.startHotfix(repo, hotfixVersion, options) ⇒ Branch 54 | Starts a git flow "hotfix" 55 | 56 | **Kind**: static method of [Hotfix](#Hotfix) 57 | **Returns**: Branch - The nodegit branch for the hotfix 58 | **Async**: 59 | 60 | | Param | Type | Description | 61 | | --- | --- | --- | 62 | | repo | Object | The repository to start a hotfix in | 63 | | hotfixVersion | String | The version of the hotfix to start | 64 | | options | Object | Options for start hotfix | 65 | 66 | 67 | ### Hotfix.finishHotfix(repo, hotfixVersion, options) ⇒ Commit 68 | Finishes a git flow "hotfix" 69 | 70 | **Kind**: static method of [Hotfix](#Hotfix) 71 | **Returns**: Commit - The commit created by finishing the hotfix 72 | **Async**: 73 | 74 | | Param | Type | Description | 75 | | --- | --- | --- | 76 | | repo | Object | The repository to finish a hotfix in | 77 | | hotfixVersion | String | The version of the hotfix to finish | 78 | | options | Object | Options for finish hotfix | 79 | 80 | **Options**: 81 | 82 | | Option | Type | Description | 83 | | --- | --- | --- | 84 | | keepBranch | Boolean | Keep the branch after merge | 85 | | message | String | Tag will be created with this message | 86 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 87 | | postDevelopMergeCallback | Function | Callback fired after a successful merge with development occurs. | 88 | | postMasterMergeCallback | Function | Callback fired after a successful merge with master occurs. | 89 | -------------------------------------------------------------------------------- /docs/Release.md: -------------------------------------------------------------------------------- 1 | 2 | ## Release 3 | All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | 5 | **Kind**: global class 6 | 7 | * [Release](#Release) 8 | * _instance_ 9 | * [.startRelease(releaseVersion, options)](#Release+startRelease) ⇒ Branch 10 | * [.finishRelease(releaseVersion, options)](#Release+finishRelease) ⇒ Commit 11 | * _static_ 12 | * [.startRelease(repo, releaseVersion, options)](#Release.startRelease) ⇒ Branch 13 | * [.finishRelease(repo, releaseVersion, options)](#Release.finishRelease) ⇒ Commit 14 | 15 | 16 | ### release.startRelease(releaseVersion, options) ⇒ Branch 17 | Starts a git flow "release" 18 | 19 | **Kind**: instance method of [Release](#Release) 20 | **Returns**: Branch - The nodegit branch for the release 21 | **Async**: 22 | 23 | | Param | Type | Description | 24 | | --- | --- | --- | 25 | | releaseVersion | String | The version of the release to start | 26 | | options | Object | Options for start release | 27 | 28 | 29 | ### release.finishRelease(releaseVersion, options) ⇒ Commit 30 | Finishes a git flow "release" 31 | 32 | **Kind**: instance method of [Release](#Release) 33 | **Returns**: Commit - The commit created by finishing the release 34 | **Async**: 35 | 36 | | Param | Type | Description | 37 | | --- | --- | --- | 38 | | releaseVersion | String | The name of the release to finish | 39 | | options | Object | Options for finish release | 40 | 41 | **Options**: 42 | 43 | | Option | Type | Description | 44 | | --- | --- | --- | 45 | | keepBranch | Boolean | Keep the branch after merge | 46 | | message | String | Tag will be created with this message | 47 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 48 | | beforeMergeCallback | Function | Callback fired immediately before a merge occurs. Is parameterized by targetBranchName and releaseBranchName | 49 | | postDevelopMergeCallback | Function | Callback fired after a successful merge with development occurs. | 50 | | postMasterMergeCallback | Function | Callback fired after a successful merge with master occurs. | 51 | 52 | 53 | ### Release.startRelease(repo, releaseVersion, options) ⇒ Branch 54 | Starts a git flow "release" 55 | 56 | **Kind**: static method of [Release](#Release) 57 | **Returns**: Branch - The nodegit branch for the release 58 | **Async**: 59 | 60 | | Param | Type | Description | 61 | | --- | --- | --- | 62 | | repo | Object | The repository to start a release in | 63 | | releaseVersion | String | The version of the release to start | 64 | | options | Object | Options for start release | 65 | 66 | 67 | ### Release.finishRelease(repo, releaseVersion, options) ⇒ Commit 68 | Finishes a git flow "release" 69 | 70 | **Kind**: static method of [Release](#Release) 71 | **Returns**: Commit - The commit created by finishing the release 72 | **Async**: 73 | 74 | | Param | Type | Description | 75 | | --- | --- | --- | 76 | | repo | Object | The repository to finish a release in | 77 | | releaseVersion | String | The name of the release to finish | 78 | | options | Object | Options for finish release | 79 | 80 | **Options**: 81 | 82 | | Option | Type | Description | 83 | | --- | --- | --- | 84 | | keepBranch | Boolean | Keep the branch after merge | 85 | | message | String | Tag will be created with this message | 86 | | processMergeMessageCallback | Function | Callback that is fired before merge occurs. If the callback returns a Promise, the **processMergeMessageCallback** promise must succeed before the merge occurs. The result of the **processMergeMessageCallback** must be a string or a promise that resolves to a string, as that message will be used for the merge message. the **processMergeMessageCallback** will be called with the generated merge message as a parameter. | 87 | | postDevelopMergeCallback | Function | Callback fired after a successful merge with development occurs. | 88 | | postMasterMergeCallback | Function | Callback fired after a successful merge with master occurs. | 89 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "build/spec", 3 | "spec_files": [ 4 | "**/*-spec.js" 5 | ], 6 | "stopSpecOnExpectationFailure": false, 7 | "random": false 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodegit-flow", 3 | "version": "2.2.4", 4 | "description": "nodegit-flow contains gitflow methods that aren't include in the vanilla nodegit package", 5 | "main": "build/src/index.js", 6 | "scripts": { 7 | "compile": "babel -d ./build/spec ./spec && babel -d ./build/src ./src", 8 | "eslint": "./node_modules/.bin/eslint src spec", 9 | "prepublish": "babel -d ./build/src ./src", 10 | "debug-test": "npm run eslint && npm run compile && node-debug --debug-brk node_modules/.bin/jasmine --config=jasmine.json", 11 | "test": "npm run eslint && npm run compile && node_modules/.bin/jasmine --config=jasmine.json" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Axosoft/nodegit-flow.git" 16 | }, 17 | "keywords": [ 18 | "node", 19 | "git", 20 | "gitflow", 21 | "nodegit" 22 | ], 23 | "files": [ 24 | "build/src" 25 | ], 26 | "author": "Axosoft, LLC", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/Axosoft/nodegit-flow/issues" 30 | }, 31 | "homepage": "https://github.com/Axosoft/nodegit-flow#readme", 32 | "devDependencies": { 33 | "@babel/cli": "^7.8.4", 34 | "@babel/core": "^7.8.4", 35 | "@babel/preset-env": "^7.8.4", 36 | "eslint": "^5.8.0", 37 | "fs-extra": "^0.26.2", 38 | "jasmine": "^2.99.0", 39 | "jsdoc-to-markdown": "^5.0.3", 40 | "nodegit": "^0.28.0-alpha.21" 41 | }, 42 | "peerDependencies": { 43 | "nodegit": ">=0.28.0-alpha.21" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spec/tests/Base-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const NodeGit = require('../utils/NodeGit'); 4 | 5 | const { Base } = NodeGit.Flow.__TEST__; 6 | 7 | describe('Base', function() { 8 | beforeEach(function() { 9 | const repoConfig = { 10 | lock() { 11 | return Promise.resolve({ 12 | commit() { 13 | return Promise.resolve(); 14 | } 15 | }); 16 | }, 17 | getString() { 18 | throw new Error('Do not call config.getString without taking a snapshot first.'); 19 | }, 20 | setString() { 21 | return Promise.resolve(); 22 | }, 23 | snapshot() { 24 | return Promise.resolve({ 25 | getString(key) { 26 | const defaultConfig = NodeGit.Flow.getConfigDefault(); 27 | return defaultConfig[key] || true; 28 | } 29 | }); 30 | } 31 | }; 32 | this.repo = { 33 | config() { 34 | return Promise.resolve(repoConfig); 35 | }, 36 | createBranch() { 37 | return Promise.resolve(); 38 | }, 39 | getBranchCommit() { 40 | return Promise.resolve({id(){ return '12345';}}); 41 | } 42 | }; 43 | }); 44 | 45 | it('should have all its static methods', function() { 46 | expect(NodeGit.Flow.init).toEqual(jasmine.any(Function)); 47 | expect(NodeGit.Flow.isInitialized).toEqual(jasmine.any(Function)); 48 | expect(NodeGit.Flow.open).toEqual(jasmine.any(Function)); 49 | }); 50 | 51 | describe('init', function() { 52 | it('should throw error if no repository is passed', function(done) { 53 | spyOn(NodeGit.Branch, 'lookup').and.returnValue(Promise.resolve()); 54 | return NodeGit.Flow.init() 55 | .then(jasmine.fail) 56 | .catch((reason) => { 57 | expect(reason).toEqual(jasmine.any(Error)); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('should return new flow object if repository is passed', function(done) { 63 | spyOn(NodeGit.Branch, 'lookup').and.returnValue(Promise.resolve({ id: () => '' })); 64 | const defaultConfig = NodeGit.Flow.getConfigDefault(); 65 | return NodeGit.Flow.init(this.repo, defaultConfig) 66 | .then((flow) => { 67 | expect(flow).toBeDefined(); 68 | expect(flow.startFeature).toEqual(jasmine.any(Function)); 69 | expect(flow.startFeature).toEqual(jasmine.any(Function)); 70 | expect(flow.startHotfix).toEqual(jasmine.any(Function)); 71 | expect(flow.startHotfix).toEqual(jasmine.any(Function)); 72 | expect(flow.startRelease).toEqual(jasmine.any(Function)); 73 | expect(flow.startRelease).toEqual(jasmine.any(Function)); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('develop branch should exist after initialization', function(done) { 79 | spyOn(NodeGit.Branch, 'lookup').and.returnValue(Promise.resolve({ id: () => '' })); 80 | const defaultConfig = NodeGit.Flow.getConfigDefault(); 81 | return NodeGit.Flow.init(this.repo, defaultConfig) 82 | .then(() => NodeGit.Flow.developBranchExists(this.repo)) 83 | .then((exists) => { 84 | expect(exists).toBeTruthy(); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('develop branch should not exist after initialization and delete develop branch', function(done) { 90 | const defaultConfig = NodeGit.Flow.getConfigDefault(); 91 | spyOn(NodeGit.Branch, 'lookup').and.callFake((repo, branchName) => { 92 | if (branchName === defaultConfig['gitflow.branch.develop']) { 93 | return Promise.reject(new Error('Could not find branch')); 94 | } 95 | return Promise.resolve(); 96 | }); 97 | return NodeGit.Flow.init(this.repo, defaultConfig) 98 | .then(() => NodeGit.Flow.developBranchExists(this.repo)) 99 | .then((exists) => { 100 | expect(exists).toBeFalsy(); 101 | done(); 102 | }); 103 | }); 104 | 105 | it('master branch should not exist after initialization and delete master branch', function(done) { 106 | const defaultConfig = NodeGit.Flow.getConfigDefault(); 107 | spyOn(NodeGit.Branch, 'lookup').and.callFake((repo, branchName) => { 108 | if (branchName === defaultConfig['gitflow.branch.master']) { 109 | return Promise.reject(new Error('Could not find branch')); 110 | } 111 | return Promise.resolve(); 112 | }); 113 | return NodeGit.Flow.masterBranchExists(this.repo) 114 | .then((exists) => { 115 | expect(exists).toBeFalsy(); 116 | done(); 117 | }); 118 | }); 119 | }); 120 | 121 | describe('open', function() { 122 | beforeEach(function() { 123 | spyOn(NodeGit.Branch, 'lookup').and.returnValue(Promise.resolve()); 124 | }); 125 | 126 | it('should throw error if no repository is passed', function(done) { 127 | return NodeGit.Flow.open() 128 | .catch((reason) => { 129 | expect(reason).toEqual(jasmine.any(Error)); 130 | done(); 131 | }); 132 | }); 133 | 134 | it('should throw error if repository does not have gitflow initialized', function(done) { 135 | spyOn(Base, 'isInitialized').and.returnValue(Promise.resolve(false)); 136 | 137 | return NodeGit.Flow.open(this.repo) 138 | .catch((reason) => { 139 | expect(reason).toEqual(jasmine.any(Error)); 140 | done(); 141 | }); 142 | }); 143 | 144 | it('should create a Flow instance for an already initialized repository', function(done) { 145 | spyOn(Base, 'isInitialized').and.returnValue(Promise.resolve(true)); 146 | 147 | return NodeGit.Flow.open(this.repo) 148 | .then((flow) => { 149 | expect(flow).toBeDefined(); 150 | expect(flow.startFeature).toEqual(jasmine.any(Function)); 151 | expect(flow.startFeature).toEqual(jasmine.any(Function)); 152 | expect(flow.startHotfix).toEqual(jasmine.any(Function)); 153 | expect(flow.startHotfix).toEqual(jasmine.any(Function)); 154 | expect(flow.startRelease).toEqual(jasmine.any(Function)); 155 | expect(flow.startRelease).toEqual(jasmine.any(Function)); 156 | done(); 157 | }); 158 | }); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /spec/tests/Feature-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const NodeGit = require('../utils/NodeGit'); 4 | const RepoUtils = require('../utils/RepoUtils'); 5 | const MergeUtils = require('../../src/utils/MergeUtils'); 6 | 7 | const expectStartFeatureSuccess = function expectStartFeatureSuccess(featureBranch, expectedBranchName) { 8 | expect(featureBranch.isBranch()).toBeTruthy(); 9 | expect(featureBranch.shorthand()).toBe(expectedBranchName); 10 | expect(featureBranch.isHead()).toBeTruthy(); 11 | }; 12 | 13 | const expectFinishFeatureSuccess = function expectFinishFeatureSuccess(featureBranch, keepBranch, message) { 14 | let developBranch; 15 | const promise = NodeGit.Branch.lookup( 16 | this.repo, 17 | this.config['gitflow.branch.develop'], 18 | NodeGit.Branch.BRANCH.LOCAL 19 | ) 20 | .then((_developBranch) => { 21 | developBranch = _developBranch; 22 | expect(developBranch.isHead()); 23 | return this.repo.getCommit(developBranch.target()); 24 | }) 25 | .then((developCommit) => { 26 | const expectedCommitMessage = message || MergeUtils.getMergeMessage(developBranch, featureBranch); 27 | expect(developCommit.message()).toBe(expectedCommitMessage); 28 | return NodeGit.Branch.lookup(this.repo, featureBranch.shorthand(), NodeGit.Branch.BRANCH.LOCAL); 29 | }); 30 | 31 | if (!keepBranch) { 32 | return promise.catch((err) => { 33 | expect(err.message.toLowerCase()).toBe(`cannot locate local branch '${featureBranch.shorthand().toLowerCase()}'`); 34 | }); 35 | } 36 | 37 | return promise; 38 | }; 39 | 40 | describe('Feature', function() { 41 | beforeEach(function(done) { 42 | this.repoName = 'featureRepo'; 43 | this.fileName = 'foobar.js'; 44 | return RepoUtils.createRepo(this.repoName) 45 | .then((repo) => { 46 | this.repo = repo; 47 | return RepoUtils.commitFileToRepo( 48 | this.repo, 49 | this.fileName, 50 | 'Line1\nLine2\nLine3' 51 | ); 52 | }) 53 | .then((firstCommit) => { 54 | this.firstCommit = firstCommit; 55 | this.config = NodeGit.Flow.getConfigDefault(); 56 | this.featurePrefix = this.config['gitflow.prefix.feature']; 57 | 58 | return NodeGit.Flow.init(this.repo, this.config); 59 | }) 60 | .then((flow) => { 61 | this.flow = flow; 62 | done(); 63 | }); 64 | }); 65 | 66 | afterEach(function() { 67 | RepoUtils.deleteRepo(this.repoName); 68 | }); 69 | 70 | it('should be able to start feature statically', function(done) { 71 | const featureName = 'foobar'; 72 | NodeGit.Flow.startFeature(this.repo, featureName) 73 | .then((featureBranch) => { 74 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 75 | done(); 76 | }); 77 | }); 78 | 79 | it('should be able to start feature using flow instance', function(done) { 80 | const featureName = 'foobar'; 81 | this.flow.startFeature(featureName) 82 | .then((featureBranch) => { 83 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('should be able to finish feature statically', function(done) { 89 | const featureName = 'foobar'; 90 | let featureBranch; 91 | NodeGit.Flow.startFeature(this.repo, featureName) 92 | .then((_featureBranch) => { 93 | featureBranch = _featureBranch; 94 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 95 | 96 | return RepoUtils.commitFileToRepo( 97 | this.repo, 98 | 'someFile.js', 99 | 'Hello World', 100 | 'second commit', 101 | this.firstCommit 102 | ); 103 | }) 104 | .then(() => NodeGit.Flow.finishFeature(this.repo, featureName)) 105 | .then(() => expectFinishFeatureSuccess.call(this, featureBranch)) 106 | .then(done); 107 | }); 108 | 109 | it('should be able to finish feature on flow instance', function(done) { 110 | const featureName = 'foobar'; 111 | let featureBranch; 112 | this.flow.startFeature(featureName) 113 | .then((_featureBranch) => { 114 | featureBranch = _featureBranch; 115 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 116 | 117 | return RepoUtils.commitFileToRepo( 118 | this.repo, 119 | 'someFile.js', 120 | 'Hello World', 121 | 'second commit', 122 | this.firstCommit 123 | ); 124 | }) 125 | .then(() => this.flow.finishFeature(featureName, {keepBranch: true})) 126 | .then(() => expectFinishFeatureSuccess.call(this, featureBranch)) 127 | .then(done); 128 | }); 129 | 130 | it('should be able to finish feature statically and keep the branch', function(done) { 131 | const featureName = 'foobar'; 132 | let featureBranch; 133 | NodeGit.Flow.startFeature(this.repo, featureName) 134 | .then((_featureBranch) => { 135 | featureBranch = _featureBranch; 136 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 137 | 138 | return RepoUtils.commitFileToRepo( 139 | this.repo, 140 | 'someFile.js', 141 | 'Hello World', 142 | 'second commit', 143 | this.firstCommit 144 | ); 145 | }) 146 | .then(() => NodeGit.Flow.finishFeature(this.repo, featureName, {keepBranch: true})) 147 | .then(() => expectFinishFeatureSuccess.call(this, featureBranch, true)) 148 | .then(done); 149 | }); 150 | 151 | it('should be able to finish feature on flow instance and keep the branch', function(done) { 152 | const featureName = 'foobar'; 153 | let featureBranch; 154 | this.flow.startFeature(featureName) 155 | .then((_featureBranch) => { 156 | featureBranch = _featureBranch; 157 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 158 | 159 | return RepoUtils.commitFileToRepo( 160 | this.repo, 161 | 'someFile.js', 162 | 'Hello World', 163 | 'second commit', 164 | this.firstCommit 165 | ); 166 | }) 167 | .then(() => this.flow.finishFeature(featureName, {keepBranch: true})) 168 | .then(() => expectFinishFeatureSuccess.call(this, featureBranch, true)) 169 | .then(done); 170 | }); 171 | 172 | it('should be able to finish feature statically with a rebase', function(done) { 173 | const featureName = 'foobar'; 174 | let featureBranch; 175 | NodeGit.Flow.startFeature(this.repo, featureName) 176 | .then((_featureBranch) => { 177 | featureBranch = _featureBranch; 178 | expectStartFeatureSuccess(featureBranch, this.featurePrefix + featureName); 179 | 180 | return RepoUtils.commitFileToRepo( 181 | this.repo, 182 | 'someFile.js', 183 | 'Hello World', 184 | 'second commit', 185 | this.firstCommit 186 | ); 187 | }) 188 | .then(() => this.repo.checkoutBranch('develop')) 189 | .then(() => RepoUtils.commitFileToRepo( 190 | this.repo, 191 | 'someOtherFile.js', 192 | 'Some content', 193 | 'third commit', 194 | this.firstCommit, 195 | 'refs/heads/develop' 196 | )) 197 | .then(() => NodeGit.Flow.finishFeature(this.repo, featureName, {keepBranch: false, isRebase: true})) 198 | .then(() => expectFinishFeatureSuccess.call(this, featureBranch, false, 'second commit')) 199 | .then(done); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /spec/tests/Hotfix-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const NodeGit = require('../utils/NodeGit'); 4 | const RepoUtils = require('../utils/RepoUtils'); 5 | 6 | const { utils } = NodeGit.Flow.__TEST__; 7 | 8 | const expectStartHotfixSuccess = function expectStartHotfixSuccess(hotfixBranch, expectedBranchName) { 9 | expect(hotfixBranch.isBranch()).toBeTruthy(); 10 | expect(hotfixBranch.shorthand()).toBe(expectedBranchName); 11 | expect(hotfixBranch.isHead()).toBeTruthy(); 12 | }; 13 | 14 | const expectFinishHotfixSuccess = function expectFinishHotfixSuccess( 15 | hotfixBranch, 16 | expectedTagName, 17 | keepBranch, 18 | developMergeMessage, 19 | masterMergeMessage 20 | ) { 21 | let developBranch; 22 | let masterBranch; 23 | let developCommit; 24 | let masterCommit; 25 | const promise = Promise.all([this.config['gitflow.branch.develop'], this.config['gitflow.branch.master']].map( 26 | (branch) => NodeGit.Branch.lookup( 27 | this.repo, 28 | branch, 29 | NodeGit.Branch.BRANCH.LOCAL 30 | ) 31 | )) 32 | .then((branches) => { 33 | developBranch = branches[0]; 34 | masterBranch = branches[1]; 35 | expect(developBranch.isHead()); 36 | return Promise.all(branches.map((branch) => this.repo.getCommit(branch.target()))); 37 | }) 38 | .then((commits) => { 39 | developCommit = commits[0]; 40 | masterCommit = commits[1]; 41 | const expectedDevelopCommitMessage 42 | = developMergeMessage || utils.Merge.getMergeMessage(developBranch, hotfixBranch); 43 | const expectedMasterCommitMessage 44 | = masterMergeMessage || utils.Merge.getMergeMessage(masterBranch, hotfixBranch); 45 | expect(developCommit.message()).toBe(expectedDevelopCommitMessage); 46 | expect(masterCommit.message()).toBe(expectedMasterCommitMessage); 47 | return NodeGit.Reference.lookup(this.repo, expectedTagName); 48 | }) 49 | .then((tag) => { 50 | expect(tag.isTag()).toBeTruthy(); 51 | expect(tag.target()).toEqual(masterCommit.id()); 52 | return NodeGit.Branch.lookup(this.repo, hotfixBranch.shorthand(), NodeGit.Branch.BRANCH.LOCAL); 53 | }); 54 | 55 | if (!keepBranch) { 56 | return promise 57 | .catch((err) => { 58 | expect(err.message.toLowerCase()).toBe(`cannot locate local branch '${hotfixBranch.shorthand().toLowerCase()}'`); 59 | }); 60 | } 61 | 62 | return promise; 63 | }; 64 | 65 | describe('Hotfix', function() { 66 | beforeEach(function(done) { 67 | this.repoName = 'hotfixRepo'; 68 | this.fileName = 'foobar.js'; 69 | return RepoUtils.createRepo(this.repoName) 70 | .then((repo) => { 71 | this.repo = repo; 72 | return RepoUtils.commitFileToRepo( 73 | this.repo, 74 | this.fileName, 75 | 'Line1\nLine2\nLine3' 76 | ); 77 | }) 78 | .then((firstCommit) => { 79 | this.firstCommit = firstCommit; 80 | this.config = NodeGit.Flow.getConfigDefault(); 81 | this.hotfixPrefix = this.config['gitflow.prefix.hotfix']; 82 | this.versionPrefix = this.config['gitflow.prefix.versiontag']; 83 | 84 | return NodeGit.Flow.init(this.repo, this.config); 85 | }) 86 | .then((flow) => { 87 | this.flow = flow; 88 | done(); 89 | }); 90 | }); 91 | 92 | afterEach(function() { 93 | RepoUtils.deleteRepo(this.repoName); 94 | }); 95 | 96 | it('should be able to start hotfix statically', function(done) { 97 | const hotfixName = '1.0.0'; 98 | NodeGit.Flow.startHotfix(this.repo, hotfixName) 99 | .then((hotfixBranch) => { 100 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 101 | done(); 102 | }); 103 | }); 104 | 105 | it('should be able to start hotfix using flow instance', function(done) { 106 | const hotfixName = '1.0.0'; 107 | this.flow.startHotfix(hotfixName) 108 | .then((hotfixBranch) => { 109 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 110 | done(); 111 | }); 112 | }); 113 | 114 | it('should be able to finish hotfix statically', function(done) { 115 | const hotfixName = '1.0.0'; 116 | const fullTagName = `refs/tags/${this.versionPrefix}${hotfixName}`; 117 | let hotfixBranch; 118 | NodeGit.Flow.startHotfix(this.repo, hotfixName) 119 | .then((_hotfixBranch) => { 120 | hotfixBranch = _hotfixBranch; 121 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 122 | return RepoUtils.commitFileToRepo( 123 | this.repo, 124 | 'anotherFile.js', 125 | 'Hello World', 126 | 'second commit', 127 | this.firstCommit 128 | ); 129 | }) 130 | .then(() => NodeGit.Flow.finishHotfix(this.repo, hotfixName)) 131 | .then(() => expectFinishHotfixSuccess.call(this, hotfixBranch, fullTagName)) 132 | .then(done); 133 | }); 134 | 135 | it('should be able to finish hotfix using flow instance', function(done) { 136 | const hotfixName = '1.0.0'; 137 | const fullTagName = `refs/tags/${this.versionPrefix}${hotfixName}`; 138 | let hotfixBranch; 139 | this.flow.startHotfix(hotfixName) 140 | .then((_hotfixBranch) => { 141 | hotfixBranch = _hotfixBranch; 142 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 143 | 144 | return RepoUtils.commitFileToRepo( 145 | this.repo, 146 | 'anotherFile.js', 147 | 'Hello World', 148 | 'second commit', 149 | this.firstCommit 150 | ); 151 | }) 152 | .then(() => this.flow.finishHotfix(hotfixName)) 153 | .then(() => expectFinishHotfixSuccess.call(this, hotfixBranch, fullTagName)) 154 | .then(done); 155 | }); 156 | 157 | it('should be able to finish hotfix statically and keep the branch', function(done) { 158 | const hotfixName = '1.0.0'; 159 | const fullTagName = `refs/tags/${this.versionPrefix}${hotfixName}`; 160 | let hotfixBranch; 161 | NodeGit.Flow.startHotfix(this.repo, hotfixName) 162 | .then((_hotfixBranch) => { 163 | hotfixBranch = _hotfixBranch; 164 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 165 | return RepoUtils.commitFileToRepo( 166 | this.repo, 167 | 'anotherFile.js', 168 | 'Hello World', 169 | 'second commit', 170 | this.firstCommit 171 | ); 172 | }) 173 | .then(() => NodeGit.Flow.finishHotfix(this.repo, hotfixName, {keepBranch: true})) 174 | .then(() => expectFinishHotfixSuccess.call(this, hotfixBranch, fullTagName, true)) 175 | .then(done); 176 | }); 177 | 178 | it('should be able to finish hotfix using flow instance and keep the branch', function(done) { 179 | const hotfixName = '1.0.0'; 180 | const fullTagName = `refs/tags/${this.versionPrefix}${hotfixName}`; 181 | let hotfixBranch; 182 | this.flow.startHotfix(hotfixName) 183 | .then((_hotfixBranch) => { 184 | hotfixBranch = _hotfixBranch; 185 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 186 | 187 | return RepoUtils.commitFileToRepo( 188 | this.repo, 189 | 'anotherFile.js', 190 | 'Hello World', 191 | 'second commit', 192 | this.firstCommit 193 | ); 194 | }) 195 | .then(() => this.flow.finishHotfix(hotfixName, {keepBranch: true})) 196 | .then(() => expectFinishHotfixSuccess.call(this, hotfixBranch, fullTagName, true)) 197 | .then(done); 198 | }); 199 | 200 | it('should be able to finish a hotfix that is still pointed at master', function(done) { 201 | const hotfixName = '1.0.0'; 202 | const fullTagName = `refs/tags/${this.versionPrefix}${hotfixName}`; 203 | const expectedCommitMessage = 'initial commit'; 204 | let hotfixBranch; 205 | this.flow.startHotfix(hotfixName) 206 | .then((_hotfixBranch) => { 207 | hotfixBranch = _hotfixBranch; 208 | expectStartHotfixSuccess(hotfixBranch, this.hotfixPrefix + hotfixName); 209 | return this.flow.finishHotfix(hotfixName, {keepBranch: true}); 210 | }) 211 | .then(() => expectFinishHotfixSuccess.call( 212 | this, 213 | hotfixBranch, 214 | fullTagName, 215 | true, 216 | expectedCommitMessage, 217 | expectedCommitMessage 218 | )) 219 | .then(done); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /spec/tests/Release-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const NodeGit = require('../utils/NodeGit'); 4 | const RepoUtils = require('../utils/RepoUtils'); 5 | 6 | const { utils } = NodeGit.Flow.__TEST__; 7 | 8 | const expectStartReleaseSuccess = function expectStartReleaseSuccess(releaseBranch, expectedBranchName) { 9 | expect(releaseBranch.isBranch()).toBeTruthy(); 10 | expect(releaseBranch.shorthand()).toBe(expectedBranchName); 11 | expect(releaseBranch.isHead()).toBeTruthy(); 12 | }; 13 | 14 | const expectFinishReleaseSuccess = function expectFinishReleaseSuccess( 15 | releaseBranch, 16 | expectedTagName, 17 | keepBranch, 18 | developCommitMessage, 19 | masterCommitMessage 20 | ) { 21 | let developBranch; 22 | let masterBranch; 23 | let developCommit; 24 | let masterCommit; 25 | const promise = Promise.all([this.config['gitflow.branch.develop'], this.config['gitflow.branch.master']].map( 26 | (branch) => NodeGit.Branch.lookup( 27 | this.repo, 28 | branch, 29 | NodeGit.Branch.BRANCH.LOCAL 30 | ) 31 | )) 32 | .then((branches) => { 33 | developBranch = branches[0]; 34 | masterBranch = branches[1]; 35 | expect(developBranch.isHead()); 36 | return Promise.all(branches.map((branch) => this.repo.getCommit(branch.target()))); 37 | }) 38 | .then((commits) => { 39 | developCommit = commits[0]; 40 | masterCommit = commits[1]; 41 | const expectedDevelopCommitMessage = 42 | developCommitMessage || utils.Merge.getMergeMessage(developBranch, releaseBranch); 43 | const expectedMasterCommitMessage = 44 | masterCommitMessage || utils.Merge.getMergeMessage(masterBranch, releaseBranch); 45 | expect(developCommit.message()).toBe(expectedDevelopCommitMessage); 46 | expect(masterCommit.message()).toBe(expectedMasterCommitMessage); 47 | return NodeGit.Reference.lookup(this.repo, expectedTagName); 48 | }) 49 | .then((tag) => { 50 | expect(tag.isTag()).toBeTruthy(); 51 | expect(tag.target()).toEqual(masterCommit.id()); 52 | return NodeGit.Branch.lookup(this.repo, releaseBranch.shorthand(), NodeGit.Branch.BRANCH.LOCAL); 53 | }); 54 | 55 | if (!keepBranch) { 56 | return promise 57 | .catch((err) => { 58 | expect(err.message.toLowerCase()).toBe(`cannot locate local branch '${releaseBranch.shorthand().toLowerCase()}'`); 59 | }); 60 | } 61 | 62 | return promise; 63 | }; 64 | 65 | describe('Release', function() { 66 | beforeEach(function(done) { 67 | this.repoName = 'releaseRepo'; 68 | this.fileName = 'foobar.js'; 69 | return RepoUtils.createRepo(this.repoName) 70 | .then((repo) => { 71 | this.repo = repo; 72 | return RepoUtils.commitFileToRepo( 73 | this.repo, 74 | this.fileName, 75 | 'Line1\nLine2\nLine3' 76 | ); 77 | }) 78 | .then((firstCommit) => { 79 | this.firstCommit = firstCommit; 80 | this.config = NodeGit.Flow.getConfigDefault(); 81 | this.releasePrefix = this.config['gitflow.prefix.release']; 82 | this.versionPrefix = this.config['gitflow.prefix.versiontag']; 83 | 84 | return NodeGit.Flow.init(this.repo, this.config); 85 | }) 86 | .then((flow) => { 87 | this.flow = flow; 88 | done(); 89 | }); 90 | }); 91 | 92 | afterEach(function() { 93 | RepoUtils.deleteRepo(this.repoName); 94 | }); 95 | 96 | it('should be able to start release statically', function(done) { 97 | const releaseName = '1.0.0'; 98 | NodeGit.Flow.startRelease(this.repo, releaseName) 99 | .then((releaseBranch) => { 100 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 101 | done(); 102 | }); 103 | }); 104 | 105 | it('should be able to start release using flow instance', function(done) { 106 | const releaseName = '1.0.0'; 107 | this.flow.startRelease(releaseName) 108 | .then((releaseBranch) => { 109 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 110 | done(); 111 | }); 112 | }); 113 | 114 | it('should be able to finish release statically', function(done) { 115 | const releaseName = '1.0.0'; 116 | const fullTagName = `refs/tags/${this.versionPrefix}${releaseName}`; 117 | let releaseBranch; 118 | NodeGit.Flow.startRelease(this.repo, releaseName) 119 | .then((_releaseBranch) => { 120 | releaseBranch = _releaseBranch; 121 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 122 | 123 | return RepoUtils.commitFileToRepo( 124 | this.repo, 125 | 'anotherFile.js', 126 | 'some content', 127 | 'second commit', 128 | this.firstCommit 129 | ); 130 | }) 131 | .then(() => NodeGit.Flow.finishRelease(this.repo, releaseName)) 132 | .then(() => expectFinishReleaseSuccess.call(this, releaseBranch, fullTagName)) 133 | .then(done); 134 | }); 135 | 136 | it('should be able to finish release using flow instance', function(done) { 137 | const releaseName = '1.0.0'; 138 | const fullTagName = `refs/tags/${this.versionPrefix}${releaseName}`; 139 | let releaseBranch; 140 | this.flow.startRelease(releaseName) 141 | .then((_releaseBranch) => { 142 | releaseBranch = _releaseBranch; 143 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 144 | 145 | return RepoUtils.commitFileToRepo( 146 | this.repo, 147 | 'anotherFile.js', 148 | 'some content', 149 | 'second commit', 150 | this.firstCommit 151 | ); 152 | }) 153 | .then(() => this.flow.finishRelease(releaseName)) 154 | .then(() => expectFinishReleaseSuccess.call(this, releaseBranch, fullTagName)) 155 | .then(done); 156 | }); 157 | 158 | it('should be able to finish release statically and keep the branch', function(done) { 159 | const releaseName = '1.0.0'; 160 | const fullTagName = `refs/tags/${this.versionPrefix}${releaseName}`; 161 | let releaseBranch; 162 | NodeGit.Flow.startRelease(this.repo, releaseName) 163 | .then((_releaseBranch) => { 164 | releaseBranch = _releaseBranch; 165 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 166 | 167 | return RepoUtils.commitFileToRepo( 168 | this.repo, 169 | 'anotherFile.js', 170 | 'some content', 171 | 'second commit', 172 | this.firstCommit 173 | ); 174 | }) 175 | .then(() => NodeGit.Flow.finishRelease(this.repo, releaseName, {keepBranch: true})) 176 | .then(() => expectFinishReleaseSuccess.call(this, releaseBranch, fullTagName, true)) 177 | .then(done); 178 | }); 179 | 180 | it('should be able to finish release using flow instance and keep the branch', function(done) { 181 | const releaseName = '1.0.0'; 182 | const fullTagName = `refs/tags/${this.versionPrefix}${releaseName}`; 183 | let releaseBranch; 184 | this.flow.startRelease(releaseName) 185 | .then((_releaseBranch) => { 186 | releaseBranch = _releaseBranch; 187 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 188 | 189 | return RepoUtils.commitFileToRepo( 190 | this.repo, 191 | 'anotherFile.js', 192 | 'some content', 193 | 'second commit', 194 | this.firstCommit 195 | ); 196 | }) 197 | .then(() => this.flow.finishRelease(releaseName, {keepBranch: true})) 198 | .then(() => expectFinishReleaseSuccess.call(this, releaseBranch, fullTagName, true)) 199 | .then(done); 200 | }); 201 | 202 | it('should be able to finish release while branch is still pointed at master', function(done) { 203 | const releaseName = '1.0.0'; 204 | const fullTagName = `refs/tags/${this.versionPrefix}${releaseName}`; 205 | const commitMessage = 'initial commit'; 206 | let releaseBranch; 207 | this.flow.startRelease(releaseName) 208 | .then((_releaseBranch) => { 209 | releaseBranch = _releaseBranch; 210 | expectStartReleaseSuccess(releaseBranch, this.releasePrefix + releaseName); 211 | return this.flow.finishRelease(releaseName, {keepBranch: true}); 212 | }) 213 | .then(() => expectFinishReleaseSuccess.call(this, releaseBranch, fullTagName, true, commitMessage, commitMessage)) 214 | .then(done); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /spec/tests/index-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const { Flow } = require('../utils/NodeGit'); 4 | 5 | describe('Flow', function() { 6 | it('should contain all of the static methods', function() { 7 | expect(Flow.startFeature).toEqual(jasmine.any(Function)); 8 | expect(Flow.startFeature).toEqual(jasmine.any(Function)); 9 | expect(Flow.startHotfix).toEqual(jasmine.any(Function)); 10 | expect(Flow.startHotfix).toEqual(jasmine.any(Function)); 11 | expect(Flow.startRelease).toEqual(jasmine.any(Function)); 12 | expect(Flow.startRelease).toEqual(jasmine.any(Function)); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /spec/tests/utils/Promisify-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint prefer-arrow-callback: 0 */ 2 | 3 | const promisify = require('../../../src/utils/Promisify'); 4 | 5 | describe('Promisify', function() { 6 | beforeEach(function() { 7 | const test = this; 8 | test.successMessage = 'Successfully called this function'; 9 | test.errorMessage = 'This is an error'; 10 | 11 | this.standardFunction = (arg, cb) => { 12 | if (arg) { 13 | cb(null, test.successMessage); 14 | } 15 | else { 16 | cb(test.errorMessage); 17 | } 18 | }; 19 | }); 20 | 21 | it('can promisify a standard function with a callback and succeed', function(done) { 22 | const promisifiedFunction = promisify(this.standardFunction); 23 | promisifiedFunction(true) 24 | .then((res) => { 25 | expect(res).toBe(this.successMessage); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('can promisify a standard function with a callback and fail', function(done) { 31 | const promisifiedFunction = promisify(this.standardFunction); 32 | promisifiedFunction(false) 33 | .catch((err) => { 34 | expect(err).toBe(this.errorMessage); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /spec/utils/NodeGit.js: -------------------------------------------------------------------------------- 1 | const NodeGit = require('nodegit'); 2 | const attachFlow = require('../../src'); 3 | 4 | process.env.NODEGIT_FLOW_TESTING_ENABLED = true; 5 | module.exports = attachFlow(NodeGit); 6 | -------------------------------------------------------------------------------- /spec/utils/RepoUtils.js: -------------------------------------------------------------------------------- 1 | const fse = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const promisify = require('../../src/utils/Promisify'); 5 | const NodeGit = require('./NodeGit'); 6 | 7 | fse.writeFile = promisify(fse.writeFile); 8 | 9 | const RepoUtils = { 10 | repoDir: '../repos', 11 | 12 | addFileToIndex(repository, fileName) { 13 | return repository.index() 14 | .then((index) => { 15 | return index.addByPath(fileName) 16 | .then(() => index.write()) 17 | .then(() => index.writeTree()); 18 | }); 19 | }, 20 | 21 | createRepo(repoName) { 22 | const repoPath = path.join(__dirname, this.repoDir, repoName); 23 | fse.ensureDirSync(repoPath); 24 | return NodeGit.Repository.init(repoPath, 0); 25 | }, 26 | 27 | commitFileToRepo(repo, fileName, fileContent, _commitMessage, parent, branchName = 'HEAD') { 28 | const repoWorkDir = repo.workdir(); 29 | const signature = NodeGit.Signature.create('Foo bar', 30 | 'foo@bar.com', 123456789, 60); 31 | const commitMessage = _commitMessage || 'initial commit'; 32 | const parents = (parent) ? [parent] : []; 33 | 34 | return fse.writeFile(path.join(repoWorkDir, fileName), fileContent) 35 | .then(() => this.addFileToIndex(repo, fileName)) 36 | .then((oid) => repo.createCommit(branchName, signature, signature, commitMessage, oid, parents)) 37 | .then((commitOid) => repo.getCommit(commitOid)); 38 | }, 39 | 40 | deleteRepo(repoName) { 41 | const repoPath = path.join(__dirname, this.repoDir, repoName); 42 | if (fse.existsSync(repoPath)) { 43 | fse.removeSync(repoPath); 44 | } 45 | } 46 | }; 47 | 48 | module.exports = RepoUtils; 49 | -------------------------------------------------------------------------------- /src/Base.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, { constants }, { Config, Feature, Hotfix, Release }) => { 2 | const GitFlowClasses = [Config, Feature, Hotfix, Release]; 3 | 4 | function createFlowInstance(repo) { 5 | const Flow = {}; 6 | 7 | // Magic to keep individual object context when using init methods 8 | GitFlowClasses.forEach((GitFlowClass) => { 9 | const gitflowObject = new GitFlowClass(repo); 10 | Object.getOwnPropertyNames(GitFlowClass.prototype).forEach((propName) => { 11 | if (propName !== 'constructor' && typeof GitFlowClass.prototype[propName] === 'function') { 12 | Flow[propName] = function() { 13 | return gitflowObject[propName].apply(gitflowObject, arguments); 14 | }; 15 | } 16 | }); 17 | }); 18 | 19 | return Flow; 20 | } 21 | 22 | /** 23 | * All of this class' functions are attached to `NodeGit.Flow` 24 | * @class 25 | */ 26 | class Base { 27 | /** 28 | * Check if the repo is initialized with git flow and its develop branch exists 29 | * @async 30 | * @param {Repository} repo The nodegit repository instance to check 31 | * @return {Boolean} Whether or not the develop branch as specified in the git config exists 32 | */ 33 | static developBranchExists(repo) { 34 | if (!repo) { 35 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 36 | } 37 | 38 | return this.isInitialized(repo) 39 | .then((isInitialized) => { 40 | if (!isInitialized) { 41 | return Promise.reject(new Error(constants.ErrorMessage.GIT_FLOW_NOT_INITIALIZED)); 42 | } 43 | 44 | return repo.config() 45 | .then((config) => config.snapshot()) 46 | .then((snapshot) => snapshot.getString('gitflow.branch.develop')) 47 | .then((developBranchName) => NodeGit.Branch.lookup(repo, developBranchName, NodeGit.Branch.BRANCH.LOCAL)) 48 | .then(() => true) 49 | .catch(() => false); 50 | }); 51 | } 52 | 53 | /** 54 | * Initializes the repo to use git flow 55 | * @async 56 | * @param {Repository} repo The repository to initialize git flow in 57 | * @param {Object} gitflowConfig The git flow configuration to use 58 | * @return {Flow} An instance of a flow object tied to the repository 59 | */ 60 | static init(repo, gitflowConfig) { 61 | if (!repo) { 62 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 63 | } 64 | 65 | gitflowConfig = gitflowConfig || {}; 66 | 67 | const defaultConfig = Config.getConfigDefault(); 68 | const configKeys = Object.keys(defaultConfig); 69 | const configToUse = {}; 70 | 71 | // filter out non-gitflow keys 72 | configKeys.forEach((key) => { 73 | configToUse[key] = gitflowConfig[key]; 74 | }); 75 | 76 | const configError = Config.validateConfig(configToUse); 77 | if (configError) { 78 | return Promise.reject(new Error(configError)); 79 | } 80 | 81 | const masterBranchName = configToUse['gitflow.branch.master']; 82 | const developBranchName = configToUse['gitflow.branch.develop']; 83 | 84 | return NodeGit.Branch.lookup(repo, masterBranchName, NodeGit.Branch.BRANCH.LOCAL) 85 | .catch(() => { 86 | return Promise.reject(new Error('The branch set as the `master` branch must already exist locally')); 87 | }) 88 | .then(() => { 89 | // Create the `develop` branch if it does not already exist 90 | return NodeGit.Branch.lookup(repo, developBranchName, NodeGit.Branch.BRANCH.LOCAL) 91 | .catch(() => 92 | repo.getBranchCommit(`refs/remotes/origin/${developBranchName}`) 93 | .catch(() => repo.getBranchCommit(masterBranchName)) 94 | .then((commit) => repo.createBranch(developBranchName, commit.id())) 95 | ); 96 | }) 97 | .then(() => repo.config()) 98 | .then((config) => { 99 | // Set the config values. We chain them so we don't have concurrent setString calls to the same config file 100 | return config.lock().then((transaction) => { 101 | const setPromise = configKeys.reduce((last, next) => { 102 | return last 103 | .then(() => { 104 | return config.setString(next, configToUse[next]); 105 | }); 106 | }, Promise.resolve()); 107 | 108 | // NOTE The only way to unlock is to call `commit` or to free the transaction. 109 | // NodeGit doesn't expose explicitly freeing the transaction, so if setPromise rejects, 110 | // we simply wait for it to self-free. 111 | return setPromise.then(() => transaction.commit()); 112 | }); 113 | }) 114 | .then(() => createFlowInstance(repo)); 115 | } 116 | 117 | /** 118 | * Check if the repo is using git flow 119 | * @async 120 | * @param {Repository} repo The nodegit repository instance to check 121 | * @return {Boolean} Whether or not the repo has git flow initialized 122 | */ 123 | static isInitialized(repo) { 124 | if (!repo) { 125 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 126 | } 127 | 128 | return repo.config() 129 | .then((config) => config.snapshot()) 130 | .then((snapshot) => { 131 | const promises = Config.getConfigRequiredKeys().map((key) => { 132 | return snapshot.getString(key); 133 | }); 134 | 135 | return Promise.all(promises) 136 | .then(() => true) 137 | .catch(() => false); 138 | }); 139 | } 140 | 141 | /** 142 | * Check if the repo is initialized with git flow and its master branch exists 143 | * @async 144 | * @param {Repository} repo The nodegit repository instance to check 145 | * @return {Boolean} Whether or not the master branch as specified in the git config exists 146 | */ 147 | static masterBranchExists(repo) { 148 | if (!repo) { 149 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 150 | } 151 | 152 | return Base.isInitialized(repo) 153 | .then((isInitialized) => { 154 | if (!isInitialized) { 155 | return Promise.reject(new Error(constants.ErrorMessage.GIT_FLOW_NOT_INITIALIZED)); 156 | } 157 | 158 | return repo.config() 159 | .then((config) => config.snapshot()) 160 | .then((snapshot) => snapshot.getString('gitflow.branch.master')) 161 | .then((masterBranchName) => NodeGit.Branch.lookup(repo, masterBranchName, NodeGit.Branch.BRANCH.LOCAL)) 162 | .then(() => true) 163 | .catch(() => false); 164 | }); 165 | } 166 | 167 | /** 168 | * Creates a Flow instance for a repo that already has git flow initialized 169 | * @async 170 | * @param {Repository} repo The target nodegit repository 171 | * @return {Flow} An instance of a flow object tied to the repository 172 | */ 173 | static open(repo) { 174 | return Base.isInitialized(repo) 175 | .then((isInitialized) => { 176 | if (!isInitialized) { 177 | return Promise.reject(new Error('The repository does not have gitflow initialized')); 178 | } 179 | 180 | return createFlowInstance(repo); 181 | }); 182 | } 183 | } 184 | 185 | return Base; 186 | }; 187 | -------------------------------------------------------------------------------- /src/Config.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, { constants }) => { 2 | function _getConfigKeys() { 3 | return Object.keys(Config.getConfigDefault()); 4 | } 5 | 6 | function _getConfigValue(repo, configKey) { 7 | if (!repo) { 8 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 9 | } 10 | 11 | if (_getConfigKeys().indexOf(configKey) === -1) { 12 | return Promise.reject(new Error('Invalid gitflow config key.')); 13 | } 14 | 15 | return repo.config() 16 | .then((config) => config.snapshot()) 17 | .then((snapshot) => snapshot.getString(configKey)) 18 | .catch(() => Promise.reject(new Error(`Failed to read config value ${configKey}`))); 19 | } 20 | 21 | /** 22 | * All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 23 | * @class 24 | */ 25 | class Config { 26 | constructor(repo) { 27 | this.repo = repo; 28 | } 29 | 30 | /** 31 | * Get default git flow configuration values you can use for initializing. Note that the `initialize` function does 32 | * not use any values the user did not explicitly pass in. 33 | * @return {Object} An object of git flow config key/value pairs. 34 | */ 35 | static getConfigDefault() { 36 | return { 37 | 'gitflow.branch.master': 'main', 38 | 'gitflow.branch.develop': 'develop', 39 | 40 | 'gitflow.prefix.feature': 'feature/', 41 | 'gitflow.prefix.release': 'release/', 42 | 'gitflow.prefix.hotfix': 'hotfix/', 43 | 44 | 'gitflow.prefix.versiontag': '' 45 | }; 46 | } 47 | 48 | /** 49 | * Get a list of git flow config keys that are required for initializing git flow 50 | * @return {Array} A list of config keys 51 | */ 52 | static getConfigRequiredKeys() { 53 | return [ 54 | 'gitflow.branch.master', 55 | 'gitflow.branch.develop', 56 | 'gitflow.prefix.feature', 57 | 'gitflow.prefix.release', 58 | 'gitflow.prefix.hotfix' 59 | ]; 60 | } 61 | 62 | /** 63 | * Checks a config object for all required git flow config keys. 64 | * @param {Object} config An object of git flow config key/value pairs to check 65 | * @return {Number|String} An error message, or 0 if all required keys are present. 66 | */ 67 | static validateConfig(config) { 68 | const missingKeys = Config.getConfigRequiredKeys().filter((key) => { 69 | return !config[key] || typeof config[key] !== 'string'; 70 | }); 71 | 72 | if (missingKeys.length) { 73 | return 'gitflow config missing key(s): ' + missingKeys.join(', '); 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | /** 80 | * Gets the git flow related config values for the repository 81 | * @async 82 | * @param {Repository} repo The nodegit repository to get the config values from 83 | * @return {Object} An object of git flow config key/value pairs 84 | */ 85 | static getConfig(repo) { 86 | if (!repo) { 87 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 88 | } 89 | 90 | const configKeys = _getConfigKeys(); 91 | 92 | return repo.config() 93 | .then((config) => config.snapshot()) 94 | .then((snapshot) => { 95 | const promises = configKeys.map((key) => { 96 | return snapshot.getString(key); 97 | }); 98 | 99 | return Promise.all(promises); 100 | }) 101 | .then((values) => { 102 | const result = {}; 103 | configKeys.forEach((key, i) => { 104 | result[key] = values[i]; 105 | }); 106 | 107 | return result; 108 | }); 109 | } 110 | 111 | /** 112 | * Gets the git flow related config values for the repository 113 | * @async 114 | * @return {Object} An object of git flow config key/value pairs 115 | */ 116 | getConfig() { 117 | return Config.getConfig(this.repo); 118 | } 119 | 120 | /** 121 | * Gets the config value for the git flow master branch 122 | * @async 123 | * @param {Repository} repo The nodegit repository to get the config value from 124 | * @return {String} The config value of the git flow master branch 125 | */ 126 | static getMasterBranch(repo) { 127 | return _getConfigValue(repo, 'gitflow.branch.master'); 128 | } 129 | 130 | /** 131 | * Gets the config value for the git flow master branch 132 | * @async 133 | * @return {String} The config value of the git flow master branch 134 | */ 135 | getMasterBranch() { 136 | return Config.getMasterBranch(this.repo); 137 | } 138 | 139 | 140 | /** 141 | * Gets the config value for the git flow develop branch 142 | * @async 143 | * @param {Repository} repo The nodegit repository to get the config value from 144 | * @return {String} The config value of the git flow develop branch 145 | */ 146 | static getDevelopBranch(repo) { 147 | return _getConfigValue(repo, 'gitflow.branch.develop'); 148 | } 149 | 150 | /** 151 | * Gets the config value for the git flow develop branch 152 | * @async 153 | * @return {String} The config value of the git flow develop branch 154 | */ 155 | getDevelopBranch() { 156 | return Config.getDevelopBranch(this.repo); 157 | } 158 | 159 | 160 | /** 161 | * Gets the config value for the git flow feature prefix 162 | * @async 163 | * @param {Repository} repo The nodegit repository to get the config value from 164 | * @return {String} The config value of the git flow feature prefix 165 | */ 166 | static getFeaturePrefix(repo) { 167 | return _getConfigValue(repo, 'gitflow.prefix.feature'); 168 | } 169 | 170 | 171 | /** 172 | * Gets the config value for the git flow feature prefix 173 | * @async 174 | * @return {String} The config value of the git flow feature prefix 175 | */ 176 | getFeaturePrefix() { 177 | return Config.getFeaturePrefix(this.repo); 178 | } 179 | 180 | 181 | /** 182 | * Gets the config value for the git flow release prefix 183 | * @async 184 | * @param {Repository} repo The nodegit repository to get the config value from 185 | * @return {String} The config value of the git flow release prefix 186 | */ 187 | static getReleasePrefix(repo) { 188 | return _getConfigValue(repo, 'gitflow.prefix.release'); 189 | } 190 | 191 | /** 192 | * Gets the config value for the git flow release prefix 193 | * @async 194 | * @return {String} The config value of the git flow release prefix 195 | */ 196 | getReleasePrefix() { 197 | return Config.getReleasePrefix(this.repo); 198 | } 199 | 200 | /** 201 | * Gets the config value for the git flow hotfix prefix 202 | * @async 203 | * @param {Repository} repo The nodegit repository to get the config value from 204 | * @return {String} The config value of the git flow hotfix prefix 205 | */ 206 | static getHotfixPrefix(repo) { 207 | return _getConfigValue(repo, 'gitflow.prefix.hotfix'); 208 | } 209 | 210 | /** 211 | * Gets the config value for the git flow hotfix prefix 212 | * @async 213 | * @return {String} The config value of the git flow hotfix prefix 214 | */ 215 | getHotfixPrefix() { 216 | return Config.getHotfixPrefix(this.repo); 217 | } 218 | 219 | // static getSupportPrefix(repo) { 220 | // return _getConfigValue(repo, 'gitflow.prefix.support'); 221 | // } 222 | // 223 | // getSupportPrefix() { 224 | // return Config.getSupportPrefix(this.repo); 225 | // } 226 | 227 | /** 228 | * Gets the config value for the git flow version tag prefix 229 | * @async 230 | * @param {Repository} repo The nodegit repository to get the config value from 231 | * @return {String} The config value of the git flow version tag prefix 232 | */ 233 | static getVersionTagPrefix(repo) { 234 | return _getConfigValue(repo, 'gitflow.prefix.versiontag'); 235 | } 236 | 237 | /** 238 | * Gets the config value for the git flow version tag prefix 239 | * @async 240 | * @return {String} The config value of the git flow version tag prefix 241 | */ 242 | getVersionTagPrefix() { 243 | return Config.getVersionTagPrefix(this.repo); 244 | } 245 | } 246 | 247 | return Config; 248 | }; 249 | -------------------------------------------------------------------------------- /src/Feature.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, { constants, utils }, { Config }) => { 2 | /** 3 | * All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | * @class 5 | */ 6 | class Feature { 7 | constructor(repo, config) { 8 | this.repo = repo; 9 | this.config = config; 10 | } 11 | 12 | /** 13 | * Starts a git flow "feature" 14 | * @async 15 | * @param {Object} repo The repository to start a feature in 16 | * @param {String} featureName The name of the feature to start 17 | * @param {Object} options Options for start feature 18 | * @return {Branch} The nodegit branch for the feature 19 | */ 20 | static startFeature(repo, featureName, options = {}) { 21 | const { 22 | postCheckoutHook = () => {}, 23 | sha 24 | } = options; 25 | 26 | if (!repo) { 27 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 28 | } 29 | 30 | if (!featureName) { 31 | return Promise.reject(new Error('Feature name is required')); 32 | } 33 | 34 | let featureBranchName; 35 | let featureBranch; 36 | 37 | return Config.getConfig(repo) 38 | .then((config) => { 39 | const featurePrefix = config['gitflow.prefix.feature']; 40 | const developBranchName = config['gitflow.branch.develop']; 41 | 42 | featureBranchName = featurePrefix + featureName; 43 | if (sha) { 44 | return NodeGit.Commit.lookup(repo, sha); 45 | } 46 | 47 | return NodeGit.Branch.lookup( 48 | repo, 49 | developBranchName, 50 | NodeGit.Branch.BRANCH.LOCAL 51 | ) 52 | .then((developBranch) => NodeGit.Commit.lookup(repo, developBranch.target())); 53 | }) 54 | .then((fromCommit) => repo.createBranch(featureBranchName, fromCommit)) 55 | .then((_featureBranch) => { 56 | featureBranch = _featureBranch; 57 | return repo.head() 58 | .then((headRef) => { 59 | return repo.checkoutBranch(featureBranch) 60 | .then(() => repo.head()) 61 | .then(newHeadRef => postCheckoutHook( 62 | headRef.target().toString(), 63 | newHeadRef.target().toString() 64 | )); 65 | }) 66 | }) 67 | .then(() => featureBranch); 68 | } 69 | 70 | /** 71 | * Finishes a git flow "feature" 72 | * @async 73 | * @param {Object} repo The repository to finish a feature in 74 | * @param {String} featureName The name of the feature to finish 75 | * @param {Object} options Options for finish feature 76 | * @return {Commit} The commit created by finishing the feature 77 | */ 78 | static finishFeature(repo, featureName, options = {}) { 79 | const { 80 | keepBranch, 81 | isRebase, 82 | preRebaseCallback = () => {}, 83 | beforeMergeCallback = () => {}, 84 | processMergeMessageCallback, 85 | postMergeCallback = () => {}, 86 | postCheckoutHook = () => {}, 87 | beforeRebaseFinishCallback = () => {}, 88 | signingCallback 89 | } = options; 90 | 91 | if (!repo) { 92 | return Promise.reject(new Error('Repo is required')); 93 | } 94 | 95 | if (!featureName) { 96 | return Promise.reject(new Error('Feature name is required')); 97 | } 98 | 99 | let developBranch; 100 | let featureBranch; 101 | let developCommit; 102 | let featureCommit; 103 | let cancelDevelopMerge; 104 | let mergeCommit; 105 | let developBranchName; 106 | let featureBranchName; 107 | return Config.getConfig(repo) 108 | .then((config) => { 109 | developBranchName = config['gitflow.branch.develop']; 110 | featureBranchName = config['gitflow.prefix.feature'] + featureName; 111 | 112 | return Promise.all( 113 | [developBranchName, featureBranchName] 114 | .map((branchName) => NodeGit.Branch.lookup(repo, branchName, NodeGit.Branch.BRANCH.LOCAL)) 115 | ); 116 | }) 117 | .then((branches) => { 118 | developBranch = branches[0]; 119 | featureBranch = branches[1]; 120 | 121 | return Promise.all(branches.map((branch) => repo.getCommit(branch.target()))); 122 | }) 123 | .then((commits) => { 124 | developCommit = commits[0]; 125 | featureCommit = commits[1]; 126 | 127 | // If the develop branch and feautre branch point to the same thing do not merge them 128 | // or if the `isRebase` parameter is true do not merge 129 | const isSameCommit = developCommit.id().toString() === featureCommit.id().toString(); 130 | cancelDevelopMerge = isSameCommit || isRebase; 131 | 132 | if (!cancelDevelopMerge) { 133 | return Promise.resolve(beforeMergeCallback(developBranchName, featureBranchName)) 134 | .then(() => utils.Repo.merge(developBranch, featureBranch, repo, processMergeMessageCallback, signingCallback)) 135 | .then(utils.InjectIntermediateCallback(postMergeCallback)); 136 | } else if (isRebase && !isSameCommit) { 137 | return Promise.resolve(preRebaseCallback(developBranchName, featureBranchName)) 138 | .then(() => utils.Repo.rebase(developBranch, featureBranch, repo, beforeRebaseFinishCallback, signingCallback)); 139 | } 140 | return Promise.resolve(); 141 | }) 142 | .then((_mergeCommit) => { 143 | mergeCommit = _mergeCommit; 144 | if (keepBranch) { 145 | return Promise.resolve(); 146 | } 147 | 148 | return utils.Repo.safelyDeleteBranch(repo, featureBranchName, developBranchName, postCheckoutHook); 149 | }) 150 | .then(() => mergeCommit); 151 | } 152 | 153 | /** 154 | * Starts a git flow "feature" 155 | * @async 156 | * @param {String} featureName The name of the feature to start 157 | * @param {Object} options Options for start feature 158 | * @return {Branch} The nodegit branch for the feature 159 | */ 160 | startFeature() { 161 | return Feature.startFeature(this.repo, ...arguments); 162 | } 163 | 164 | /** 165 | * Finishes a git flow "feature" 166 | * @async 167 | * @param {String} featureName The name of the feature to finish 168 | * @param {Object} options Options for finish feature 169 | * @return {Commit} The commit created by finishing the feature 170 | */ 171 | finishFeature() { 172 | return Feature.finishFeature(this.repo, ...arguments); 173 | } 174 | } 175 | 176 | return Feature; 177 | }; 178 | -------------------------------------------------------------------------------- /src/Hotfix.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, { constants, utils }, { Config }) => { 2 | /** 3 | * All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | * @class 5 | */ 6 | class Hotfix { 7 | constructor(repo) { 8 | this.repo = repo; 9 | } 10 | 11 | /** 12 | * Starts a git flow "hotfix" 13 | * @async 14 | * @param {Object} repo The repository to start a hotfix in 15 | * @param {String} hotfixVersion The version of the hotfix to start 16 | * @param {Object} options Options for start hotfix 17 | * @return {Branch} The nodegit branch for the hotfix 18 | */ 19 | static startHotfix(repo, hotfixVersion, options = {}) { 20 | const { 21 | postCheckoutHook = () => {} 22 | } = options; 23 | if (!repo) { 24 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 25 | } 26 | 27 | if (!hotfixVersion) { 28 | return Promise.reject(new Error('Hotfix version is required')); 29 | } 30 | 31 | let hotfixBranchName; 32 | let hotfixBranch; 33 | 34 | return Config.getConfig(repo) 35 | .then((config) => { 36 | const hotfixPrefix = config['gitflow.prefix.hotfix']; 37 | const masterBranchName = config['gitflow.branch.master']; 38 | hotfixBranchName = hotfixPrefix + hotfixVersion; 39 | 40 | return NodeGit.Branch.lookup( 41 | repo, 42 | masterBranchName, 43 | NodeGit.Branch.BRANCH.LOCAL 44 | ); 45 | }) 46 | .then((masterBranch) => NodeGit.Commit.lookup(repo, masterBranch.target())) 47 | .then((localMasterCommit) => repo.createBranch(hotfixBranchName, localMasterCommit)) 48 | .then((_hotfixBranch) => { 49 | hotfixBranch = _hotfixBranch; 50 | return repo.head() 51 | .then((headRef) => { 52 | return repo.checkoutBranch(hotfixBranch) 53 | .then(() => repo.head()) 54 | .then(newHeadRef => postCheckoutHook( 55 | headRef.target().toString(), 56 | newHeadRef.target().toString() 57 | )); 58 | }) 59 | }) 60 | .then(() => hotfixBranch); 61 | } 62 | 63 | /** 64 | * Finishes a git flow "hotfix" 65 | * @async 66 | * @param {Object} repo The repository to finish a hotfix in 67 | * @param {String} hotfixVersion The version of the hotfix to finish 68 | * @param {Object} options Options for finish hotfix 69 | * @return {Commit} The commit created by finishing the hotfix 70 | */ 71 | static finishHotfix(repo, hotfixVersion, options = {}) { 72 | const { 73 | keepBranch, 74 | message, 75 | processMergeMessageCallback, 76 | beforeMergeCallback = () => {}, 77 | postDevelopMergeCallback = () => {}, 78 | postMasterMergeCallback = () => {}, 79 | postCheckoutHook = () => {}, 80 | signingCallback, 81 | onlyMaster = false, 82 | } = options; 83 | 84 | if (!repo) { 85 | return Promise.reject(new Error('Repo is required')); 86 | } 87 | 88 | if (!hotfixVersion) { 89 | return Promise.reject(new Error('Hotfix name is required')); 90 | } 91 | 92 | let developBranchName; 93 | let hotfixBranchName; 94 | let masterBranchName; 95 | let cancelMasterMerge; 96 | let cancelDevelopMerge; 97 | let developBranch; 98 | let hotfixBranch; 99 | let masterBranch; 100 | let developCommit; 101 | let hotfixCommit; 102 | let masterCommit; 103 | let mergeCommit; 104 | let versionPrefix; 105 | return Config.getConfig(repo) 106 | .then((config) => { 107 | developBranchName = config['gitflow.branch.develop']; 108 | hotfixBranchName = config['gitflow.prefix.hotfix'] + hotfixVersion; 109 | masterBranchName = config['gitflow.branch.master']; 110 | versionPrefix = config['gitflow.prefix.versiontag']; 111 | 112 | // Get the develop, master, and hotfix branch 113 | return Promise.all( 114 | [developBranchName, hotfixBranchName, masterBranchName] 115 | .map((branchName) => NodeGit.Branch.lookup(repo, branchName, NodeGit.Branch.BRANCH.LOCAL)) 116 | ); 117 | }) 118 | .then((branches) => { 119 | developBranch = branches[0]; 120 | hotfixBranch = branches[1]; 121 | masterBranch = branches[2]; 122 | 123 | // Get the commits that the develop, master, and hotfix branches point to 124 | return Promise.all(branches.map((branch) => repo.getCommit(branch.target()))); 125 | }) 126 | .then((commits) => { 127 | developCommit = commits[0]; 128 | hotfixCommit = commits[1]; 129 | masterCommit = commits[2]; 130 | 131 | // If either develop or master point to the same commit as the hotfix branch cancel 132 | // their respective merge 133 | cancelDevelopMerge = onlyMaster || developCommit.id().toString() === hotfixCommit.id().toString(); 134 | cancelMasterMerge = masterCommit.id().toString() === hotfixCommit.id().toString(); 135 | 136 | // Merge hotfix into develop 137 | if (!cancelDevelopMerge) { 138 | return Promise.resolve(beforeMergeCallback(developBranchName, hotfixBranchName)) 139 | .then(() => utils.Repo.merge(developBranch, hotfixBranch, repo, processMergeMessageCallback, signingCallback)) 140 | .then(utils.InjectIntermediateCallback(postDevelopMergeCallback)); 141 | } 142 | return Promise.resolve(); 143 | }) 144 | .then((_mergeCommit) => { 145 | mergeCommit = _mergeCommit; 146 | 147 | const tagName = versionPrefix + hotfixVersion; 148 | // Merge the hotfix branch into master 149 | if (!cancelMasterMerge) { 150 | return Promise.resolve(beforeMergeCallback(masterBranchName, hotfixBranchName)) 151 | .then(() => utils.Repo.merge(masterBranch, hotfixBranch, repo, processMergeMessageCallback, signingCallback)) 152 | .then(utils.InjectIntermediateCallback(postMasterMergeCallback)) 153 | .then((oid) => utils.Tag.create(oid, tagName, message, repo)); 154 | } 155 | 156 | // If the merge is cancelled only tag the master commit 157 | const masterOid = NodeGit.Oid.fromString(masterCommit.id().toString()); 158 | return utils.Tag.create(masterOid, tagName, message, repo); 159 | }) 160 | .then(() => { 161 | if (keepBranch) { 162 | return Promise.resolve(); 163 | } 164 | 165 | return utils.Repo.safelyDeleteBranch(repo, hotfixBranchName, masterBranchName, postCheckoutHook); 166 | }) 167 | .then(() => mergeCommit); 168 | } 169 | 170 | /** 171 | * Starts a git flow "hotfix" 172 | * @async 173 | * @param {String} hotfixVersion The version of the hotfix to start 174 | * @param {Object} options Options for start hotfix 175 | * @return {Branch} The nodegit branch for the hotfix 176 | */ 177 | startHotfix() { 178 | return Hotfix.startHotfix(this.repo, ...arguments); 179 | } 180 | 181 | /** 182 | * Finishes a git flow "hotfix" 183 | * @async 184 | * @param {String} hotfixVersion The version of the hotfix to finish 185 | * @param {Object} options Options for finish hotfix 186 | * @return {Commit} The commit created by finishing the hotfix 187 | */ 188 | finishHotfix() { 189 | return Hotfix.finishHotfix(this.repo, ...arguments); 190 | } 191 | } 192 | 193 | return Hotfix; 194 | }; 195 | -------------------------------------------------------------------------------- /src/Release.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, { constants, utils }, { Config }) => { 2 | /** 3 | * All of this class' functions are attached to `NodeGit.Flow` or a `Flow` instance object 4 | * @class 5 | */ 6 | class Release { 7 | constructor(repo) { 8 | this.repo = repo; 9 | } 10 | 11 | /** 12 | * Starts a git flow "release" 13 | * @async 14 | * @param {Object} repo The repository to start a release in 15 | * @param {String} releaseVersion The version of the release to start 16 | * @param {Object} options Options for start release 17 | * @return {Branch} The nodegit branch for the release 18 | */ 19 | static startRelease(repo, releaseVersion, options = {}) { 20 | const { 21 | postCheckoutHook = () => {}, 22 | sha 23 | } = options; 24 | 25 | if (!repo) { 26 | return Promise.reject(new Error(constants.ErrorMessage.REPO_REQUIRED)); 27 | } 28 | 29 | if (!releaseVersion) { 30 | return Promise.reject(new Error('Release version is required')); 31 | } 32 | 33 | let releaseBranchName; 34 | let releaseBranch; 35 | 36 | return Config.getConfig(repo) 37 | .then((config) => { 38 | const releasePrefix = config['gitflow.prefix.release']; 39 | const developBranchName = config['gitflow.branch.develop']; 40 | releaseBranchName = releasePrefix + releaseVersion; 41 | 42 | // If we have a sha look that up instead of the develop branch 43 | if (sha) { 44 | return NodeGit.Commit.lookup(repo, sha); 45 | } 46 | 47 | return NodeGit.Branch.lookup( 48 | repo, 49 | developBranchName, 50 | NodeGit.Branch.BRANCH.LOCAL 51 | ) 52 | .then((developBranch) => NodeGit.Commit.lookup(repo, developBranch.target())); 53 | }) 54 | .then((startingCommit) => repo.createBranch(releaseBranchName, startingCommit)) 55 | .then((_releaseBranch) => { 56 | releaseBranch = _releaseBranch; 57 | return repo.head() 58 | .then((headRef) => { 59 | return repo.checkoutBranch(releaseBranch) 60 | .then(() => repo.head()) 61 | .then(newHeadRef => postCheckoutHook( 62 | headRef.target().toString(), 63 | newHeadRef.target().toString() 64 | )); 65 | }) 66 | }) 67 | .then(() => releaseBranch); 68 | } 69 | 70 | /** 71 | * Finishes a git flow "release" 72 | * @async 73 | * @param {Object} repo The repository to finish a release in 74 | * @param {String} releaseVersion The version of the release to finish 75 | * @param {Object} options Options for finish release 76 | * @return {Commit} The commit created by finishing the release 77 | */ 78 | static finishRelease(repo, releaseVersion, options = {}) { 79 | const { 80 | keepBranch, 81 | message, 82 | processMergeMessageCallback, 83 | beforeMergeCallback = () => {}, 84 | postDevelopMergeCallback = () => {}, 85 | postMasterMergeCallback = () => {}, 86 | postCheckoutHook = () => {}, 87 | signingCallback, 88 | onlyMaster = false, 89 | } = options; 90 | 91 | if (!repo) { 92 | return Promise.reject(new Error('Repo is required')); 93 | } 94 | 95 | if (!releaseVersion) { 96 | return Promise.reject(new Error('Release name is required')); 97 | } 98 | 99 | let developBranchName; 100 | let releaseBranchName; 101 | let masterBranchName; 102 | let developBranch; 103 | let releaseBranch; 104 | let masterBranch; 105 | let cancelDevelopMerge; 106 | let cancelMasterMerge; 107 | let developCommit; 108 | let releaseCommit; 109 | let masterCommit; 110 | let mergeCommit; 111 | let versionPrefix; 112 | return Config.getConfig(repo) 113 | .then((config) => { 114 | developBranchName = config['gitflow.branch.develop']; 115 | releaseBranchName = config['gitflow.prefix.release'] + releaseVersion; 116 | masterBranchName = config['gitflow.branch.master']; 117 | versionPrefix = config['gitflow.prefix.versiontag']; 118 | 119 | // Get the develop, master, and release branch 120 | return Promise.all( 121 | [developBranchName, releaseBranchName, masterBranchName] 122 | .map((branchName) => NodeGit.Branch.lookup(repo, branchName, NodeGit.Branch.BRANCH.LOCAL)) 123 | ); 124 | }) 125 | .then((branches) => { 126 | developBranch = branches[0]; 127 | releaseBranch = branches[1]; 128 | masterBranch = branches[2]; 129 | 130 | // Get the commits that the develop, master, and release branches point to 131 | return Promise.all(branches.map((branch) => repo.getCommit(branch.target()))); 132 | }) 133 | .then((commits) => { 134 | developCommit = commits[0]; 135 | releaseCommit = commits[1]; 136 | masterCommit = commits[2]; 137 | 138 | // If either develop or master point to the same commit as the release branch cancel 139 | // their respective merge 140 | cancelDevelopMerge = onlyMaster || developCommit.id().toString() === releaseCommit.id().toString(); 141 | cancelMasterMerge = masterCommit.id().toString() === releaseCommit.id().toString(); 142 | 143 | // Merge release into develop 144 | if (!cancelDevelopMerge) { 145 | return Promise.resolve(beforeMergeCallback(developBranchName, releaseBranchName)) 146 | .then(() => utils.Repo.merge(developBranch, releaseBranch, repo, processMergeMessageCallback, signingCallback)) 147 | .then(utils.InjectIntermediateCallback(postDevelopMergeCallback)); 148 | } 149 | return Promise.resolve(); 150 | }) 151 | .then((_mergeCommit) => { 152 | mergeCommit = _mergeCommit; 153 | 154 | const tagName = versionPrefix + releaseVersion; 155 | // Merge the release branch into master 156 | if (!cancelMasterMerge) { 157 | return Promise.resolve(beforeMergeCallback(masterBranchName, releaseBranchName)) 158 | .then(() => utils.Repo.merge(masterBranch, releaseBranch, repo, processMergeMessageCallback, signingCallback)) 159 | .then(utils.InjectIntermediateCallback(postMasterMergeCallback)) 160 | .then((oid) => utils.Tag.create(oid, tagName, message, repo)); 161 | } 162 | 163 | const masterOid = NodeGit.Oid.fromString(masterCommit.id().toString()); 164 | return utils.Tag.create(masterOid, tagName, message, repo); 165 | }) 166 | .then(() => { 167 | if (keepBranch) { 168 | return Promise.resolve(); 169 | } 170 | 171 | return utils.Repo.safelyDeleteBranch(repo, releaseBranchName, masterBranchName, postCheckoutHook); 172 | }) 173 | .then(() => mergeCommit); 174 | } 175 | 176 | /** 177 | * Starts a git flow "release" 178 | * @async 179 | * @param {String} releaseVersion The version of the release to start 180 | * @param {Object} options Options for start release 181 | * @return {Branch} The nodegit branch for the release 182 | */ 183 | startRelease() { 184 | return Release.startRelease(this.repo, ...arguments); 185 | } 186 | 187 | /** 188 | * Finishes a git flow "release" 189 | * @async 190 | * @param {String} releaseVersion The version of the release to finish 191 | * @param {Object} options Options for finish release 192 | * @return {Commit} The commit created by finishing the release 193 | */ 194 | finishRelease() { 195 | return Release.finishRelease(this.repo, ...arguments); 196 | } 197 | } 198 | 199 | return Release; 200 | }; 201 | -------------------------------------------------------------------------------- /src/constants/BranchConstants.js: -------------------------------------------------------------------------------- 1 | const BranchConstants = { 2 | mergeState: { 3 | UP_TO_DATE: 'up_to_date', 4 | FAST_FORWARD: 'fast_forward', 5 | NORMAL: 'normal' 6 | } 7 | }; 8 | 9 | module.exports = BranchConstants; 10 | -------------------------------------------------------------------------------- /src/constants/ErrorMessageConstants.js: -------------------------------------------------------------------------------- 1 | const ErrorMessageConstants = { 2 | GIT_FLOW_NOT_INITIALIZED: 'Git flow has not been initialized on this repo', 3 | REPO_REQUIRED: 'A repository is required' 4 | }; 5 | 6 | module.exports = ErrorMessageConstants; 7 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | const BranchConstants = require('./BranchConstants'); 2 | const ErrorMessageConstants = require('./ErrorMessageConstants'); 3 | 4 | module.exports = { 5 | Branch: BranchConstants, 6 | ErrorMessage: ErrorMessageConstants 7 | }; 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const constants = require('./constants'); 2 | const makeUtils = require('./utils'); 3 | const makeBase = require('./Base'); 4 | const makeConfig = require('./Config'); 5 | const makeFeature = require('./Feature'); 6 | const makeHotfix = require('./Hotfix'); 7 | const makeRelease = require('./Release'); 8 | 9 | module.exports = (NodeGit) => { 10 | if (NodeGit.Flow) { 11 | return NodeGit; 12 | } 13 | 14 | const utils = makeUtils(NodeGit); 15 | const Config = makeConfig(NodeGit, { constants }); 16 | const Feature = makeFeature(NodeGit, { constants, utils }, { Config }); 17 | const Hotfix = makeHotfix(NodeGit, { constants, utils }, { Config }); 18 | const Release = makeRelease(NodeGit, { constants, utils }, { Config }); 19 | const Base = makeBase(NodeGit, { constants }, { Config, Feature, Hotfix, Release }); 20 | 21 | const GitFlowClasses = [Base, Config, Feature, Hotfix, Release]; 22 | // Add static Flow methods to provided nodegit instance 23 | NodeGit.Flow = {}; 24 | GitFlowClasses.forEach((GitFlowClass) => { 25 | utils.Assign(NodeGit.Flow, GitFlowClass); 26 | }); 27 | 28 | if (process.env.NODEGIT_FLOW_TESTING_ENABLED) { 29 | NodeGit.Flow.__TEST__ = { 30 | Base, 31 | Config, 32 | Feature, 33 | Hotfix, 34 | Release, 35 | utils 36 | }; 37 | } 38 | 39 | return NodeGit; 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/AssignUtils.js: -------------------------------------------------------------------------------- 1 | const noOpProperties = [ 2 | 'arguments', 3 | 'caller', 4 | 'callee' 5 | ]; 6 | 7 | module.exports = function(to, from) { 8 | Object.getOwnPropertyNames(from).forEach((key) => { 9 | if (!~noOpProperties.indexOf(key) && typeof from[key] === 'function') { 10 | to[key] = from[key]; 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/InjectIntermediateCallbackUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | (callback) => (a) => Promise.resolve(callback()).then(() => a); 3 | -------------------------------------------------------------------------------- /src/utils/MergeUtils.js: -------------------------------------------------------------------------------- 1 | const MergeUtils = { 2 | getMergeMessage(toBranch, fromBranch) { 3 | let mergeDecorator; 4 | if (fromBranch.isTag()) { 5 | mergeDecorator = 'tag'; 6 | } else if (fromBranch.isRemote()) { 7 | mergeDecorator = 'remote-tracking branch'; 8 | } else { 9 | mergeDecorator = 'branch'; 10 | } 11 | 12 | const message = `Merge ${mergeDecorator} '${fromBranch.shorthand()}'`; 13 | 14 | // https://github.com/git/git/blob/master/builtin/fmt-merge-msg.c#L456-L459 15 | return toBranch.shorthand() !== 'master' 16 | ? `${message} into ${toBranch.shorthand()}` 17 | : message; 18 | } 19 | }; 20 | 21 | module.exports = MergeUtils; 22 | -------------------------------------------------------------------------------- /src/utils/Promisify.js: -------------------------------------------------------------------------------- 1 | const promisify = function promisify(fn) { 2 | return function() { 3 | let resolve; 4 | let reject; 5 | const promise = new Promise((_resolve, _reject) => { 6 | resolve = _resolve; 7 | reject = _reject; 8 | }); 9 | 10 | fn(...arguments, (err, res) => { 11 | if (err) { 12 | reject(err); 13 | } else { 14 | resolve(res); 15 | } 16 | }); 17 | return promise; 18 | }; 19 | }; 20 | 21 | module.exports = promisify; 22 | -------------------------------------------------------------------------------- /src/utils/RepoUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit, MergeUtils) => ({ 2 | safelyDeleteBranch(repo, branchName, branchNameToCheckoutIfUnsafe, postCheckoutHook = () => {}) { 3 | return repo.head() 4 | .then((headRef) => { 5 | if (branchName !== headRef.shorthand()) { 6 | return; 7 | } 8 | 9 | return repo.checkoutBranch(branchNameToCheckoutIfUnsafe) 10 | .then(() => repo.head()) 11 | .then(newHeadRef => postCheckoutHook( 12 | headRef.target().toString(), 13 | newHeadRef.target().toString() 14 | )); 15 | }) 16 | .then(() => NodeGit.Reference.remove(repo, `refs/heads/${branchName}`)); 17 | }, 18 | 19 | merge(toBranch, fromBranch, repo, processMergeMessageCallback = a => a, signingCallback) { 20 | return Promise.resolve() 21 | .then(() => { 22 | if (!toBranch.isHead()) { 23 | return repo.checkoutBranch(toBranch); 24 | } 25 | return Promise.resolve(); 26 | }) 27 | .then(() => NodeGit.AnnotatedCommit.fromRef(repo, fromBranch)) 28 | .then((fromCommit) => { 29 | const checkoutOpts = { 30 | checkoutStrategy: NodeGit.Checkout.STRATEGY.SAFE | NodeGit.Checkout.STRATEGY.RECREATE_MISSING 31 | }; 32 | return NodeGit.Merge.merge(repo, fromCommit, null, checkoutOpts); 33 | }) 34 | .then(() => repo.index()) 35 | .then((index) => { 36 | if (index.hasConflicts && index.hasConflicts()) { 37 | return Promise.reject(index); 38 | } 39 | 40 | return index.writeTree(); 41 | }) 42 | .then((treeOid) => Promise.all([ 43 | treeOid, 44 | processMergeMessageCallback(MergeUtils.getMergeMessage(toBranch, fromBranch)), 45 | repo.defaultSignature(), 46 | repo.getHeadCommit(), 47 | repo.getBranchCommit('MERGE_HEAD') 48 | ])) 49 | .then(([treeOid, message, signature, ...commits]) => signingCallback 50 | ? repo.createCommitWithSignature('HEAD', signature, signature, message, treeOid, commits, signingCallback) 51 | : repo.createCommit('HEAD', signature, signature, message, treeOid, commits) 52 | ) 53 | .then((commitId) => { 54 | repo.stateCleanup(); 55 | 56 | return commitId; 57 | }); 58 | }, 59 | 60 | rebase(toBranch, fromBranch, repo, beforeRebaseFinish, signingCallback) { 61 | return repo.rebaseBranches( 62 | fromBranch.name(), 63 | toBranch.name(), 64 | undefined, 65 | undefined, 66 | undefined, 67 | beforeRebaseFinish, 68 | { 69 | signingCb: signingCallback 70 | } 71 | ) 72 | .then((result) => { 73 | if (result.hasConflicts && result.hasConflicts()) { 74 | return Promise.reject(result); 75 | } 76 | 77 | return toBranch.setTarget(result, ''); 78 | }); 79 | } 80 | }); 81 | -------------------------------------------------------------------------------- /src/utils/TagUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = (NodeGit) => ({ 2 | create(oid, tagName, tagMessage = '', repo) { 3 | return Promise.all([ 4 | NodeGit.Commit.lookup(repo, oid), 5 | repo.defaultSignature() 6 | ]) 7 | .then(([commit, ourSignature]) => 8 | NodeGit.Tag.create(repo, tagName, commit, ourSignature, tagMessage, 0) 9 | ); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | const AssignUtils = require('./AssignUtils'); 2 | const InjectIntermediateCallbackUtils = require('./InjectIntermediateCallbackUtils'); 3 | const MergeUtils = require('./MergeUtils'); 4 | const RepoUtils = require('./RepoUtils'); 5 | const TagUtils = require('./TagUtils'); 6 | 7 | module.exports = (NodeGit) => ({ 8 | Assign: AssignUtils, 9 | InjectIntermediateCallback: InjectIntermediateCallbackUtils, 10 | Merge: MergeUtils, 11 | Repo: RepoUtils(NodeGit, MergeUtils), 12 | Tag: TagUtils(NodeGit) 13 | }); 14 | --------------------------------------------------------------------------------