KEMBAR78
Reimagine how the app determines its context · Issue #2 · cli/cli · GitHub
Skip to content

Reimagine how the app determines its context #2

@mislav

Description

@mislav

The app currently determines the context it's operating under from several sources:

  • the filesystem (e.g. current directory name)
  • app config file (currently ~/.config/hub in YAML format)
  • environment variables (e.g. GH_REPO, GITHUB_TOKEN, etc.)
  • git config (via git config & git remote -v, sources: .git/config & ~/.gitconfig)
  • git tree (e.g. current branch, current commit SHA)
  • SSH config (sources: ~/.ssh/config & /etc/ssh_config) - resolves SSH aliases for hostnames listed under git remotes
  • GitHub API (e.g. info about the current repo)

When something like gh pr create gets run, quite a lot happens under the hood. For example:

  1. Current GitHub user and OAuth token are obtained from app config;
  2. The list of git remotes gets queried and parsed;
  3. The "main" remote (i.e. one pointing to the canonical GitHub repo) is determined by searching for the first one in this list: upstream, github, origin;
  4. The base branch is determined via git symbolic-ref refs/remotes/<REMOTE>/HEAD (alternatively, by querying repo information via API);
  5. The head branch is determined by looking at the push target for the current branch:
    • explicit upstream branch configuration is first looked up;
    • otherwise, the first remote that has a same-named tracking branch is the likely push target;
    • otherwise, assume the branch isn't pushed yet, so determine the first remote that points to a GitHub project that the current user has write capabilities to;
    • if such a remote doesn't exist, create one by forking;
  6. A person's preferred text editor is looked up for authoring PR description text;
  7. PR creation operation proceeds.

To facilitate all these lookups, the current codebase has a loose system of mapping one piece of information to another. For example:

  • current git repo 👉 default ("base") branch
  • current git repo 👉 "main" remote
  • git remote 👉 GitHub repository (a.k.a. "project") it maps to
  • tracking branch 👉 git remote it belongs to
  • current user 👉 the person's fork

Since a lot of lookups start from the current git repo (based on the current working directory at the time that the CLI app runs), there is a LocalRepo struct that encapsulates performing some of these mappings, while additional mapping logic is scattered across individual methods such as Branch.RemoteName(), Remote.Project(), etc. Some problems I find with the current system:

  • Inconsistent naming (e.g. "repository" vs. "project", the ambiguity of "branch")
  • Blurred responsibility between objects (e.g. why would a Branch have to know how to map itself to a Remote, or a Remote to a Project?)
  • Methods that do too much but don't sufficiently betray intent (e.g. LocalRepo.RemoteBranchAndProject())
  • Hard to stub out in tests (ideally, unit tests should be able to set up a mock context in memory rather than having to write a test git repo out to fileystem)
  • All of this code is under the same Go package: "github".

My rough proposal for starting to address this:

  • Get rid of methods and structs named "Project" from the codebase;
  • Get rid of LocalRepo;
  • Design an abstraction around git config that is able to mock responses in memory;
  • Consider moving github/branch.go to under the "git" package;
  • Consider isolating git/ssh_config.go to its own package;
  • Minimize the implementation of Remote and instead perform necessary mappings through a separate service object;
  • Minimize the "github" package until it's ideally gone;
  • Write Go unit tests along the way to confirm the testability of these implementations.

Let's revise all this as we go along! Thank you for reading. 🙇

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions