UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

99 lines 4.6 kB
"use strict"; 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