Commit 0e05d42e authored by Tomas Vik (OOO back on 2026-08-31)'s avatar Tomas Vik (OOO back on 2026-08-31) 🌴
Browse files

feat: open local file during MR review

parent 6c5429e3
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -139,6 +139,12 @@
          "dark": "src/assets/images/dark/refresh.svg"
        }
      },
      {
				"command": "gl.openMrFile",
				"title": "Open Changed File In Local Project",
				"category": "GitLab",
				"icon": "$(go-to-file)"
			},
      {
        "command": "gl.resolveThread",
        "title": "Resolve Thread",
@@ -240,6 +246,10 @@
          "command": "gl.checkoutMrBranch",
          "when": "false"
        },
        {
          "command": "gl.openMrFile",
          "when": "false"
        },
        {
          "command": "gl.showIssuesAssignedToMe",
          "when": "gitlab:validState"
@@ -390,6 +400,13 @@
          "group": "inline",
          "when": "commentController =~ /^gitlab-mr-/"
        }
      ],
      "editor/title": [
				{
					"command": "gl.openMrFile",
					"when": "resourceScheme == 'gl-review' && resourceFilename != '' && isInDiffEditor",
					"group": "navigation@-99"
				}
      ]
    },
    "viewsContainers": {
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ export const USER_COMMANDS = {
  CLONE_WIKI: 'gl.cloneWiki',
  CREATE_SNIPPET_PATCH: 'gl.createSnippetPatch',
  APPLY_SNIPPET_PATCH: 'gl.applySnippetPatch',
  OPEN_MR_FILE: 'gl.openMrFile',
};

/*
+45 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import * as fs from 'fs';
import { VS_COMMANDS } from '../command_names';
import { toReviewUri } from '../review/review_uri';
import { mrVersion, reviewUriParams } from '../test_utils/entities';
import { openMrFile } from './open_mr_file';
import { gitExtensionWrapper } from '../git/git_extension_wrapper';
import { WrappedRepository } from '../git/wrapped_repository';
import { asMock } from '../test_utils/as_mock';

jest.mock('fs', () => ({
  promises: {
    access: jest.fn(),
  },
}));

describe('openMrFile', () => {
  beforeEach(() => {
    jest
      .spyOn(gitExtensionWrapper, 'getRepository')
      .mockReturnValue(({ getMr: () => ({ mrVersion }) } as unknown) as WrappedRepository);
    asMock(fs.promises.access).mockResolvedValue(undefined);
  });

  it('calls VS Code open with the correct diff file', async () => {
    await openMrFile(toReviewUri({ ...reviewUriParams, path: 'new_file.js' }));
    expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
      VS_COMMANDS.OPEN,
      vscode.Uri.file('/new_file.js'),
    );
  });

  it("calls shows information message when the file doesn't exist", async () => {
    asMock(fs.promises.access).mockRejectedValue(new Error());
    await openMrFile(toReviewUri({ ...reviewUriParams, path: 'new_file.js' }));
    expect(vscode.commands.executeCommand).not.toHaveBeenCalled();
    expect(vscode.window.showWarningMessage).toHaveBeenCalled();
  });

  it("throws assertion error if the diff can't be found", async () => {
    await expect(
      openMrFile(toReviewUri({ ...reviewUriParams, path: 'file_that_is_not_in_mr_diff.c' })),
    ).rejects.toThrowError(/Extension did not find the file/);
  });
});
+39 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import * as path from 'path';
import * as assert from 'assert';
import { promises as fs } from 'fs';
import { VS_COMMANDS } from '../command_names';
import { fromReviewUri } from '../review/review_uri';
import { gitExtensionWrapper } from '../git/git_extension_wrapper';
import { removeLeadingSlash } from '../utils/remove_leading_slash';

/** returns true if file exists, false if it doesn't */
const tryToOpen = async (filePath: string): Promise<boolean> => {
  try {
    await fs.access(filePath); // throws if file doesn't exist
  } catch (e) {
    return false;
  }
  await vscode.commands.executeCommand(VS_COMMANDS.OPEN, vscode.Uri.file(filePath));
  return true;
};

const findDiffWithPath = (diffs: RestDiffFile[], relativePath: string): RestDiffFile | undefined =>
  diffs.find(d => d.new_path === relativePath || d.old_path === relativePath);

export const openMrFile = async (uri: vscode.Uri): Promise<void> => {
  const params = fromReviewUri(uri);
  assert(params.path);
  const repository = gitExtensionWrapper.getRepository(params.repositoryRoot);
  const cachedMr = repository.getMr(params.mrId);
  assert(cachedMr);
  const diff = findDiffWithPath(cachedMr.mrVersion.diffs, removeLeadingSlash(params.path));
  assert(diff, 'Extension did not find the file in the MR, please refresh the side panel.');
  const getFullPath = (relative: string) => path.join(params.repositoryRoot, relative);
  const opened =
    (await tryToOpen(getFullPath(diff.new_path))) || (await tryToOpen(getFullPath(diff.old_path)));
  if (!opened)
    await vscode.window.showWarningMessage(
      `The file ${params.path} doesn't exist in your local project`,
    );
};
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ const { checkoutMrBranch } = require('./commands/checkout_mr_branch');
const { cloneWiki } = require('./commands/clone_wiki');
const { createSnippetPatch } = require('./commands/create_snippet_patch');
const { applySnippetPatch } = require('./commands/apply_snippet_patch');
const { openMrFile } = require('./commands/open_mr_file');

const wrapWithCatch = command => async (...args) => {
  try {
@@ -89,6 +90,7 @@ const registerCommands = (context, outputChannel) => {
      issuableDataProvider.refresh();
      currentBranchDataProvider.refresh();
    },
    [USER_COMMANDS.OPEN_MR_FILE]: openMrFile,
    [PROGRAMMATIC_COMMANDS.NO_IMAGE_REVIEW]: () =>
      vscode.window.showInformationMessage("GitLab MR review doesn't support images yet."),
  };
Loading