├── .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 |
--------------------------------------------------------------------------------