NAV
JavaScript Shell

Getting Started

What Is Private Comments

Private comments allows you to leave “private comments” on specific lines of a codebase that are not stored in the codebase.

Imagine being dropped into a new codebase and having the freedom to leave whatever todo items and breadcrumbs you want without cluttering the codebase. Imagine working on a client’s codebase and not having to worry about what you say, or who sees it.

Even better, Private Comments is guaranteed to not leak any secrets about the codebase you’re working on. We can’t guarantee what you write won’t, but the closest the system comes is reusing git treeishes from the repo you’re commenting on.

Thanks to git, you can see the old comments from historical versions of your files, and outdated comments disappear when the lines they comment on change or go away.

This site explains how it works, and the APIs you’ll need to work with in order to create a plugin to support it in your favorite editor. For details on installing the the Private Comments application check out the GitHub repository.

an example gif

How Does It Work?

Private Comments is a tiny little REST server. A plugin for your favorite editor sends it new and edited comments to save, and requsts a list of existing comments for files you’re working on.

Comments are stored in a separate git repo for each project. If you want, you can share your comments repo with teammates.

Please see the GitHub README for instructions on how to install and run the Private Comments server.

Writing Editor Plugins

Overall, writing a plugin that works with Private Comments is pretty simple, and you only need to support two endpoints which I’ll cover in a moment. You’ll likely spend the majority of your time figuring out how to display the comments and let users create or edit them.

Check out this diagram for a high level overview of the process for viewing and creating comments.

How you choose to display, and edit comments is entirely up to you. Spend time focusing on usability and style. I’d also recommend that you make it easy to toggle the display of comments entirely.

Once you’ve released your comment, add it to the wiki.

Core Concepts

In order to prevent Private Comments from leaking secrets most of the data you provide to PC will be SHA 256 hashes.

A comment requires 3 pieces of information to tie it to the right place in your codebase:

Project Names

It doesn’t matter if the project name you use for the hash is auto-generated or comes from user input. It is critical that you have a mechanism that allows for users with multiple computers to use the exact same project name on each device. Different names will result in different project name hashes, and that will prevent them from syncing comments between devices.

The simplest solution to this is to let users see the name you’re using for the current project. If your editor supports the concept of named projects, that would be the obvious thing to use, but the must be able to override it in your plugin in case they’ve named the project differently on the other device(s).

Another simple solution is to store it with git config on a per repository basis. For example:

$ git config --add private-comments.project-name "my_project_name"
$ git config --get private-comments.project-name
my_project_name

Adding / Updating A Comment

Prerequisite: The line of code being commented on must have been committed first before Private Comments can persist the comment. Comments on new lines must be managed by your plugin until the new line has been committed.

Prerequisite: You must have the ability to run git blame on the file. You’ll need this to know which lines of the file come from which git commits. More specifically, you’ll need to know which treeish each line associated with.

Gather up the things listed in Core Concepts along with the comment, line number, and the treeish of the line being commented on. Then send a POST to the /v1/comments endpoint with JSON.

Note: If project_name_hash has not been seen before a new project will be automatically created, and a new git repository will be initialized. Because of this it is imperative that plugins have a reliable and consistent way of persisting or regenerating project name hashes.

Requesting Comments for a file

Prerequisite: You must have the ability to run git blame on the file. You’ll need this to know which lines of the file come from which git commits. More specifically, you’ll need to know which treeish each line associated with.

Private Comments retrieves comments for you on a per-file basis. Whenever users start displaying a new file, you can sent PC a GET request to /v1/comments with the required info. What you’ll get back is a list of all the comments left on this file, for all the treeishes in the file.

Use Git Blame to gather up all the treeishes that make up your file. Put together a unique list,

Note: You may also receive comments for lines that no-longer exist in the file. For example. Lets say your “cars.rb” file has had two commits. Lines 1-10 were added in the first commit. Your user left a comment on line 1 and a comment on line 7. Lines 5-10 were then replaced in a second commit.

When your user opens the file git blame tells you that the file is comprised of commits from 2 treeishes. You gather those treeishes up along with the other core concepts and pass them off to PC. Because that first commit’s treeish is in the list for this file PC will give you both of the comments for that treeish. The response data will let you know what line and treeish each comment is for. Just throw out any comments where the line number is no longer associated with treeish that came back with the comment.

Summarized Process:

Shutting down the server

Should you need it, you can send a request to /shutdown to shut down the server. However, it is strongly recommended that you leave it running. Your user may be using multiple editors

Comment

A comment object contains a collection of information about a specific comment

In addition to this, the JSON you send along with you comment can include any other metadata your plugin needs to do its job in displaying this comment. It is stronly recommended that you don’t include file names, class names, or anything else from the code that could leak secrets that might violate someone’s NDA.

The comment itself is just random text to Private Comments. It can be plain text, or encrypted. Silently encrypting and decrypting it would be an excellent layer of protection for your users.

Note: The file path hash must be the full path from the project root to the file plus the file name. Without the full path there may be hash collisions for two files with the same name, which will result in returning, or overwriting comments for the wrong file.

Comment Parameters

Parameter Type Required Description
project_name_hash String true A SHA 256 hash of the project name
file_path_hash String true A SHA 256 hash of the path to the file from the root of the project.
treeish String true The treeish that created the line under comment (from git blame)
line_number Integer true An integer indicating the line number being commented on
comment String true The comment left by the user
anything else Any false Literally any other metadata your plugin wants to store about this comment

Comment Endpoints

Add / Update a comment endpoint

// add or update a comment
var data = JSON.stringify({
  "project_name_hash": "7135459ae30c0d5180b623986c420bf20856461cb6b9b860986a22c7654ed755",
  "file_path_hash": "faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87",
  "treeish": "777a58e942164e94964999bcc7cd20bbcda28fe55ac38be28e530f58ddad5ad8",
  "line_number": 4,
  "comment": "comment text here"
});

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("POST", "http://localhost:5749/v1/comments");
xhr.setRequestHeader("content-type", "application/json");

xhr.send(data);
# add or update a comment
curl --request POST \
  --url http://localhost:5749/v1/comments \
  --header 'content-type: application/json' \
  --data '{
  "project_name_hash": "7135459ae30c0d5180b623986c420bf20856461cb6b9b860986a22c7654ed755",
  "file_path_hash": "faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87",
  "treeish": "777a58e942164e94964999bcc7cd20bbcda28fe55ac38be28e530f58ddad5ad8",
  "line_number": 4,
  "comment": "comment text here"
}'

The code above will return JSON structured like this:

{
  "status": "SUCCESS",
  "description": "faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87-4.json written"
}

This endpoint creates or updates a single comment object and takes the full list of parameters noted above.

HTTP Request

POST /v1/comments

Delete a comment endpoint

This endpoint deletes a single comment and takes the comment parameters listed above, minus the “comment”. Note that the HTTP DELETE verb only officially supports query params.

This endpoint expects the following query parameters:

Parameter Type Description
project_hash_name String (see above)
file_path_hash String (see above)
treeish String the single treeish relevant to the current version of line where the associated comment is to be deleted.
line_number Integer true
// delete a comment
var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("DELETE", "http://localhost:5749/v1/comments?project_name_hash=PROJECT_NAME_HASH&file_path_hash=FILE_PATH_HASH&treeish=SINGLE_TREEISH&line_number=LINE_NUMBER");

xhr.send(data);
# delete a comment
curl --request DELETE \
  --url 'http://localhost:5749/v1/comments?project_name_hash=PROJECT_NAME_HASH&file_path_hash=FILE_PATH_HASH&treeish=SINGLE_TREEISH&line_number=LINE_NUMBER'

The code above will return JSON structured like this:

{
  "status": "SUCCESS",
  "description": "comment removed"
}

HTTP Request

DELETE /v1/comments?project_name_hash=<PROJECT_NAME_HASH>&file_path_hash=<FILE_PATH_HASH>&treeish=<SINGLE_TREEISH>&line_number=<LINE_NUMBER>

Retrieve comments for a file endpoint

This endpoint retrieves all the comments relevant to the treeishes in the current verison of the specified file. Note that this may contain comments that are associated with a treeish that is still in your file, but for a line that has since been replaced with another commit.

For example: Your first commit adds all the lines of a file. All lines of this file are associated with the same treeish. Then you leave a private comment on the 1st line of the file. The top half of the file is then changed in a subsequent commit. The lines in the bottom half of the file would be associated with the first commit still. Becaues you pass all relevant treeishes, the server would return the comment from the first line, because it was tied with a treeish that is still in the file, BUT that comment would no longer be relevant to the version of the file the user was viewing. It’s up to the consumer of the API to address that.

This endpoint expects the following query parameters:

Parameter Type Description
project_hash_name String (see above)
file_path_hash String (see above)
treeishes String a comma-separated list of treeishes relevant to the current version of the file (generated via git blame).
// retrieve a comment
var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("GET", "http://localhost:5749/v1/comments?project_name_hash&file_path_hash=faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87&treeishes=777a58e942164e94964999bcc7cd20bbcda28fe55ac38be28e530f58ddad5ad8%2C263fe246fb49b562e501427470efed926dc11cb9f8909be5b1987d3f2adff712");

xhr.send(data);
# retrieve a comment
curl --request GET \
  --url 'http://localhost:5749/v1/comments?project_name_hash=7135459ae30c0d5180b623986c420bf20856461cb6b9b860986a22c7654ed755&file_path_hash=faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87&treeishes=777a58e942164e94964999bcc7cd20bbcda28fe55ac38be28e530f58ddad5ad8%2C263fe246fb49b562e501427470efed926dc11cb9f8909be5b1987d3f2adff712'

The code above will return JSON structured like this:

{
  "project_name_hash": "7135459ae30c0d5180b623986c420bf20856461cb6b9b860986a22c7654ed755",
  "file_path_hash": "faf3e6fc36b8524dad3aa4a317e734623f0dfcf6934a659015827406ebfb0c87",
  "comments": [
    {
      "treeish": "777a58e942164e94964999bcc7cd20bbcda28fe55ac38be28e530f58ddad5ad8",
      "line_number": 4,
      "comment": "comment text here"
    }
  ]
}

HTTP Request

GET /v1/comments?project_hash_name={project_hash}&file_path_hash={file_path_hash}&treeishes={comma separated list of treeishes}

Server Status

Provides a means of performing a quick sanity-check that the server is actually up and running. Note that the status request is not currently versioned.

// server status
var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("GET", "http://localhost:5749/status");

xhr.send(data);

# server status
curl --request GET \
  --url http://localhost:5749/status

The code above will return JSON structured like this:

{"status": "ALIVE"}

HTTP Request

GET /status