UNPKG

5.94 kBJavaScriptView Raw
1var cp = require('child_process');
2var path = require('path');
3var util = require('util');
4
5var Q = require('q');
6var fs = require('q-io/fs');
7
8var git = 'git';
9
10
11
12/**
13 * @constructor
14 * @param {number} code Error code.
15 * @param {string} message Error message.
16 */
17function ProcessError(code, message) {
18 var callee = arguments.callee;
19 Error.apply(this, [message]);
20 Error.captureStackTrace(this, callee);
21 this.code = code;
22 this.message = message;
23 this.name = callee.name;
24}
25util.inherits(ProcessError, Error);
26
27
28/**
29 * Execute a git command.
30 * @param {Array.<string>} args Arguments (e.g. ['remote', 'update']).
31 * @param {string} cwd Repository directory.
32 * @return {Promise} A promise. The promise will be resolved with the exit code
33 * or rejected with an error. To get stdout, use a progress listener (e.g.
34 * `promise.progress(function(chunk) {console.log(String(chunk);}))`).
35 */
36exports = module.exports = function(args, cwd) {
37 return spawn(git, args, cwd);
38};
39
40
41/**
42 * Set the Git executable to be used by exported methods (defaults to 'git').
43 * @param {string} exe Git executable (full path if not already on path).
44 */
45exports.exe = function(exe) {
46 git = exe;
47};
48
49
50/**
51 * Util function for handling spawned processes as promises.
52 * @param {string} exe Executable.
53 * @param {Array.<string>} args Arguments.
54 * @param {string} cwd Working directory.
55 * @return {Promise} A promise.
56 */
57function spawn(exe, args, cwd) {
58 var deferred = Q.defer();
59 var child = cp.spawn(exe, args, {cwd: cwd || process.cwd()});
60 var buffer = [];
61 child.stderr.on('data', function(chunk) {
62 buffer.push(chunk.toString());
63 });
64 child.stdout.on('data', function(chunk) {
65 deferred.notify(chunk);
66 });
67 child.on('close', function(code) {
68 if (code) {
69 var msg = buffer.join('') || 'Process failed: ' + code;
70 deferred.reject(new ProcessError(code, msg));
71 } else {
72 deferred.resolve(code);
73 }
74 });
75 return deferred.promise;
76}
77
78
79/**
80 * Initialize repository.
81 * @param {string} cwd Repository directory.
82 * @return {ChildProcess} Child process.
83 */
84exports.init = function init(cwd) {
85 return spawn(git, ['init'], cwd);
86};
87
88
89/**
90 * Clone a repo into the given dir if it doesn't already exist.
91 * @param {string} repo Repository URL.
92 * @param {string} dir Target directory.
93 * @return {Promise} A promise.
94 */
95exports.clone = function clone(repo, dir) {
96 return fs.exists(dir).then(function(exists) {
97 if (exists) {
98 return Q.resolve();
99 } else {
100 return fs.makeTree(path.dirname(dir)).then(function() {
101 return spawn(git, ['clone', repo, dir]);
102 });
103 }
104 });
105};
106
107
108/**
109 * Clean up unversioned files.
110 * @param {string} cwd Repository directory.
111 * @return {Promise} A promise.
112 */
113var clean = exports.clean = function clean(cwd) {
114 return spawn(git, ['clean', '-f', '-d'], cwd);
115};
116
117
118/**
119 * Hard reset to remote/branch
120 * @param {string} remote Remote alias.
121 * @param {string} branch Branch name.
122 * @param {string} cwd Repository directory.
123 * @return {Promise} A promise.
124 */
125var reset = exports.reset = function reset(remote, branch, cwd) {
126 return spawn(git, ['reset', '--hard', remote + '/' + branch], cwd);
127};
128
129
130/**
131 * Fetch from a remote.
132 * @param {string} remote Remote alias.
133 * @param {string} cwd Repository directory.
134 * @return {Promise} A promise.
135 */
136exports.fetch = function fetch(remote, cwd) {
137 return spawn(git, ['fetch', remote], cwd);
138};
139
140
141/**
142 * Checkout a branch (create an orphan if it doesn't exist on the remote).
143 * @param {string} remote Remote alias.
144 * @param {string} branch Branch name.
145 * @param {string} cwd Repository directory.
146 * @return {Promise} A promise.
147 */
148exports.checkout = function checkout(remote, branch, cwd) {
149 var treeish = remote + '/' + branch;
150 return spawn(git, ['ls-remote', '--exit-code', '.', treeish], cwd)
151 .then(function() {
152 // branch exists on remote, hard reset
153 return spawn(git, ['checkout', branch], cwd)
154 .then(function() {
155 return clean(cwd);
156 })
157 .then(function() {
158 return reset(remote, branch, cwd);
159 });
160 }, function(error) {
161 if (error instanceof ProcessError && error.code === 2) {
162 // branch doesn't exist, create an orphan
163 return spawn(git, ['checkout', '--orphan', branch], cwd);
164 } else {
165 // unhandled error
166 return Q.reject(error);
167 }
168 });
169};
170
171
172/**
173 * Remove all unversioned files.
174 * @param {string} files Files argument.
175 * @param {string} cwd Repository directory.
176 * @return {Promise} A promise.
177 */
178exports.rm = function rm(files, cwd) {
179 return spawn(git, ['rm', '--ignore-unmatch', '-r', '-f', files], cwd);
180};
181
182
183/**
184 * Add files.
185 * @param {string} files Files argument.
186 * @param {string} cwd Repository directory.
187 * @return {Promise} A promise.
188 */
189exports.add = function add(files, cwd) {
190 return spawn(git, ['add', files], cwd);
191};
192
193
194/**
195 * Commit.
196 * @param {string} message Commit message.
197 * @param {string} cwd Repository directory.
198 * @return {Promise} A promise.
199 */
200exports.commit = function commit(message, cwd) {
201 return spawn(git, ['diff-index', '--quiet', 'HEAD', '.'], cwd)
202 .then(function() {
203 // nothing to commit
204 return Q.resolve();
205 })
206 .fail(function() {
207 return spawn(git, ['commit', '-m', message], cwd);
208 });
209};
210
211
212/**
213 * Add tag
214 * @param {string} tag Name of tag.
215 * @param {string} cwd Repository directory.
216 * @return {Promise} A promise.
217 */
218exports.tag = function tag(tag, cwd) {
219 return spawn(git, ['tag', tag], cwd);
220};
221
222
223/**
224 * Push a branch.
225 * @param {string} remote Remote alias.
226 * @param {string} branch Branch name.
227 * @param {string} cwd Repository directory.
228 * @return {Promise} A promise.
229 */
230exports.push = function push(remote, branch, cwd) {
231 return spawn(git, ['push', '--tags', remote, branch], cwd);
232};