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,047 lines (930 loc) 40.5 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. */ 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 _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 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; }; })(); var getForkBaseName = _asyncToGenerator(function* (directoryPath) { var arcConfig = yield (0, (_nuclideArcanistRpc2 || _nuclideArcanistRpc()).readArcConfig)(directoryPath); if (arcConfig != null) { return arcConfig['arc.feature.start.default'] || arcConfig['arc.land.onto.default'] || DEFAULT_ARC_PROJECT_FORK_BASE; } return DEFAULT_FORK_BASE_NAME; } /** * @return Array of additional watch expressions to apply to the primary * watchman subscription. */ ); function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 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 _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _commonsNodeNuclideUri2; function _commonsNodeNuclideUri() { return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri')); } var _fs2; function _fs() { return _fs2 = _interopRequireDefault(require('fs')); } var _nuclideWatchmanHelpers2; function _nuclideWatchmanHelpers() { return _nuclideWatchmanHelpers2 = require('../../nuclide-watchman-helpers'); } var _hgConstants2; function _hgConstants() { return _hgConstants2 = require('./hg-constants'); } var _rxjsBundlesRxUmdMinJs2; function _rxjsBundlesRxUmdMinJs() { return _rxjsBundlesRxUmdMinJs2 = require('rxjs/bundles/Rx.umd.min.js'); } var _hgBlameOutputParser2; function _hgBlameOutputParser() { return _hgBlameOutputParser2 = require('./hg-blame-output-parser'); } var _hgDiffOutputParser2; function _hgDiffOutputParser() { return _hgDiffOutputParser2 = require('./hg-diff-output-parser'); } var _hgRevisionExpressionHelpers2; function _hgRevisionExpressionHelpers() { return _hgRevisionExpressionHelpers2 = require('./hg-revision-expression-helpers'); } var _hgRevisionStateHelpers2; function _hgRevisionStateHelpers() { return _hgRevisionStateHelpers2 = require('./hg-revision-state-helpers'); } var _hgUtils2; function _hgUtils() { return _hgUtils2 = require('./hg-utils'); } var _commonsNodeFsPromise2; function _commonsNodeFsPromise() { return _commonsNodeFsPromise2 = _interopRequireDefault(require('../../commons-node/fsPromise')); } var _commonsNodeDebounce2; function _commonsNodeDebounce() { return _commonsNodeDebounce2 = _interopRequireDefault(require('../../commons-node/debounce')); } var _hgBookmarkHelpers2; function _hgBookmarkHelpers() { return _hgBookmarkHelpers2 = require('./hg-bookmark-helpers'); } var _nuclideArcanistRpc2; function _nuclideArcanistRpc() { return _nuclideArcanistRpc2 = require('../../nuclide-arcanist-rpc'); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); var DEFAULT_ARC_PROJECT_FORK_BASE = 'remote/master'; var DEFAULT_FORK_BASE_NAME = 'default'; var WATCHMAN_SUBSCRIPTION_NAME_PRIMARY = 'hg-repository-watchman-subscription-primary'; var WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARK = 'hg-repository-watchman-subscription-hgbookmark'; var WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARKS = 'hg-repository-watchman-subscription-hgbookmarks'; var WATCHMAN_HG_DIR_STATE = 'hg-repository-watchman-subscription-dirstate'; var CHECK_CONFLICT_DELAY_MS = 500; // If Watchman reports that many files have changed, it's not really useful to report this. // This is typically caused by a large rebase or a Watchman re-crawl. // We'll just report that the repository state changed, which should trigger a full client refresh. var FILES_CHANGED_LIMIT = 1000; // Mercurial (as of v3.7.2) [strips lines][1] matching the following prefix when a commit message is // created by an editor invoked by Mercurial. Because Nuclide is not invoked by Mercurial, Nuclide // must mimic the same stripping. // // Note: `(?m)` converts to `/m` in JavaScript-flavored RegExp to mean 'multiline'. // // [1] https://selenic.com/hg/file/3.7.2/mercurial/cmdutil.py#l2734 var COMMIT_MESSAGE_STRIP_LINE = /^HG:.*(\n|$)/gm; // Suffixes of hg error messages that indicate that an error is safe to ignore, // and should not warrant a user-visible error. These generally happen // when performing an hg operation on a non-existent or untracked file. var IGNORABLE_ERROR_SUFFIXES = ['abort: no files to copy', 'No such file or directory', 'does not exist!']; /** * These are status codes used by Mercurial's output. * Documented in http://selenic.com/hg/help/status. */ /** * Internally, the HgRepository uses the string StatusCodeId to do bookkeeping. * However, GitRepository uses numbers to represent its statuses, and returns * statuses as numbers. In order to keep our status 'types' the same, we map the * string StatusCodeId to numbers. * The numbers themselves should not matter; they are meant to be passed * to ::isStatusNew/::isStatusModified to be interpreted. */ function getPrimaryWatchmanSubscriptionRefinements() { var refinements = []; try { // $FlowFB refinements = require('./fb/config').primaryWatchSubscriptionRefinements; } catch (e) { // purposely blank } return refinements; } var HgService = (function () { function HgService(workingDirectory) { var _this = this; _classCallCheck(this, HgService); this._workingDirectory = workingDirectory; this._filesDidChangeObserver = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject(); this._hgActiveBookmarkDidChangeObserver = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject(); this._hgBookmarksDidChangeObserver = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject(); this._hgRepoStateDidChangeObserver = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject(); this._hgConflictStateDidChangeObserver = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject(); this._lockFileHeld = false; this._lockFilePath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(workingDirectory, '.hg', 'wlock'); this._rebaseStateFilePath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(workingDirectory, '.hg', 'rebasestate'); this._isRebasing = false; this._isInConflict = false; this._debouncedCheckConflictChange = (0, (_commonsNodeDebounce2 || _commonsNodeDebounce()).default)(function () { _this._checkConflictChange(); }, CHECK_CONFLICT_DELAY_MS); this._watchmanSubscriptionPromise = this._subscribeToWatchman(); } _createClass(HgService, [{ key: 'waitForWatchmanSubscriptions', value: function waitForWatchmanSubscriptions() { return this._watchmanSubscriptionPromise; } }, { key: 'dispose', value: _asyncToGenerator(function* () { this._filesDidChangeObserver.complete(); this._hgRepoStateDidChangeObserver.complete(); this._hgActiveBookmarkDidChangeObserver.complete(); this._hgBookmarksDidChangeObserver.complete(); this._hgConflictStateDidChangeObserver.complete(); if (this._hgDirWatcher != null) { this._hgDirWatcher.close(); this._hgDirWatcher = null; } yield this._cleanUpWatchman(); }) // Wrapper to help mocking during tests. }, { key: '_hgAsyncExecute', value: function _hgAsyncExecute(args, options) { return (0, (_hgUtils2 || _hgUtils()).hgAsyncExecute)(args, options); } /** * Section: File and Repository Status */ /** * Shells out of the `hg status` to get the statuses of the paths. All paths * are presumed to be within the repo. (If any single path is not within the repo, * this method will return an empty map.) * @param options An Object with the following fields: * * `hgStatusOption`: an HgStatusOption */ }, { key: 'fetchStatuses', value: _asyncToGenerator(function* (filePaths, options) { var _this2 = this; var statusMap = new Map(); var args = ['status', '-Tjson']; if (options && 'hgStatusOption' in options) { if (options.hgStatusOption === (_hgConstants2 || _hgConstants()).HgStatusOption.ONLY_IGNORED) { args.push('--ignored'); } else if (options.hgStatusOption === (_hgConstants2 || _hgConstants()).HgStatusOption.ALL_STATUSES) { args.push('--all'); } } args = args.concat(filePaths); var execOptions = { cwd: this._workingDirectory }; var output = undefined; try { output = yield this._hgAsyncExecute(args, execOptions); } catch (e) { return statusMap; } var statuses = JSON.parse(output.stdout); statuses.forEach(function (status) { statusMap.set((_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(_this2._workingDirectory, status.path), status.status); }); return statusMap; }) }, { key: '_subscribeToWatchman', value: _asyncToGenerator(function* () { var _this3 = this; // Using a local variable here to allow better type refinement. var watchmanClient = new (_nuclideWatchmanHelpers2 || _nuclideWatchmanHelpers()).WatchmanClient(); this._watchmanClient = watchmanClient; var workingDirectory = this._workingDirectory; var primarySubscriptionExpression = ['allof', ['not', ['dirname', '.hg']], // Hg appears to modify temporary files that begin with these // prefixes, every time a file is saved. // TODO (t7832809) Remove this when it is unnecessary. ['not', ['match', 'hg-checkexec-*', 'wholename']], ['not', ['match', 'hg-checklink-*', 'wholename']], // This watchman subscription is used to determine when and which // files to fetch new statuses for. There is no reason to include // directories in these updates, and in fact they may make us overfetch // statuses. (See diff summary of D2021498.) // This line restricts this subscription to only return files. ['type', 'f']]; primarySubscriptionExpression = primarySubscriptionExpression.concat(getPrimaryWatchmanSubscriptionRefinements()); // Subscribe to changes to files unrelated to source control. var primarySubscribtion = yield watchmanClient.watchDirectoryRecursive(workingDirectory, WATCHMAN_SUBSCRIPTION_NAME_PRIMARY, { fields: ['name', 'exists', 'new'], expression: primarySubscriptionExpression, defer: ['hg.update'] }); logger.debug('Watchman subscription ' + WATCHMAN_SUBSCRIPTION_NAME_PRIMARY + ' established.'); // TODO(most): Replace the usage of node watchers when watchman is ready // with the advanced option: "defer": "hg.update" // Watchman currently (v4.5) doesn't report `.hg` file updates until it reaches // a stable filesystem (not respecting `defer_vcs` option) and // that doesn't happen with big mercurial updates (the primary use of state file watchers). // Hence, we here use node's filesystem watchers instead. try { this._hgDirWatcher = (_fs2 || _fs()).default.watch((_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(workingDirectory, '.hg'), function (event, fileName) { if (fileName === 'wlock') { _this3._debouncedCheckConflictChange(); } else if (fileName === 'rebasestate') { _this3._debouncedCheckConflictChange(); } }); logger.debug('Node watcher created for wlock files.'); } catch (error) { (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('Error when creating node watcher for hg state files', error); } // Subscribe to changes to the active Mercurial bookmark. var hgActiveBookmarkSubscription = yield watchmanClient.watchDirectoryRecursive(workingDirectory, WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARK, { fields: ['name', 'exists'], expression: ['name', '.hg/bookmarks.current', 'wholename'], defer: ['hg.update'] }); logger.debug('Watchman subscription ' + WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARK + ' established.'); // Subscribe to changes in Mercurial bookmarks. var hgBookmarksSubscription = yield watchmanClient.watchDirectoryRecursive(workingDirectory, WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARKS, { fields: ['name', 'exists'], expression: ['name', '.hg/bookmarks', 'wholename'], defer: ['hg.update'] }); logger.debug('Watchman subscription ' + WATCHMAN_SUBSCRIPTION_NAME_HGBOOKMARKS + ' established.'); var dirStateSubscribtion = yield watchmanClient.watchDirectoryRecursive(workingDirectory, WATCHMAN_HG_DIR_STATE, { fields: ['name'], expression: ['name', '.hg/dirstate', 'wholename'], defer: ['hg.update'] }); logger.debug('Watchman subscription ' + WATCHMAN_HG_DIR_STATE + ' established.'); primarySubscribtion.on('change', this._filesDidChange.bind(this)); hgActiveBookmarkSubscription.on('change', this._hgActiveBookmarkDidChange.bind(this)); hgBookmarksSubscription.on('change', this._hgBookmarksDidChange.bind(this)); dirStateSubscribtion.on('change', this._emitHgRepoStateChanged.bind(this)); }) }, { key: '_cleanUpWatchman', value: _asyncToGenerator(function* () { if (this._watchmanClient != null) { yield this._watchmanClient.dispose(); this._watchmanClient = null; } }) /** * @param fileChanges The latest changed watchman files. */ }, { key: '_filesDidChange', value: function _filesDidChange(fileChanges) { if (fileChanges.length > FILES_CHANGED_LIMIT) { this._emitHgRepoStateChanged(); return; } var workingDirectory = this._workingDirectory; var changedFiles = fileChanges.map(function (change) { return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(workingDirectory, change.name); }); this._filesDidChangeObserver.next(changedFiles); } }, { key: '_updateStateFilesExistence', value: _asyncToGenerator(function* () { var _ref = yield Promise.all([(_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.exists(this._lockFilePath), (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.exists(this._rebaseStateFilePath)]); var _ref2 = _slicedToArray(_ref, 2); var lockExists = _ref2[0]; var rebaseStateExists = _ref2[1]; this._lockFileHeld = lockExists; this._isRebasing = rebaseStateExists; }) }, { key: '_checkConflictChange', value: _asyncToGenerator(function* () { yield this._updateStateFilesExistence(); if (this._isInConflict) { if (!this._isRebasing) { this._isInConflict = false; this._hgConflictStateDidChangeObserver.next(false); } return; } // Detect if we are not in a conflict. if (!this._lockFileHeld && this._isRebasing) { var mergeConflicts = yield this.fetchMergeConflicts(); if (mergeConflicts.length > 0) { this._isInConflict = true; this._hgConflictStateDidChangeObserver.next(true); } } }) }, { key: '_emitHgRepoStateChanged', value: function _emitHgRepoStateChanged() { this._hgRepoStateDidChangeObserver.next(); } }, { key: '_hgActiveBookmarkDidChange', value: function _hgActiveBookmarkDidChange() { this._hgActiveBookmarkDidChangeObserver.next(); } }, { key: '_hgBookmarksDidChange', value: function _hgBookmarksDidChange() { this._hgBookmarksDidChangeObserver.next(); } /** * Observes one of more files has changed. Applies to all files except * .hgignore files. (See ::onHgIgnoreFileDidChange.) * @return A Observable which emits the changed file paths. */ }, { key: 'observeFilesDidChange', value: function observeFilesDidChange() { return this._filesDidChangeObserver.publish(); } /** * Observes that a Mercurial event has occurred (e.g. histedit) that would * potentially invalidate any data cached from responses from this service. */ }, { key: 'observeHgRepoStateDidChange', value: function observeHgRepoStateDidChange() { return this._hgRepoStateDidChangeObserver.publish(); } /** * Observes when a Mercurial repository enters and exits a rebase state. */ }, { key: 'observeHgConflictStateDidChange', value: function observeHgConflictStateDidChange() { this._checkConflictChange(); return this._hgConflictStateDidChangeObserver.publish(); } /** * Shells out to `hg diff` to retrieve line diff information for the paths. * @param An Array of NuclideUri (absolute paths) for which to fetch diff info. * @return A map of each NuclideUri (absolute path) to its DiffInfo. * Each path is presumed to be in the repo. * If the `hg diff` call fails, this method returns null. * If a path has no changes, it will not appear in the returned Map. */ }, { key: 'fetchDiffInfo', value: _asyncToGenerator(function* (filePaths) { // '--unified 0' gives us 0 lines of context around each change (we don't // care about the context). // '--noprefix' omits the a/ and b/ prefixes from filenames. // '--nodates' avoids appending dates to the file path line. var args = ['diff', '--unified', '0', '--noprefix', '--nodates'].concat(filePaths); var options = { cwd: this._workingDirectory }; var output = undefined; try { output = yield this._hgAsyncExecute(args, options); } catch (e) { (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('Error when running hg diff for paths: ' + filePaths.toString() + ' \n\tError: ' + e.stderr); return null; } var pathToDiffInfo = (0, (_hgDiffOutputParser2 || _hgDiffOutputParser()).parseMultiFileHgDiffUnifiedOutput)(output.stdout); var absolutePathToDiffInfo = new Map(); for (var _ref33 of pathToDiffInfo) { var _ref32 = _slicedToArray(_ref33, 2); var filePath = _ref32[0]; var diffInfo = _ref32[1]; absolutePathToDiffInfo.set((_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(this._workingDirectory, filePath), diffInfo); } return absolutePathToDiffInfo; }) /** * Section: Bookmarks */ }, { key: 'createBookmark', value: function createBookmark(name, revision) { var args = []; if (revision) { args.push('--rev', revision); } args.push(name); return this._runSimpleInWorkingDirectory('bookmark', args); } }, { key: 'deleteBookmark', value: function deleteBookmark(name) { return this._runSimpleInWorkingDirectory('bookmarks', ['--delete', name]); } }, { key: 'renameBookmark', value: function renameBookmark(name, nextName) { return this._runSimpleInWorkingDirectory('bookmarks', ['--rename', name, nextName]); } /** * @return The name of the current bookmark. */ }, { key: 'fetchActiveBookmark', value: function fetchActiveBookmark() { return (0, (_hgBookmarkHelpers2 || _hgBookmarkHelpers()).fetchActiveBookmark)((_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(this._workingDirectory, '.hg')); } /** * @return An Array of bookmarks for this repository. */ }, { key: 'fetchBookmarks', value: _asyncToGenerator(function* () { var args = ['bookmarks', '-Tjson']; var execOptions = { cwd: this._workingDirectory }; var output = undefined; try { output = yield this._hgAsyncExecute(args, execOptions); } catch (e) { (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('LocalHgServiceBase failed to fetch bookmarks. Error: ' + e.stderr); throw e; } return JSON.parse(output.stdout); }) /** * Observes that the active Mercurial bookmark has changed. */ }, { key: 'observeActiveBookmarkDidChange', value: function observeActiveBookmarkDidChange() { return this._hgActiveBookmarkDidChangeObserver.publish(); } /** * Observes that Mercurial bookmarks have changed. */ }, { key: 'observeBookmarksDidChange', value: function observeBookmarksDidChange() { return this._hgBookmarksDidChangeObserver.publish(); } /** * Section: Repository State at Specific Revisions */ /** * @param filePath: The full path to the file of interest. * @param revision: An expression that hg can understand, specifying the * revision at which we want to see the file content. */ }, { key: 'fetchFileContentAtRevision', value: function fetchFileContentAtRevision(filePath, revision) { return (0, (_hgRevisionStateHelpers2 || _hgRevisionStateHelpers()).fetchFileContentAtRevision)(filePath, revision, this._workingDirectory); } }, { key: 'fetchFilesChangedAtRevision', value: function fetchFilesChangedAtRevision(revision) { return (0, (_hgRevisionStateHelpers2 || _hgRevisionStateHelpers()).fetchFilesChangedAtRevision)(revision, this._workingDirectory); } /** * Fetch the revision details between the current head and the the common ancestor * of head and master in the repository. * @return an array with the revision info (`title`, `author`, `date` and `id`) * or `null` if no common ancestor was found. */ }, { key: 'fetchRevisionInfoBetweenHeadAndBase', value: _asyncToGenerator(function* () { var forkBaseName = yield getForkBaseName(this._workingDirectory); var revisionsInfo = yield (0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).fetchRevisionInfoBetweenRevisions)((0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).expressionForCommonAncestor)(forkBaseName), (0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).expressionForRevisionsBeforeHead)(0), this._workingDirectory); return revisionsInfo; }) /** * Resolve the revision details of the base branch */ }, { key: 'getBaseRevision', value: _asyncToGenerator(function* () { var forkBaseName = yield getForkBaseName(this._workingDirectory); return yield (0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).fetchRevisionInfo)((0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).expressionForCommonAncestor)(forkBaseName), this._workingDirectory); }) /** * Gets the blame for the filePath at the current revision, including uncommitted changes * (but not unsaved changes). * @param filePath The file to get blame information for. * @return A Map that maps a line number (0-indexed) to the name that line blames to. * The name is of the form: "Firstname Lastname <username@email.com> ChangeSetID". * The Firstname Lastname may not appear sometimes. * If no blame information is available, returns an empty Map. */ }, { key: 'getBlameAtHead', value: _asyncToGenerator(function* (filePath) { var args = ['blame', '-r', 'wdir()', '-Tjson', '--changeset', '--user', '--line-number', filePath]; var execOptions = { cwd: this._workingDirectory }; var output = undefined; try { output = yield this._hgAsyncExecute(args, execOptions); } catch (e) { (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('LocalHgServiceBase failed to fetch blame for file: ' + filePath + '. Error: ' + e.stderr); throw e; } return (0, (_hgBlameOutputParser2 || _hgBlameOutputParser()).parseHgBlameOutput)(output.stdout); }) /** * Returns the value of the config item at `key`. * @param key Name of config item */ }, { key: 'getConfigValueAsync', value: _asyncToGenerator(function* (key) { var args = ['config', key]; var execOptions = { cwd: this._workingDirectory }; try { return (yield this._hgAsyncExecute(args, execOptions)).stdout.trim(); } catch (e) { (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('Failed to fetch Hg config for key ' + key + '. Error: ' + e.toString()); return null; } }) /** * Gets the Differential Revision id (aka DXXXXXX) id for the specified changeSetId, if it exists. * Otherwise, returns null. * This implementation relies on the "phabdiff" template being available as defined in: * https://bitbucket.org/facebook/hg-experimental/src/fbf23b3f96bade5986121a7c57d7400585d75f54/phabdiff.py. */ }, { key: 'getDifferentialRevisionForChangeSetId', value: _asyncToGenerator(function* (changeSetId) { var args = ['log', '-T', '{phabdiff}\n', '--limit', '1', '--rev', changeSetId]; var execOptions = { cwd: this._workingDirectory }; try { var output = yield this._hgAsyncExecute(args, execOptions); var _stdout = output.stdout.trim(); return _stdout ? _stdout : null; } catch (e) { // This should not happen: `hg log` does not error even if it does not recognize the template. (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('Failed when trying to get differential revision for: ' + changeSetId); return null; } }) /** * Get the output of the experimental smartlog extension from Mercurial: * https://bitbucket.org/facebook/hg-experimental/#markdown-header-smartlog. * @param ttyOutput If true, return the output as if stdout were attached to a tty. * @param concise true to run `hg smartlog`; false to run `hg ssl`. * @return The output from running the command. */ }, { key: 'getSmartlog', value: _asyncToGenerator(function* (ttyOutput, concise) { // disable the pager extension so that 'hg ssl' terminates. We can't just use // HGPLAIN because we have not found a way to get colored output when we do. var args = ['--config', 'extensions.pager=!', concise ? 'ssl' : 'smartlog']; var execOptions = { cwd: this._workingDirectory, NO_HGPLAIN: concise, // `hg ssl` is likely user-defined. TTY_OUTPUT: ttyOutput }; return yield this._hgAsyncExecute(args, execOptions); }) }, { key: '_commitCode', value: _asyncToGenerator(function* (message) { var extraArgs = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var args = ['commit']; var tempFile = null; if (message != null) { tempFile = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.tempfile(); var strippedMessage = message.replace(COMMIT_MESSAGE_STRIP_LINE, ''); yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.writeFile(tempFile, strippedMessage); args.push('-l', tempFile); } var execOptions = { cwd: this._workingDirectory }; try { yield this._hgAsyncExecute(args.concat(extraArgs), execOptions); } finally { if (tempFile != null) { yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.unlink(tempFile); } } }) /** * Commit code to version control. * @param message Commit message. */ }, { key: 'commit', value: function commit(message) { return this._commitCode(message); } /** * Amend code changes to the latest commit. * @param message Commit message. Message will remain unchaged if not provided. */ }, { key: 'amend', value: function amend(message) { var extraArgs = ['--amend']; if (message == null) { extraArgs.push('--reuse-message', '.'); } return this._commitCode(message, extraArgs); } }, { key: 'revert', value: function revert(filePaths) { return this._runSimpleInWorkingDirectory('revert', filePaths); } }, { key: '_runSimpleInWorkingDirectory', value: _asyncToGenerator(function* (action, args) { var opts = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var options = _extends({}, opts, { cwd: this._workingDirectory }); var cmd = [action].concat(args); try { yield this._hgAsyncExecute(cmd, options); } catch (e) { var errorString = e.stderr || e.message || e.toString(); (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('hg %s failed with [%s] arguments: %s', action, args.toString(), errorString); throw new Error(errorString); } }) /** * @param revision This could be a changeset ID, name of a bookmark, revision number, etc. * @param create Currently, this parameter is ignored. */ }, { key: 'checkout', value: function checkout(revision, create) { return this._runSimpleInWorkingDirectory('checkout', [revision]); } /* * Silence errors from hg calls that don't include any tracked files - these * are generally harmless and should not create an error notification. * This checks the error string in order to avoid potentially slow hg pre-checks. */ }, { key: '_rethrowErrorIfHelpful', value: function _rethrowErrorIfHelpful(e) { if (!IGNORABLE_ERROR_SUFFIXES.some(function (s) { return e.message.endsWith(s + '\n'); })) { throw e; } } /** * Rename/move files versioned under Hg. * @param filePaths Which files should be renamed/moved. * @param destPath What should the file be renamed/moved to. */ }, { key: 'rename', value: _asyncToGenerator(function* (filePaths, destPath, after) { var args = [].concat(_toConsumableArray(filePaths.map(function (p) { return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.getPath(p); })), [// Sources (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.getPath(destPath)]); // Dest if (after) { args.unshift('--after'); } try { yield this._runSimpleInWorkingDirectory('rename', args); } catch (e) { if (after) { this._rethrowErrorIfHelpful(e); } else { throw e; } } }) /** * Remove a file versioned under Hg. * @param filePath Which file should be removed. */ }, { key: 'remove', value: _asyncToGenerator(function* (filePaths, after) { var args = ['-f'].concat(_toConsumableArray(filePaths.map(function (p) { return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.getPath(p); }))); if (after) { args.unshift('--after'); } try { yield this._runSimpleInWorkingDirectory('remove', args); } catch (e) { if (after) { this._rethrowErrorIfHelpful(e); } else { throw e; } } }) /** * Version a new file under Hg. * @param filePath Which file should be versioned. */ }, { key: 'add', value: function add(filePaths) { return this._runSimpleInWorkingDirectory('add', filePaths); } // TODO(t12228275) This is a stopgap hack, fix it. This takes the default // commit message from the hg commit message and since there is no change to // the commit message the commands errors out with error code : 255 and piggy // backs the stdout and stderr into an error message. }, { key: 'getTemplateCommitMessage', value: _asyncToGenerator(function* () { var editor = undefined; // Windows will need 'type' in place of 'cat' if (process.platform === 'win32') { editor = 'type'; } else { editor = 'cat'; } var args = ['commit', '--config', 'ui.editor=' + editor]; var execOptions = { cwd: this._workingDirectory }; try { var _ref4 = yield this._hgAsyncExecute(args, execOptions); var output = _ref4.stdout; // It is most certianly not going to come here ever, the hg commit // is always going to error since no change is made to the commit message. var _stdout2 = output.stdout.trim(); return _stdout2 || null; } catch (e) { // Since no change was made stderr is returned and the service thinks the // command execution failed. This is to get the stdout part of the message. // If there is no match return null. This will need to go away soon. // The message format : stderr:<error> stdout:<stdout> var re = /stdout:(.[\n\r]*[\s\S]*)/; var _stdout3 = re.exec(e.message); if (_stdout3 === null) { return this.getConfigValueAsync('committemplate.emptymsg'); } // Match exists, return the commit message return _stdout3[1].trim(); } }) }, { key: 'getHeadCommitMessage', value: _asyncToGenerator(function* () { var args = ['log', '-T', '{desc}\n', '--limit', '1', '--rev', (0, (_hgRevisionExpressionHelpers2 || _hgRevisionExpressionHelpers()).expressionForRevisionsBeforeHead)(0)]; var execOptions = { cwd: this._workingDirectory }; try { var output = yield this._hgAsyncExecute(args, execOptions); var _stdout4 = output.stdout.trim(); return _stdout4 || null; } catch (e) { // This should not happen: `hg log` does not error even if it does not recognize the template. (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)().error('Failed when trying to get head commit message'); return null; } }) }, { key: 'log', value: _asyncToGenerator(function* (filePaths, limit) { var args = ['log', '-Tjson']; if (limit != null && limit > 0) { args.push('--limit', String(limit)); } for (var filePath of filePaths) { args.push(filePath); } var execOptions = { cwd: this._workingDirectory }; var result = yield this._hgAsyncExecute(args, execOptions); var entries = JSON.parse(result.stdout); return { entries: entries }; }) }, { key: 'fetchMergeConflicts', value: _asyncToGenerator(function* () { var _this4 = this; var _ref5 = yield this._hgAsyncExecute(['resolve', '--list', '-Tjson'], { cwd: this._workingDirectory }); var stdout = _ref5.stdout; var fileListStatuses = JSON.parse(stdout); var conflictedFiles = fileListStatuses.filter(function (fileStatus) { return fileStatus.status === (_hgConstants2 || _hgConstants()).StatusCodeId.UNRESOLVED; }); var origBackupPath = yield this._getOrigBackupPath(); var conflicts = yield Promise.all(conflictedFiles.map(_asyncToGenerator(function* (conflictedFile) { var message = undefined; // Heuristic: If the `.orig` file doesn't exist, then it's deleted by the rebasing commit. if (yield _this4._checkOrigFile(origBackupPath, conflictedFile.path)) { message = (_hgConstants2 || _hgConstants()).MergeConflictStatus.BOTH_CHANGED; } else { message = (_hgConstants2 || _hgConstants()).MergeConflictStatus.DELETED_IN_THEIRS; } return { path: conflictedFile.path, message: message }; }))); return conflicts; }) }, { key: '_getOrigBackupPath', value: _asyncToGenerator(function* () { if (this._origBackupPath == null) { var relativeBackupPath = yield this.getConfigValueAsync('ui.origbackuppath'); if (relativeBackupPath == null) { this._origBackupPath = this._workingDirectory; } else { this._origBackupPath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(this._workingDirectory, relativeBackupPath); } } return this._origBackupPath; }) }, { key: '_checkOrigFile', value: _asyncToGenerator(function* (origBackupPath, filePath) { var origFilePath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(origBackupPath, filePath + '.orig'); logger.info('origBackupPath:', origBackupPath); logger.info('filePath:', filePath); logger.info('origFilePath:', origFilePath); return yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.exists(origFilePath); }) }, { key: 'resolveConflictedFile', value: function resolveConflictedFile(filePath) { return this._runSimpleInWorkingDirectory('resolve', ['-m', filePath]); } }, { key: 'continueRebase', value: function continueRebase() { return this._runSimpleInWorkingDirectory('rebase', ['--continue']); } }, { key: 'abortRebase', value: function abortRebase() { return this._runSimpleInWorkingDirectory('rebase', ['--abort']); } /** * Copy files versioned under Hg. * @param filePaths Which files should be copied. * @param destPath What should the new file be named to. */ }, { key: 'copy', value: _asyncToGenerator(function* (filePaths, destPath, after) { var args = [].concat(_toConsumableArray(filePaths.map(function (p) { return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.getPath(p); })), [// Sources (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.getPath(destPath)]); // Dest if (after) { args.unshift('--after'); } try { yield this._runSimpleInWorkingDirectory('copy', args); } catch (e) { if (after) { this._rethrowErrorIfHelpful(e); } else { throw e; } } }) }]); return HgService; })(); exports.HgService = HgService; // List of bookmarks at this revision.