The VS Code Extension That Helps You Control Your Pull Requests Size
Tags: git, github, vscode, typescript
Published: 2023-11-01 20:11:53

When I was working as an intern developer in a startup in my hometown, I noticed one particular thing that was quite annoying: large pull requests.

In this post, I'm gonna talk more about [Changes Counter](https://marketplace.visualstudio.com/items?itemName=LeonardoHolanda.changes-counter), a VS Code extension I made to tackle this problem. Here's what you will see:

- [The Problem](#the-problem)
- [The Solution](#the-solution)
- [The Implementation](#the-implementation)
- [The Result](#the-result)

## The Problem

If you have reviewed a PR with thousands of lines, you know that it ain't fun. It takes time and the more time you spend on it, the more tired you get while also becoming harder to find bugs and mistakes in the code.

<figure>
  <img src="https://www.swarmia.com/static/dcad6608994a7c2fcca0ab325233fa17/27330/lgtm-tweet.png">
  <figcaption>This tweet is a classic when posts are made about pull requests
</figure>

In the context of my internship, I remember seeing some large PRs taking so long to be merged that it would affect the progress of other tasks resulting in bottlenecks.

And there's actually a [study](https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/) on this made by a company called SmartBear which recommends reviewing "no more than 200 to 400 lines of code at a time".

In summary: large pull requests suck.

### Why would they happen in my team?

During the Scrum ceremonies, our team would decide which histories and tasks to create on the Jira board. Most of the time, we would agree that the tasks' scope was fine and reasonable. However, when working on these tasks, we noticed that they required more changes than predicted.

Sometimes, we would push the changes to the remote branch and create pull requests with lots of changes. Sometimes, we would notice that it would be too much for our reviewer friend and split the task into two separate ones.

But this whole process would rely on intuition rather than the actual number of changes that we would make in the PR. And being a team of interns, this intuition hasn't yet got enough time to develop itself and achieve good results. The same thing applies to task creation, I think.

So I thought: What if the dev would know exactly how many changes will go in the PR while coding the task instead of relying on intuition?

This way, the dev will always know if the PR is getting larger than desired and then decide to take some action.

## The Solution

One of the extensions that we were encouraged to use in the internship was GitLens. It provides lots of features to make the whole git experience better and it actually would come in handy frequently during the work we did.

While using it, I noticed that the Search & Compare feature contains data about changed lines of code. But unfortunately, not in a way that could solve our problem.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5zypzec88m879kjgu52.png">
  <figcaption>GitLens Search & Compare feature screenshot
</figure>

To solve our problem, the data needed to be presented in an easier way to look at while being more useful to the developer during the process of working on a task. Kinda like a status bar item like the Errors & Warnings.

Also, a notification warning the developer that a given change quantity threshold was exceeded could be nice in case the dev was unaware of it.

I ended up with these requirements:
- A status bar item that shows how many lines of code were changed
- The item is updated every time a file is saved
- The user can set a threshold to determine an acceptable quantity of changed lines
- A notification is sent when the threshold is exceeded

## The Implementation

With the requirements in mind, I decided to develop a VS Code extension myself and try to tackle these requirements into features.

I knew nothing about coding VS Code extensions. However, this [documentation](https://code.visualstudio.com/api/get-started/your-first-extension) was really valuable in giving the base extension code and instructions on running it in a dev environment.

Then, it was a matter of adding the features I wanted. Lots of things were actually simple and I don't think it's worth mentioning here. But this one problem was interesting to me:

### How to run Git commands in TypeScript?

The first question I had was: "How to count the number of lines changed?". Since I already knew about git diff, it was just a matter of running this command and working on its output.

But since VS Code extensions are developed with TypeScript in a Node.js environment, how would it be possible to run git diff and hold its output inside the extension code?

Searching about it, I discovered the Child Process module from Node.js. You can use it to spawn a subprocess that can run git commands. This is the code provided in the [documentation](https://nodejs.org/docs/latest-v18.x/api/child_process.html#child_processspawncommand-args-options) as an example:

```
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
}); 
```

Bringing this to the extension code, we get something like this:

```
  async getDiffData(): Promise<DiffData> {
    return new Promise((resolve, reject) => {
      const comparisonBranch = this.context.workspaceState.get<string>("comparisonBranch");
      if (comparisonBranch === undefined) {
        reject("A comparison branch wasn't defined. Please, define a comparison branch.");
        return;
      }

      const gitChildProcess = spawn(
        "git",
        ["diff", comparisonBranch, "--shortstat", ...this.diffExclusionParameters],
        {
          cwd: vscode.workspace.workspaceFolders![0].uri.fsPath,
          shell: true, // Diff exclusion parameters doesn't work without this
        }
      );

      gitChildProcess.on("error", (err) => reject(err));

      let chunks: Buffer[] = [];
      gitChildProcess.stdout.on("data", (chunk: Buffer) => {
        chunks.push(chunk);
      });

      gitChildProcess.stderr.on("data", (data: Buffer) => {
        reject(data.toString());
      });

      gitChildProcess.on("close", () => {
        const processOutput = Buffer.concat(chunks);
        resolve(this.extractDiffData(processOutput));
      });
    });
  }
```

By calling this function, we get all we need through the resolved promise: an object containing the changes data or the error we must handle.

### Logging

A thing I learned while developing this extension is how important logging is. When some users reported problems, the lack of information was something that made debugging difficult.

When I started logging some lifecycle events and errors that could eventually appear, I noticed that it would be far easier to debug once the user sent me the log. Knowing where to search for the bug is obviously crucial.

If you are developing VS Code extensions, try to introduce logging as early as possible to avoid debugging in the dark.

And don't forget to search for the best practices before doing it. They are simple and can be helpful to make your logging more consistent with other applications. Here's an [example](https://devcenter.heroku.com/articles/writing-best-practices-for-application-logs).

## The Result

You can see the extension page at the Visual Studio Marketplace [here](https://marketplace.visualstudio.com/items?itemName=LeonardoHolanda.changes-counter).

<figure>
  <img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExaHE2eWduY3oxaHUxcmRoOGpubHQ3MGM2aDRlYmJsNmRvODNmcDZtMCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/AmNBg5C7EyT9APOSUY/giphy.gif">
  <figcaption>Changes Counter extension
</figure>

And you can see the code [here](https://github.com/leo-holanda/changes-counter/blob/main/src/extension.ts).

Any feedback and suggestions are more than welcome!

## What I learned from all this
- How to create and deploy a VS Code extension
- How to spawn subprocesses and run system commands with the Child Process module from Node.js
- The importance of logging