Skip to content

Draft: feat(branch-tree-view): Display tag pipelines in branch tree

  • Please check this box if this contribution uses AI-generated content (including content generated by GitLab Duo features) as outlined in the GitLab DCO & CLA

Description

extension-POC

This POC MR outlines kitchen-sink changes to introduce displaying tag pipelines along side branch resources. The general approach taken was to add an auxiliary TagState to BranchState and ensure the logic to process the top level TagState was flexible enough to also handle the auxiliary TagState on a BranchState.

The Tree data provider has refactors that follow suit with this pattern by encapsulating tag display logic into a TagTree class that can either be displayed as a top level node or under an auxiliary node. BranchTree similarly contains the logic for displaying the branch state and an optional TagTree property for the auxiliary tag state.

Moving forward, we could potentially part these changes out into at least two MRs:

  1. CurrentBranchRefresher handles populating tagState on the BranchState interface
  2. CurrentBranchDataProvider handles the display of an auxiliary tagState on BranchState

The refactors made some of these changes easier to execute on, but if the refactors went too far I can also pullback changes in these MRs to be more lean!

More detailed technical notes
  1. Changes to CurrentBranchRefresher:

The CurrentBranchRefresher class has been modified, with some of its logic moved to a new HeadStateFetcher class to improve modularity and separation of concerns. One of the main targets of these refactors was ensuring that the auxiliary tagState on the BranchState interface can be processed in the same way as the top level TagState interface.

// Feature branch
export class CurrentBranchRefresher {
  // ...
  async refresh(userInitiated = false) {
    const projectInRepository = getActiveProject();
    const headStateFetcher = new HeadStateFetcher(projectInRepository, userInitiated);
    this.#latestState = await headStateFetcher.fetch();
    this.#stateChangedEmitter.fire(this.#latestState);
    this.#lastRefresh = dayjs();
  }
  // ...
}
  1. Introduction of HeadStateFetcher:

The new HeadStateFetcher class is responsible for fetching the state of the HEAD ref and populating the tree view with the appropriate resources. It uses a two-step process: first creating a stub state, then filling it out with fetched API resources. This class largely just moves logic from the CurrentBranchRefresher with some light refactors. The extra modularity here allows TagState to be processed with the same logic whether it's a parent state or an auxiliary state on BranchState.

// Feature branch
export class HeadStateFetcher {
  // ...
  async fetch(): Promise<TreeState> {
    try {
      const projectInRepository = this.#projectInRepository;
      if (!projectInRepository) return INVALID_STATE;

      const headStateStub = await this.#getStubForHead();
      if (headStateStub.type === 'invalid') return headStateStub;

      return await this.#buildStateFromStub(headStateStub);
    } catch (e) {
      log.error(e);
      return { type: 'invalid', error: e };
    }
  }
  // ...
}
  1. Changes to getTrackingBranchName:

The getTrackingBranchName function has been modified to use getBranch instead of directly accessing state.HEAD.name. This change aims to ensure that only the actual branch name is returned, not a tag name when in detached HEAD state.

// Feature branch
export const getTrackingBranchName = async (
  rawRepository: Repository,
): Promise<string | undefined> => {
  const branch = await rawRepository.getBranch('HEAD');
  const branchName = branch?.name;
  // ...
}

// Main branch
export const getTrackingBranchName = async (
  rawRepository: Repository,
): Promise<string | undefined> => {
  const branchName = rawRepository.state.HEAD?.name;
  // ...
}
  1. Changes to Tree Data Providers:

The CurrentBranchDataProvider has been renamed to CurrentHeadDataProvider to reflect that it handles displaying both Branch and Tag states. It now uses separate BranchTree and TagTree classes to handle the respective states.

// Feature branch
export class CurrentHeadDataProvider implements vscode.TreeDataProvider<ItemModel | vscode.TreeItem>, vscode.Disposable {
  // ...
  async getChildren(element?: ItemModel | vscode.TreeItem): Promise<(ItemModel | vscode.TreeItem)[]> {
    if (element instanceof ItemModel ||  element instanceof TagNode) return element.getChildren();
    if (!this.#root) return [];
    return this.#root.getChildren();
  }
  // ...
  refresh(state: TreeState) {
    this.#root?.dispose();
    if (state.type === 'branch') {
      this.#root = new BranchTree(state);
    } else if (state.type === 'tag') {
      this.#root = new TagTree(state);
    } else {
      this.#root = undefined;
    }
    this.#eventEmitter.fire();
  }
  // ...
}
  1. Introduction of BranchTree and TagTree:

These new classes encapsulate the logic for displaying branch and tag resources. The goal was to provide a more object-oriented approach to handling different states and reduce the amount of logic and conditionals in the tree data provider. Similar to the refactors splitting CurrentBranchRefresher and HeadStateFetcher, a tighter separation of concerns allows the auxiliary TagState to be processed with the same logic that is used for the parent TagState.

// Feature branch
export class BranchTree implements vscode.Disposable {
  // ...
  async getChildren(): Promise<(ItemModel | vscode.TreeItem)[]> {
    // Logic for displaying branch items
  }
  // ...
}

export class TagTree implements vscode.Disposable {
  // ...
  async getChildren(): Promise<(ItemModel | vscode.TreeItem)[]> {
    // Logic for displaying tag items
  }
  // ...
}
  1. Dynamic Panel Label:

The changes introduce the ability to change the label of the panel based on the current state (branch or tag).

// Feature branch
export class CurrentHeadDataProvider {
  // ...
  getViewTitle(): string {
    if (!this.#root) return 'For current branch';
    return this.#root.getViewTitle();
  }
  // ...
}

Related Issues

Relates to #1246

What CHANGELOG entry will this MR create?

This MR is not intended to be merged, and is for discussion only. If we move forward, we can track some more iterative MRs back to this parent MR and close it once this effort is complete.

Edited by Van Anderson

Merge request reports