forked from breser/git2consul
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbranch.js
More file actions
247 lines (202 loc) · 8.57 KB
/
branch.js
File metadata and controls
247 lines (202 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var logger = require('../logging.js');
var consul_broker = require('../consul');
var git_commands = require('./commands.js');
function Branch(repo_config, name) {
// Immutable properties
Object.defineProperty(this, 'url', {value: repo_config.url});
Object.defineProperty(this, 'repo_name', {value: repo_config.name });
Object.defineProperty(this, 'name', {value: name });
Object.defineProperty(this, 'branch_parent', {value: repo_config.local_store + path.sep + repo_config.name});
Object.defineProperty(this, 'branch_directory', {value: this.branch_parent + path.sep + name});
Object.defineProperty(this, 'expand_keys', { value: repo_config['expand_keys'] === true });
Object.defineProperty(this, 'common_properties', { value: repo_config['common_properties']});
Object.defineProperty(this, 'include_branch_name', {
// If include_branch_name is not set, assume true. Otherwise, identity check the value against true.
value: repo_config['include_branch_name'] == undefined || repo_config['include_branch_name'] === true
});
Object.defineProperty(this, 'mountpoint', {value: repo_config['mountpoint'] });
if (repo_config['source_root'] &&
repo_config.source_root.length > 0 &&
(repo_config.source_root.charAt(repo_config.source_root.length - 1) == '/')) {
repo_config.source_root = repo_config.source_root.substring(0, repo_config.source_root.length - 1);
}
Object.defineProperty(this, 'source_root', {value: repo_config['source_root'] });
// Writable properties
Object.defineProperty(this, 'busy', {value: false, writable: true});
Object.defineProperty(this, 'update_in_progress', {value: false, writable: true});
Object.defineProperty(this, 'pending_ref_change', {value: undefined, writable: true});
Object.defineProperty(this, 'pending_callbacks', {value: [], writable: true});
}
Branch.prototype.clone = function(cb) {
var this_obj = this;
this_obj.busy = true;
git_commands.clone(this_obj.url, this_obj.name, this_obj.branch_parent, function(err, output) {
/* istanbul ignore next */
if (err) {
this_obj.busy = false;
return cb(err);
}
consul_broker.handleRefChange(this_obj, cb);
});
};
/**
* Delete the local copy of this branch. If the branch has been corrupted, this is the cleanest way
* to reset things.
*/
Branch.prototype.purgeBranchCache = function(cb) {
var this_obj = this;
logger.warn("Purging branch cache %s for branch %s in repo %s", this.branch_directory, this.name, this.repo_name);
rimraf(this.branch_directory, function(err) {
/* istanbul ignore if */
if (err) {
logger.error("Failed to purge branch cache %s", this_obj.branch_directory);
return cb(err);
}
cb();
});
};
/**
* Fetch the branch up to a given commit. After this operation, the local copy should be up to date
* with the branch at that commit.
*/
Branch.prototype.pull = function(cb) {
var this_obj = this;
this_obj.busy = true;
git_commands.pull(this_obj.name, this_obj.branch_directory, function(err, output) {
/* istanbul ignore if */
if (err) {
this_obj.busy = false;
return cb(err);
}
consul_broker.handleRefChange(this_obj, cb);
});
};
Branch.prototype.listChangedFiles = function(from_ref, to_ref, cb) {
git_commands.listChangedFiles(from_ref, to_ref, this.branch_directory, cb);
};
Branch.prototype.listAllFiles = function(cb) {
git_commands.listAllFiles(this.branch_directory, cb);
};
Branch.prototype.getCurrentRef = function(cb) {
git_commands.getCurrentRef(this.branch_directory, cb);
};
Branch.prototype.handleRefChange = function(target_ref, cb) {
var this_obj = this;
// If an update is pending
if (this.update_in_progress) {
this.pending_ref_change = target_ref;
this.pending_callbacks.push(cb);
return;
}
// Once we wrap this callback, we are signalling that an update is in progress.
this_obj.update_in_progress = true;
this_obj.busy = true;
// We want to lock handleRefChange so that only one is happening per branch_manager at a time. This
// function allows us to manage that locking behavior and will re-run handleRefChange if one or more are
// queued up during execution of handleRefChange.
this.pull(function(err, output) {
// We call this function to update the repo if the pull was successful. If the pull wasn't succesful,
// we purge the local state and try the pull again.
var handle_successful_pull = function() {
// Callback seen. Update no longer in progress.
this_obj.update_in_progress = false;
// Grab a local copy of pending callbacks and reset the array.
var my_pending_callbacks = this_obj.pending_callbacks;
this_obj.pending_callbacks = [];
// If a request to handle a ref_change came in while we were processing a prior ref_change, run
// handleRefChange again.
if (this_obj.pending_ref_change) {
this_obj.handleRefChange(this_obj.pending_ref_change, function(err) {
my_pending_callbacks.forEach(function(pending_callback) {
pending_callback(err);
});
});
// Reset the pending_refchange because it's been copied onto the callstack of handleRefChange.
this_obj.pending_ref_change = undefined;
}
this_obj.busy = false;
// Pass values to the original callback
cb(null, output);
};
// In the case of a failed pull, we want to reset the update and busy bits before firing the callback.
var handle_failed_pull = function(err) {
this_obj.update_in_progress = false;
this_obj.busy = false;
cb(err);
};
// If the pull failed, assume the local copy of the branch is hosed. Purge and try again.
if (err) {
logger.warn("Failed to handle pull due to %s, clearing cache and retrying", err);
return this_obj.startFromScratch(err, handle_successful_pull, handle_failed_pull);
}
handle_successful_pull();
});
};
Branch.prototype.startFromScratch = function(err, success, failure) {
var this_obj = this;
// Wrap failure in a callback that will unset the busy flag.
var failure_cb = function(err) {
this_obj.busy = false;
failure(err);
};
this_obj.purgeBranchCache(function(purge_err) {
if (purge_err) {
logger.error("Cache purge failed. Failing pull.");
// The error passed into this method is most likely to explain what failed, so send this as the
// callback param.
return failure_cb(err);
}
// Reset the most recently processed ref: we don't know the state of the repo, so pretend
// we've never seen this repo before.
consul_broker.setLastProcessedRef(this_obj, null, function(set_last_processed_err) {
if (set_last_processed_err) return failure_cb(set_last_processed_err);
// If the branch isn't present on disk, create the branch_parent_directory (if necessary) and
// clone the branch from there.
mkdirp(this_obj.branch_parent, function(mkdir_err) {
/* istanbul ignore next */
if (mkdir_err) return failure_cb(mkdir_err);
// Clone the branch
this_obj.clone(function(clone_err) {
if (clone_err) return failure_cb(clone_err);
logger.info("Initialized branch %s from %s", this_obj.name, this_obj.repo_name);
this_obj.busy = false;
success();
});
});
});
});
};
Branch.prototype.init = function(cb) {
var this_obj = this;
logger.info("Initting branch %s %s", this_obj.branch_parent, this_obj.branch_directory);
this_obj.busy = true;
// Check to see if the branch directory is already present.
fs.stat(this_obj.branch_directory, function(err, stat) {
if (stat) {
// This branch was already cloned. Do a pull to make sure it's up to date.
return this_obj.getCurrentRef(function(err, ref) {
if (err) {
// If we have a failure on getCurrentRef, we suspect that the local copy is hosed. Purge it
// and start from scratch.
return this_obj.startFromScratch(null, cb, cb);
}
this_obj.pull(function(err) {
// If we have a failure on pull, we suspect that the local copy is hosed. Purge it
// and start from scratch.
return this_obj.startFromScratch(null, cb, cb);
this_obj.busy = false;
logger.info("Initialized branch %s from %s", this_obj.name, this_obj.repo_name);
cb();
});
});
} else {
// If the repo does not yet exist, start from scratch.
this_obj.startFromScratch(null, cb, cb);
}
});
};
module.exports = Branch;