UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

1,063 lines (949 loc) 37.8 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ /** * * Section: HgRepositoryClient * */ /** * HgRepositoryClient runs on the machine that Nuclide/Atom is running on. * It is the interface that other Atom packages will use to access Mercurial. * It caches data fetched from an HgService. * It implements the same interface as GitRepository, (https://atom.io/docs/api/latest/GitRepository) * in addition to providing asynchronous methods for some getters. */ var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _atom2; function _atom() { return _atom2 = require('atom'); } var _HgRepositoryClientAsync2; function _HgRepositoryClientAsync() { return _HgRepositoryClientAsync2 = _interopRequireDefault(require('./HgRepositoryClientAsync')); } var _nuclideHgRpcLibHgConstants2; function _nuclideHgRpcLibHgConstants() { return _nuclideHgRpcLibHgConstants2 = require('../../nuclide-hg-rpc/lib/hg-constants'); } var _commonsNodePromise2; function _commonsNodePromise() { return _commonsNodePromise2 = require('../../commons-node/promise'); } var _commonsNodeDebounce2; function _commonsNodeDebounce() { return _commonsNodeDebounce2 = _interopRequireDefault(require('../../commons-node/debounce')); } var _commonsNodeNuclideUri2; function _commonsNodeNuclideUri() { return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri')); } var _utils2; function _utils() { return _utils2 = require('./utils'); } var STATUS_DEBOUNCE_DELAY_MS = 300; /** * * Section: Constants, Type Definitions * */ var DID_CHANGE_CONFLICT_STATE = 'did-change-conflict-state'; var EDITOR_SUBSCRIPTION_NAME = 'hg-repository-editor-subscription'; var MAX_INDIVIDUAL_CHANGED_PATHS = 1; exports.MAX_INDIVIDUAL_CHANGED_PATHS = MAX_INDIVIDUAL_CHANGED_PATHS; function filterForOnlyNotIgnored(code) { return code !== (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.IGNORED; } function filterForOnlyIgnored(code) { return code === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.IGNORED; } function filterForAllStatues() { return true; } var HgRepositoryClient = (function () { function HgRepositoryClient(repoPath, hgService, options) { var _this = this; _classCallCheck(this, HgRepositoryClient); this.async = new (_HgRepositoryClientAsync2 || _HgRepositoryClientAsync()).default(this); this._path = repoPath; this._workingDirectory = options.workingDirectory; this._projectDirectory = options.projectRootDirectory; this._originURL = options.originURL; this._service = hgService; this._isInConflict = false; this._emitter = new (_atom2 || _atom()).Emitter(); this._disposables = {}; this._hgStatusCache = {}; this._modifiedDirectoryCache = new Map(); this._hgDiffCache = {}; this._hgDiffCacheFilesUpdating = new Set(); this._hgDiffCacheFilesToClear = new Set(); this._serializedRefreshStatusesCache = (0, (_commonsNodeDebounce2 || _commonsNodeDebounce()).default)((0, (_commonsNodePromise2 || _commonsNodePromise()).serializeAsyncCall)(this._refreshStatusesOfAllFilesInCache.bind(this)), STATUS_DEBOUNCE_DELAY_MS); this._disposables[EDITOR_SUBSCRIPTION_NAME] = atom.workspace.observeTextEditors(function (editor) { var filePath = editor.getPath(); if (!filePath) { // TODO: observe for when this editor's path changes. return; } if (!_this._isPathRelevant(filePath)) { return; } // If this editor has been previously active, we will have already // initialized diff info and registered listeners on it. if (_this._disposables[filePath]) { return; } // TODO (t8227570) Get initial diff stats for this editor, and refresh // this information whenever the content of the editor changes. var editorSubscriptions = _this._disposables[filePath] = new (_atom2 || _atom()).CompositeDisposable(); editorSubscriptions.add(editor.onDidSave(function (event) { _this._updateDiffInfo([event.path]); })); // Remove the file from the diff stats cache when the editor is closed. // This isn't strictly necessary, but keeps the cache as small as possible. // There are cases where this removal may result in removing information // that is still relevant: e.g. // * if the user very quickly closes and reopens a file; or // * if the file is open in multiple editors, and one of those is closed. // These are probably edge cases, though, and the information will be // refetched the next time the file is edited. editorSubscriptions.add(editor.onDidDestroy(function () { _this._hgDiffCacheFilesToClear.add(filePath); _this._disposables[filePath].dispose(); delete _this._disposables[filePath]; })); }); // Regardless of how frequently the service sends file change updates, // Only one batched status update can be running at any point of time. var toUpdateChangedPaths = []; var serializedUpdateChangedPaths = (0, (_commonsNodeDebounce2 || _commonsNodeDebounce()).default)((0, (_commonsNodePromise2 || _commonsNodePromise()).serializeAsyncCall)(function () { // Send a batched update and clear the pending changes. return _this._updateChangedPaths(toUpdateChangedPaths.splice(0)); }), STATUS_DEBOUNCE_DELAY_MS); var onFilesChanges = function onFilesChanges(changedPaths) { toUpdateChangedPaths.push.apply(toUpdateChangedPaths, changedPaths); // Will trigger an update immediately if no other async call is active. // Otherwise, will schedule an async call when it's done. serializedUpdateChangedPaths(); }; this._initializationPromise = this._service.waitForWatchmanSubscriptions(); this._initializationPromise.catch(function (error) { atom.notifications.addWarning('Mercurial: failed to subscribe to watchman!'); }); // Get updates that tell the HgRepositoryClient when to clear its caches. this._service.observeFilesDidChange().refCount().subscribe(onFilesChanges); this._service.observeHgRepoStateDidChange().refCount().subscribe(this._serializedRefreshStatusesCache); this._service.observeActiveBookmarkDidChange().refCount().subscribe(this.fetchActiveBookmark.bind(this)); this._service.observeBookmarksDidChange().refCount().subscribe(function () { _this._emitter.emit('did-change-bookmarks'); }); this._service.observeHgConflictStateDidChange().refCount().subscribe(this._conflictStateChanged.bind(this)); } _createClass(HgRepositoryClient, [{ key: 'destroy', value: function destroy() { var _this2 = this; this._emitter.emit('did-destroy'); this._emitter.dispose(); Object.keys(this._disposables).forEach(function (key) { _this2._disposables[key].dispose(); }); this._service.dispose(); } }, { key: '_conflictStateChanged', value: function _conflictStateChanged(isInConflict) { this._isInConflict = isInConflict; this._emitter.emit(DID_CHANGE_CONFLICT_STATE); } /** * * Section: Event Subscription * */ }, { key: 'onDidDestroy', value: function onDidDestroy(callback) { return this._emitter.on('did-destroy', callback); } }, { key: 'onDidChangeStatus', value: function onDidChangeStatus(callback) { return this._emitter.on('did-change-status', callback); } }, { key: 'onDidChangeStatuses', value: function onDidChangeStatuses(callback) { return this._emitter.on('did-change-statuses', callback); } }, { key: 'onDidChangeConflictState', value: function onDidChangeConflictState(callback) { return this._emitter.on(DID_CHANGE_CONFLICT_STATE, callback); } /** * * Section: Repository Details * */ }, { key: 'getType', value: function getType() { return 'hg'; } }, { key: 'getPath', value: function getPath() { return this._path; } }, { key: 'getWorkingDirectory', value: function getWorkingDirectory() { return this._workingDirectory.getPath(); } // @return The path of the root project folder in Atom that this // HgRepositoryClient provides information about. }, { key: 'getProjectDirectory', value: function getProjectDirectory() { return this._projectDirectory.getPath(); } // TODO This is a stub. }, { key: 'isProjectAtRoot', value: function isProjectAtRoot() { return true; } }, { key: 'relativize', value: function relativize(filePath) { return this._workingDirectory.relativize(filePath); } // TODO This is a stub. }, { key: 'hasBranch', value: function hasBranch(branch) { return false; } /** * @return The current Hg bookmark. */ }, { key: 'getShortHead', value: function getShortHead(filePath) { if (!this._activeBookmark) { // Kick off a fetch to get the current bookmark. This is async. this.async.getShortHead(); return ''; } return this._activeBookmark; } // TODO This is a stub. }, { key: 'isSubmodule', value: function isSubmodule(path) { return false; } // TODO This is a stub. }, { key: 'getAheadBehindCount', value: function getAheadBehindCount(reference, path) { return 0; } // TODO This is a stub. }, { key: 'getCachedUpstreamAheadBehindCount', value: function getCachedUpstreamAheadBehindCount(path) { return { ahead: 0, behind: 0 }; } // TODO This is a stub. }, { key: 'getConfigValue', value: function getConfigValue(key, path) { return null; } }, { key: 'getOriginURL', value: function getOriginURL(path) { return this._originURL; } // TODO This is a stub. }, { key: 'getUpstreamBranch', value: function getUpstreamBranch(path) { return null; } // TODO This is a stub. }, { key: 'getReferences', value: function getReferences(path) { return { heads: [], remotes: [], tags: [] }; } // TODO This is a stub. }, { key: 'getReferenceTarget', value: function getReferenceTarget(reference, path) { return null; } // Added for conflict detection. }, { key: 'isInConflict', value: function isInConflict() { return this._isInConflict; } /** * * Section: Reading Status (parity with GitRepository) * */ // TODO (jessicalin) Can we change the API to make this method return a Promise? // If not, might need to do a synchronous `hg status` query. }, { key: 'isPathModified', value: function isPathModified(filePath) { if (!filePath) { return false; } var cachedPathStatus = this._hgStatusCache[filePath]; if (!cachedPathStatus) { return false; } else { return this.isStatusModified((_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedPathStatus]); } } // TODO (jessicalin) Can we change the API to make this method return a Promise? // If not, might need to do a synchronous `hg status` query. }, { key: 'isPathNew', value: function isPathNew(filePath) { if (!filePath) { return false; } var cachedPathStatus = this._hgStatusCache[filePath]; if (!cachedPathStatus) { return false; } else { return this.isStatusNew((_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedPathStatus]); } } }, { key: 'isPathAdded', value: function isPathAdded(filePath) { if (!filePath) { return false; } var cachedPathStatus = this._hgStatusCache[filePath]; if (!cachedPathStatus) { return false; } else { return this.isStatusAdded((_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedPathStatus]); } } }, { key: 'isPathUntracked', value: function isPathUntracked(filePath) { if (!filePath) { return false; } var cachedPathStatus = this._hgStatusCache[filePath]; if (!cachedPathStatus) { return false; } else { return this.isStatusUntracked((_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedPathStatus]); } } // TODO (jessicalin) Can we change the API to make this method return a Promise? // If not, this method lies a bit by using cached information. // TODO (jessicalin) Make this work for ignored directories. }, { key: 'isPathIgnored', value: function isPathIgnored(filePath) { if (!filePath) { return false; } // `hg status -i` does not list the repo (the .hg directory), presumably // because the repo does not track itself. // We want to represent the fact that it's not part of the tracked contents, // so we manually add an exception for it via the _isPathWithinHgRepo check. var cachedPathStatus = this._hgStatusCache[filePath]; if (!cachedPathStatus) { return this._isPathWithinHgRepo(filePath); } else { return this.isStatusIgnored((_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedPathStatus]); } } /** * Checks if the given path is within the repo directory (i.e. `.hg/`). */ }, { key: '_isPathWithinHgRepo', value: function _isPathWithinHgRepo(filePath) { return filePath === this.getPath() || filePath.indexOf(this.getPath() + '/') === 0; } /** * Checks whether a path is relevant to this HgRepositoryClient. A path is * defined as 'relevant' if it is within the project directory opened within the repo. */ }, { key: '_isPathRelevant', value: function _isPathRelevant(filePath) { return this._projectDirectory.contains(filePath) || this._projectDirectory.getPath() === filePath; } // For now, this method only reflects the status of "modified" directories. // Tracking directory status isn't straightforward, as Hg only tracks files. // http://mercurial.selenic.com/wiki/FAQ#FAQ.2FCommonProblems.I_tried_to_check_in_an_empty_directory_and_it_failed.21 // TODO: Make this method reflect New and Ignored statuses. }, { key: 'getDirectoryStatus', value: function getDirectoryStatus(directoryPath) { if (!directoryPath) { return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeNumber.CLEAN; } var directoryPathWithSeparator = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.normalizeDir(directoryPath); if (this._modifiedDirectoryCache.has(directoryPathWithSeparator)) { return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeNumber.MODIFIED; } return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeNumber.CLEAN; } // We don't want to do any synchronous 'hg status' calls. Just use cached values. }, { key: 'getPathStatus', value: function getPathStatus(filePath) { return this.getCachedPathStatus(filePath); } }, { key: 'getCachedPathStatus', value: function getCachedPathStatus(filePath) { if (!filePath) { return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeNumber.CLEAN; } var cachedStatus = this._hgStatusCache[filePath]; if (cachedStatus) { return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[cachedStatus]; } return (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeNumber.CLEAN; } }, { key: 'getAllPathStatuses', value: function getAllPathStatuses() { var pathStatuses = Object.create(null); for (var _filePath in this._hgStatusCache) { pathStatuses[_filePath] = (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[this._hgStatusCache[_filePath]]; } return pathStatuses; } }, { key: 'isStatusModified', value: function isStatusModified(status) { return this.async.isStatusModified(status); } }, { key: 'isStatusNew', value: function isStatusNew(status) { return this.async.isStatusNew(status); } }, { key: 'isStatusAdded', value: function isStatusAdded(status) { return this.async.isStatusAdded(status); } }, { key: 'isStatusUntracked', value: function isStatusUntracked(status) { return this.async.isStatusUntracked(status); } }, { key: 'isStatusIgnored', value: function isStatusIgnored(status) { return this.async.isStatusIgnored(status); } /** * * Section: Reading Hg Status (async methods) * */ /** * Recommended method to use to get the status of files in this repo. * @param paths An array of file paths to get the status for. If a path is not in the * project, it will be ignored. * See HgService::getStatuses for more information. */ }, { key: 'getStatuses', value: _asyncToGenerator(function* (paths, options) { var _this3 = this; var statusMap = new Map(); var isRelavantStatus = this._getPredicateForRelevantStatuses(options); // Check the cache. // Note: If paths is empty, a full `hg status` will be run, which follows the spec. var pathsWithCacheMiss = []; paths.forEach(function (filePath) { var statusId = _this3._hgStatusCache[filePath]; if (statusId) { if (!isRelavantStatus(statusId)) { return; } statusMap.set(filePath, (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[statusId]); } else { pathsWithCacheMiss.push(filePath); } }); // Fetch any uncached statuses. if (pathsWithCacheMiss.length) { var newStatusInfo = yield this._updateStatuses(pathsWithCacheMiss, options); newStatusInfo.forEach(function (status, filePath) { statusMap.set(filePath, (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[status]); }); } return statusMap; }) /** * Fetches the statuses for the given file paths, and updates the cache and * sends out change events as appropriate. * @param filePaths An array of file paths to update the status for. If a path * is not in the project, it will be ignored. */ }, { key: '_updateStatuses', value: _asyncToGenerator(function* (filePaths, options) { var _this4 = this; var pathsInRepo = filePaths.filter(function (filePath) { return _this4._isPathRelevant(filePath); }); if (pathsInRepo.length === 0) { return new Map(); } var statusMapPathToStatusId = yield this._service.fetchStatuses(pathsInRepo, options); var queriedFiles = new Set(pathsInRepo); var statusChangeEvents = []; statusMapPathToStatusId.forEach(function (newStatusId, filePath) { var oldStatus = _this4._hgStatusCache[filePath]; if (oldStatus && oldStatus !== newStatusId || !oldStatus && newStatusId !== (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.CLEAN) { statusChangeEvents.push({ path: filePath, pathStatus: (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeIdToNumber[newStatusId] }); if (newStatusId === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.CLEAN) { // Don't bother keeping 'clean' files in the cache. delete _this4._hgStatusCache[filePath]; _this4._removeAllParentDirectoriesFromCache(filePath); } else { _this4._hgStatusCache[filePath] = newStatusId; if (newStatusId === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.MODIFIED) { _this4._addAllParentDirectoriesToCache(filePath); } } } queriedFiles.delete(filePath); }); // If the statuses were fetched for only changed (`hg status`) or // ignored ('hg status --ignored`) files, a queried file may not be // returned in the response. If it wasn't returned, this means its status // may have changed, in which case it should be removed from the hgStatusCache. // Note: we don't know the real updated status of the file, so don't send a change event. // TODO (jessicalin) Can we make the 'pathStatus' field in the change event optional? // Then we can send these events. var hgStatusOption = this._getStatusOption(options); if (hgStatusOption === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ONLY_IGNORED) { queriedFiles.forEach(function (filePath) { if (_this4._hgStatusCache[filePath] === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.IGNORED) { delete _this4._hgStatusCache[filePath]; } }); } else if (hgStatusOption === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ALL_STATUSES) { // If HgStatusOption.ALL_STATUSES was passed and a file does not appear in // the results, it must mean the file was removed from the filesystem. queriedFiles.forEach(function (filePath) { var cachedStatusId = _this4._hgStatusCache[filePath]; delete _this4._hgStatusCache[filePath]; if (cachedStatusId === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.MODIFIED) { _this4._removeAllParentDirectoriesFromCache(filePath); } }); } else { queriedFiles.forEach(function (filePath) { var cachedStatusId = _this4._hgStatusCache[filePath]; if (cachedStatusId !== (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.IGNORED) { delete _this4._hgStatusCache[filePath]; if (cachedStatusId === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).StatusCodeId.MODIFIED) { _this4._removeAllParentDirectoriesFromCache(filePath); } } }); } // Emit change events only after the cache has been fully updated. statusChangeEvents.forEach(function (event) { _this4._emitter.emit('did-change-status', event); }); this._emitter.emit('did-change-statuses'); return statusMapPathToStatusId; }) }, { key: '_addAllParentDirectoriesToCache', value: function _addAllParentDirectoriesToCache(filePath) { (0, (_utils2 || _utils()).addAllParentDirectoriesToCache)(this._modifiedDirectoryCache, filePath, this._projectDirectory.getParent().getPath()); } }, { key: '_removeAllParentDirectoriesFromCache', value: function _removeAllParentDirectoriesFromCache(filePath) { (0, (_utils2 || _utils()).removeAllParentDirectoriesFromCache)(this._modifiedDirectoryCache, filePath, this._projectDirectory.getParent().getPath()); } /** * Helper function for ::getStatuses. * Returns a filter for whether or not the given status code should be * returned, given the passed-in options for ::getStatuses. */ }, { key: '_getPredicateForRelevantStatuses', value: function _getPredicateForRelevantStatuses(options) { var hgStatusOption = this._getStatusOption(options); if (hgStatusOption === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ONLY_IGNORED) { return filterForOnlyIgnored; } else if (hgStatusOption === (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ALL_STATUSES) { return filterForAllStatues; } else { return filterForOnlyNotIgnored; } } /** * * Section: Retrieving Diffs (parity with GitRepository) * */ }, { key: 'getDiffStats', value: function getDiffStats(filePath) { var cleanStats = { added: 0, deleted: 0 }; if (!filePath) { return cleanStats; } var cachedData = this._hgDiffCache[filePath]; return cachedData ? { added: cachedData.added, deleted: cachedData.deleted } : cleanStats; } /** * Returns an array of LineDiff that describes the diffs between the given * file's `HEAD` contents and its current contents. * NOTE: this method currently ignores the passed-in text, and instead diffs * against the currently saved contents of the file. */ // TODO (jessicalin) Export the LineDiff type (from hg-output-helpers) when // types can be exported. // TODO (jessicalin) Make this method work with the passed-in `text`. t6391579 }, { key: 'getLineDiffs', value: function getLineDiffs(filePath, text) { if (!filePath) { return []; } var diffInfo = this._hgDiffCache[filePath]; return diffInfo ? diffInfo.lineDiffs : []; } /** * * Section: Retrieving Diffs (async methods) * */ /** * Updates the diff information for the given paths, and updates the cache. * @param An array of absolute file paths for which to update the diff info. * @return A map of each path to its DiffInfo. * This method may return `null` if the call to `hg diff` fails. * A file path will not appear in the returned Map if it is not in the repo, * if it has no changes, or if there is a pending `hg diff` call for it already. */ }, { key: '_updateDiffInfo', value: _asyncToGenerator(function* (filePaths) { var _this5 = this; var pathsToFetch = filePaths.filter(function (aPath) { // Don't try to fetch information for this path if it's not in the repo. if (!_this5._isPathRelevant(aPath)) { return false; } // Don't do another update for this path if we are in the middle of running an update. if (_this5._hgDiffCacheFilesUpdating.has(aPath)) { return false; } else { _this5._hgDiffCacheFilesUpdating.add(aPath); return true; } }); if (pathsToFetch.length === 0) { return new Map(); } // Call the HgService and update our cache with the results. var pathsToDiffInfo = yield this._service.fetchDiffInfo(pathsToFetch); if (pathsToDiffInfo) { for (var _ref3 of pathsToDiffInfo) { var _ref2 = _slicedToArray(_ref3, 2); var _filePath2 = _ref2[0]; var diffInfo = _ref2[1]; this._hgDiffCache[_filePath2] = diffInfo; } } // Remove files marked for deletion. this._hgDiffCacheFilesToClear.forEach(function (fileToClear) { delete _this5._hgDiffCache[fileToClear]; }); this._hgDiffCacheFilesToClear.clear(); // The fetched files can now be updated again. for (var pathToFetch of pathsToFetch) { this._hgDiffCacheFilesUpdating.delete(pathToFetch); } // TODO (t9113913) Ideally, we could send more targeted events that better // describe what change has occurred. Right now, GitRepository dictates either // 'did-change-status' or 'did-change-statuses'. this._emitter.emit('did-change-statuses'); return pathsToDiffInfo; }) /** * * Section: Retrieving Bookmark (async methods) * */ /* * @deprecated Use {#async.getShortHead} instead */ }, { key: 'fetchActiveBookmark', value: function fetchActiveBookmark() { return this.async.getShortHead(); } }, { key: 'fetchMergeConflicts', value: function fetchMergeConflicts() { return this._service.fetchMergeConflicts(); } }, { key: 'resolveConflictedFile', value: function resolveConflictedFile(filePath) { return this._service.resolveConflictedFile(filePath); } /** * * Section: Checking Out * */ // TODO This is a stub. }, { key: 'checkoutHead', value: function checkoutHead(path) { return false; } // TODO This is a stub. }, { key: 'checkoutReference', value: function checkoutReference(reference, create) { return false; } /** * @deprecated Use {#async.checkoutReference} instead * * This is the async version of what checkoutReference() is meant to do. */ }, { key: 'checkoutRevision', value: function checkoutRevision(reference, create) { return this.async.checkoutReference(reference, create); } /** * * Section: HgService subscriptions * */ /** * Updates the cache in response to any number of (non-.hgignore) files changing. * @param update The changed file paths. */ }, { key: '_updateChangedPaths', value: _asyncToGenerator(function* (changedPaths) { var _this6 = this; var relevantChangedPaths = changedPaths.filter(this._isPathRelevant.bind(this)); if (relevantChangedPaths.length === 0) { return; } else if (relevantChangedPaths.length <= MAX_INDIVIDUAL_CHANGED_PATHS) { // Update the statuses individually. yield this._updateStatuses(relevantChangedPaths, { hgStatusOption: (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ALL_STATUSES }); yield this._updateDiffInfo(relevantChangedPaths.filter(function (filePath) { return _this6._hgDiffCache[filePath]; })); } else { // This is a heuristic to improve performance. Many files being changed may // be a sign that we are picking up changes that were created in an automated // way -- so in addition, there may be many batches of changes in succession. // The refresh is serialized, so it is safe to call it multiple times in succession. yield this._serializedRefreshStatusesCache(); } }) }, { key: '_refreshStatusesOfAllFilesInCache', value: _asyncToGenerator(function* () { this._hgStatusCache = {}; this._modifiedDirectoryCache = new Map(); var pathsInDiffCache = Object.keys(this._hgDiffCache); this._hgDiffCache = {}; // We should get the modified status of all files in the repo that is // under the HgRepositoryClient's project directory, because when Hg // modifies the repo, it doesn't necessarily only modify files that were // previously modified. yield this._updateStatuses([this.getProjectDirectory()], { hgStatusOption: (_nuclideHgRpcLibHgConstants2 || _nuclideHgRpcLibHgConstants()).HgStatusOption.ONLY_NON_IGNORED }); if (pathsInDiffCache.length > 0) { yield this._updateDiffInfo(pathsInDiffCache); } }) /** * * Section: Repository State at Specific Revisions * */ }, { key: 'fetchFileContentAtRevision', value: function fetchFileContentAtRevision(filePath, revision) { return this._service.fetchFileContentAtRevision(filePath, revision); } }, { key: 'fetchFilesChangedAtRevision', value: function fetchFilesChangedAtRevision(revision) { return this._service.fetchFilesChangedAtRevision(revision); } }, { key: 'fetchRevisionInfoBetweenHeadAndBase', value: function fetchRevisionInfoBetweenHeadAndBase() { return this._service.fetchRevisionInfoBetweenHeadAndBase(); } // See HgService.getBaseRevision. }, { key: 'getBaseRevision', value: function getBaseRevision() { return this._service.getBaseRevision(); } // See HgService.getBlameAtHead. }, { key: 'getBlameAtHead', value: function getBlameAtHead(filePath) { return this._service.getBlameAtHead(filePath); } }, { key: 'getTemplateCommitMessage', value: function getTemplateCommitMessage() { // TODO(t12228275) This is a stopgap hack, fix it. return this._service.getTemplateCommitMessage(); } }, { key: 'getConfigValueAsync', value: function getConfigValueAsync(key, path) { return this._service.getConfigValueAsync(key); } // See HgService.getDifferentialRevisionForChangeSetId. }, { key: 'getDifferentialRevisionForChangeSetId', value: function getDifferentialRevisionForChangeSetId(changeSetId) { return this._service.getDifferentialRevisionForChangeSetId(changeSetId); } }, { key: 'getSmartlog', value: function getSmartlog(ttyOutput, concise) { return this._service.getSmartlog(ttyOutput, concise); } }, { key: 'copy', value: function copy(filePaths, destPath) { var after = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; return this._service.copy(filePaths, destPath, after); } }, { key: 'rename', value: function rename(filePaths, destPath) { var after = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; return this._service.rename(filePaths, destPath, after); } }, { key: 'remove', value: function remove(filePaths) { var after = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; return this._service.remove(filePaths, after); } }, { key: 'addAll', value: function addAll(filePaths) { return this._service.add(filePaths); } }, { key: 'commit', value: _asyncToGenerator(function* (message) { yield this._service.commit(message); this._clearClientCache(); }) }, { key: 'amend', value: _asyncToGenerator(function* (message) { yield this._service.amend(message); this._clearClientCache(); }) }, { key: 'revert', value: function revert(filePaths) { return this._service.revert(filePaths); } }, { key: 'log', value: function log(filePaths, limit) { // TODO(mbolin): Return an Observable so that results appear faster. // Unfortunately, `hg log -Tjson` is not Observable-friendly because it will // not parse as JSON until all of the data has been printed to stdout. return this._service.log(filePaths, limit); } }, { key: 'continueRebase', value: function continueRebase() { return this._service.continueRebase(); } }, { key: 'abortRebase', value: function abortRebase() { return this._service.abortRebase(); } }, { key: '_getStatusOption', value: function _getStatusOption(options) { if (options == null) { return null; } return options.hgStatusOption; } }, { key: '_clearClientCache', value: function _clearClientCache() { this._hgDiffCache = {}; this._hgStatusCache = {}; this._emitter.emit('did-change-statuses'); } }]); return HgRepositoryClient; })(); exports.HgRepositoryClient = HgRepositoryClient; /** The origin URL of this repository. */ /** The working directory of this repository. */ /** The root directory that is opened in Atom, which this Repository serves. */ // A map from a key (in most cases, a file path), to a related Disposable. // Map of directory path to the number of modified files within that directory.