wrapped_repository.ts 4.1 KB
Newer Older
1
import * as url from 'url';
2
import { basename } from 'path';
3
import * as assert from 'assert';
4 5
import { Repository } from '../api/git';

6 7 8
import { GITLAB_COM_URL } from '../constants';
import { tokenService } from '../services/token_service';
import { log } from '../log';
9
import { GitRemote, parseGitRemote } from './git_remote_parser';
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
import { getExtensionConfiguration } from '../utils/get_extension_configuration';
import { GitLabNewService } from '../gitlab/gitlab_new_service';
import { GitService } from '../git_service';

function intersectionOfInstanceAndTokenUrls(gitRemoteHosts: string[]) {
  const instanceUrls = tokenService.getInstanceUrls();

  return instanceUrls.filter(instanceUrl =>
    gitRemoteHosts.includes(url.parse(instanceUrl).host || ''),
  );
}

function heuristicInstanceUrl(gitRemoteHosts: string[]) {
  // if the intersection of git remotes and configured PATs exists and is exactly
  // one hostname, use it
  const intersection = intersectionOfInstanceAndTokenUrls(gitRemoteHosts);
  if (intersection.length === 1) {
    const heuristicUrl = intersection[0];
    log(`Found ${heuristicUrl} in the PAT list and git remotes, using it as the instanceUrl`);
    return heuristicUrl;
  }

  if (intersection.length > 1) {
    log(`Found more than one intersection of git remotes and configured PATs, ${intersection}`);
  }

  return null;
}

export function getInstanceUrlFromRemotes(gitRemoteUrls: string[]): string {
  const { instanceUrl } = getExtensionConfiguration();
  // if the workspace setting exists, use it
  if (instanceUrl) {
    return instanceUrl;
  }

  // try to determine the instance URL heuristically
  const gitRemoteHosts = gitRemoteUrls
    .map((uri: string) => parseGitRemote(uri)?.host)
    .filter((h): h is string => Boolean(h));
  const heuristicUrl = heuristicInstanceUrl(gitRemoteHosts);
  if (heuristicUrl) {
    return heuristicUrl;
  }

  // default to Gitlab cloud
  return GITLAB_COM_URL;
}

59
export class WrappedRepository {
60
  private readonly rawRepository: Repository;
61 62 63 64 65

  constructor(rawRepository: Repository) {
    this.rawRepository = rawRepository;
  }

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
  private get remoteName(): string {
    const preferredRemote = getExtensionConfiguration().remoteName;
    const branchRemote = this.rawRepository.state.HEAD?.remote;
    const firstRemote = this.rawRepository.state.remotes[0]?.name;
    return preferredRemote || branchRemote || firstRemote || 'origin';
  }

  private getRemoteByName(remoteName: string): GitRemote {
    const remoteUrl = this.rawRepository.state.remotes.find(r => r.name === remoteName)?.fetchUrl;
    assert(remoteUrl, `could not find any URL for git remote with name '${this.remoteName}'`);
    const parsedRemote = parseGitRemote(remoteUrl, this.instanceUrl);
    assert(parsedRemote, `git remote "${remoteUrl}" could not be parsed`);
    return parsedRemote;
  }

  get remote(): GitRemote {
    return this.getRemoteByName(this.remoteName);
  }

  get pipelineRemote(): GitRemote {
    const { pipelineGitRemoteName } = getExtensionConfiguration();
    return this.getRemoteByName(pipelineGitRemoteName || this.remoteName);
  }

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  get instanceUrl(): string {
    const remoteUrls = this.rawRepository.state.remotes
      .map(r => r.fetchUrl)
      .filter((r): r is string => Boolean(r));
    return getInstanceUrlFromRemotes(remoteUrls);
  }

  get gitLabService(): GitLabNewService {
    return new GitLabNewService(this.instanceUrl);
  }

  get gitService(): GitService {
    const { remoteName } = getExtensionConfiguration();
    return new GitService({
      repositoryRoot: this.rootFsPath,
      preferredRemoteName: remoteName,
    });
  }

109 110 111 112
  get name(): string {
    return basename(this.rawRepository.rootUri.fsPath);
  }

113 114 115
  get rootFsPath(): string {
    return this.rawRepository.rootUri.fsPath;
  }
116 117 118 119 120 121 122 123 124 125 126

  /**
   * Compares, whether this wrapper contains repository for the
   * same folder as the method argument.
   *
   * The VS Code Git extension can produce more instances of `Repository`
   * interface for the same git folder. We can't simply compare references with `===`.
   */
  hasSameRootAs(repository: Repository): boolean {
    return this.rootFsPath === repository.rootUri.fsPath;
  }
127
}