Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Mercurial Support

Joel Marcey edited this page May 6, 2016 · 1 revision

Discusses the various aspects of how Mercurial is supported in Nuclide (e.g., HgRepositoryClient and friends)

Where's the code?

Currently, there is support for Git and Mercurial within Nuclide. Git support is entirely provided by Atom Core via the GitRepository class.

There is currently no support for remote Git repositories.

Mercurial support (both local and remote repositories) is implemented by the Nuclide team via the following packages:

  • nuclide-hg-repository: Atom package. This package exists because Atom consumes a service ("atom.repository-provider") through its service-hub as the way to add support for repositories other than the built-in Git. A provider for the "atom.repository-provider" service has to implement the function: repositoryForDirectorySync(directory: Directory): ?Repository which should return a custom implementation of a "Repository" if the given Directory if possible. So nuclide-hg-repository provides the HgRepositoryProvider service. The HgRepositoryProvider determines whether a given Directory object is part of an Hg repository, and returns an HgRepositoryClient if so.
  • nuclide-hg-repository-client: npm package. Contains the HgRepositoryClient class, which is Nuclide's Hg version of Atom's GitRepository. The HgRepositoryClient is the "client" component of the HgService, which is a service implemented with the Nuclide service framework. The Client mostly wraps the functionality provided by the LocalHgService, and adds caching.
  • nuclide-hg-repository-base: npm package. Contains the HgService interface, as well as the nitty-gritty implementation of the HgService. The "service" implementation is broken up between two classes, LocalHgService and LocalHgServiceBase. LocalHgService wraps LocalHgServiceBase and hooks into watchman.

How to Use as a Developer

Get a Reference to a Repository

The recommended way to get a reference to a Repository object when developing with Nuclide is:

var {repositoryForPath} = require('nuclide-hg-git-bridge');
var aRepo = repositoryForPath(aNuclideUri);

This method achieves the same thing as Atom's built-in method, repositoryForDirectory, but there are two issues:

  1. Nuclide's RemoteDirectory implementation currently does not implement a method that repositoryForDirectory calls, so you will hit exceptions.
  2. Even if it did, the repositoryForPath will be more performant because it does not require a round-trip to the server for remote repositories.

API Caveats: the Lies of HgRepositoryClient

  1. As of August 2015, HgRepositoryClient has a number of methods for parity with GitRepository that are unimplemented stubs. Please see the code for more details.
  2. Perhaps the most notable difference between HgRepositoryClient and GitRepository is that HgRepositoryClient does not publicly expose any methods that fetch information from Mercurial synchronously. The synchronous method calls on HgRepositoryClient, such as getDirectoryStatus, read from a cache, and thus may provide stale data. We have, however, added public asynchronous methods that provide up-to-date data:
  • getStatuses is the async version of getPathStatus.
  • getDiffStatsForPath is the async version of getDiffStats.
  • getLineDiffsForPath is the async version of getLineDiffs.
  1. HgRepositoryClient updates line diff information on each editor "save" event, rather than continuously. As a result, the line highlighting provided by the git-diff package and the number of added/removed lines displayed in the status bar may be stale in-between saves.

Available Methods

The available GitRepository methods are listed in the Atom docs.

HgRepositoryClient currently provides most of the same methods, but some methods are stubs -- not yet implemented because they haven't been needed. HgRepositoryClient also provides methods not available on GitRepository, e.g.:

  • fetchCurrentBookmark
  • fetchFileContentAtRevision
  • getBlameAtHead

In general, the best way to find the available Hg methods is to read HgService and HgRepositoryClient.js.

How to Add Hg Methods

You should understand how the service framework works. That being said, you can look at a simple example of a method that just fetches data without caching.

Digging Deeper: Implementation Details

Working with Watchman

A few useful features of the Repository object (Git and Hg) are the methods:

  • onDidChangeStatus
  • onDidChangeStatuses

These methods allow Atom features that consume source control information to get notified when source control state changes, so they can update accordingly. For example, you see this in the file tree, which can highlight the new/modified/ignored status of files and directories. As part of the contract, if you ask a Repository for information after receiving these notifications, you should get updated information.

The problem for HgService is then: We need to update the information in HgRepositoryClient to mirror the state of the Hg repository. But how do we detect when the state of an Hg repository changes?

Our solution is to use watchman, a file-watching service developed and open-sourced by Facebook.

We use watchman to determine:

  • When a regular (non-source-control, non-generated) file is modified. This is a good indication that its source control status may have changed.
  • When the '.hgignore' file is modified. This is a good indication that our cached list of 'ignored' files may be outdated.
  • When the '.hg/wlock' file is created/deleted. This is a lock file that is present whenever Hg is modifying the "working directory" (the Hg term for "files within the Hg repo"). We watch this lock because: ** While it's present, we want watchman to ignore any changes in the working directory, because it might be really noisy -- e.g. during a rebase. ** After it's gone, we should invalidate our caches of Hg information, because we don't know what Hg did (rebase, commit, etc).
  • When the '.hg/dirstate' file is modified. This is a fallback for times when the '.hg/wlock' can't be detected, because it's around for such a short time. The dirstate is persistent, so watchman always updates us about changes to it.
  • When the '.hg/bookmarks.current' file is modified. This file contains the name of the current Hg bookmark.
  • When the arc build lock file is present. This works similar to '.hg/wlock', except for arc build.

Watchman enables the following methods on HgService:

  • onFilesDidChange
  • onHgIgnoreFileDidChange
  • onHgBookmarkDidChange
  • onHgRepoStateDidChange

Reducing Fetched Data

TODO