@atomist/automation-client
Version:
Atomist API for software low-level client
99 lines • 4.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
const path = require("path");
const metric_1 = require("../../internal/util/metric");
const logger_1 = require("../../util/logger");
const StableDirectoryManager_1 = require("./StableDirectoryManager");
const tmpDirectoryManager_1 = require("./tmpDirectoryManager");
const AtomistWorkingDirectory = path.join(".atomist", "cache");
const AbsoluteAtomistWorkingDirectory = path.join(os.homedir(), AtomistWorkingDirectory);
const cache = new StableDirectoryManager_1.StableDirectoryManager({
reuseDirectories: true,
baseDir: AbsoluteAtomistWorkingDirectory,
cleanOnExit: false,
});
/**
* Designed to accommodate occasional writes to the same repositories,
* this keeps one clone available for each repository. Every time that repository is requested,
* if that clone is available, we return it. (The caller gets to fetch, clean, etc. The directory could be dirty.)
* If that clone is locked by some other automation invocation, this
* DirectoryManager returns a temporary directory, and you get to clone into that.
*
* If the returned CloneDirectoryInfo has type: "empty-directory"
* then the caller should clone into it (not from it, you're not in the parent directory).
* If it has type: "existing-directory" then fetch, clean, checkout etc. given it's already cloned.
*
* @type {{directoryFor:
* ((owner: string, repo: string, branch: string, opts: CloneOptions) => Promise<CloneDirectoryInfo>)}}
*/
exports.CachingDirectoryManager = {
directoryFor(owner, repo, branch, opts) {
return cache.directoryFor(owner, repo, branch, opts).then(existing => pleaseLock(existing.path).then(lockResult => {
if (lockResult.success) {
incrementReuse(owner, repo);
return Object.assign(Object.assign({}, existing), { release: () => {
logger_1.logger.debug("Releasing lock on '%s'", existing.path);
return lockResult.release().then(existing.release);
}, invalidate: () => {
logger_1.logger.debug("Invalidating '%s'", existing.path);
return cache.invalidate(existing)
.then(() => {
logger_1.logger.debug("Invalidated. Now releasing lock");
return lockResult.release().then(existing.release);
});
}, provenance: (existing.provenance || "") + " successfully locked" });
}
else {
logger_1.logger.debug("Lock detected on '%s'", existing.path);
incrementFallback(owner, repo);
return tmpDirectoryManager_1.TmpDirectoryManager.directoryFor(owner, repo, branch, opts).then(cdi => (Object.assign(Object.assign({}, cdi), { provenance: `Tried '${existing.path}' but it was locked. ` + (cdi.provenance || "") })));
}
}));
},
};
exports.ReuseKey = "directory_cache.reuse";
exports.FallbackKey = "directory_cache.fallback";
function incrementReuse(owner, repo) {
metric_1.increment(`${exports.ReuseKey}.${keyFor(owner, repo)}`);
metric_1.increment(exports.ReuseKey);
}
function incrementFallback(owner, repo) {
metric_1.increment(`${exports.FallbackKey}.${keyFor(owner, repo)}`);
metric_1.increment(exports.FallbackKey);
}
function keyFor(owner, repo) {
return `${owner}/${repo}`;
}
/*
* file locking. only used here
*/
const lockfile = require("proper-lockfile");
function pleaseLock(lockPath) {
return new Promise((resolve, reject) => {
lockfile.lock(lockPath, (error, releaseCallback) => {
if (error) {
if (error.code === "ELOCKED") {
resolve({ success: false, error });
}
reject(error);
}
else {
// make the release function return a promise too. Its callback accepts a possible error.
const release = () => new Promise((releaseResolve, releaseReject) => {
releaseCallback(err => {
if (err) {
releaseReject(err);
}
else {
releaseResolve();
}
});
});
resolve({ success: true, release });
}
});
});
}
exports.pleaseLock = pleaseLock;
//# sourceMappingURL=CachingDirectoryManager.js.map