grit

package module
v0.0.0-...-129dccd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2024 License: MIT Imports: 11 Imported by: 0

README

Grit

Not working? Try the troubleshooting guide.

What?

Grit keeps track of your local clones of Git repositories, allowing you to quickly switch to clone directories in your terminal based on the repository name or a portion thereof.

Why?

I spend most of my day working with Git. Many of the repositories are hosted on GitHub.com, but many more are in my employer's private GitHub Enterprise and BitBucket installations.

Keeping track of hundreds of clones can be a little tedious, so some time back I adopted a basic directory naming convention and wrote some shell scripts to handle cloning in a consistent way.

This worked well for a while, until the list of places I needed to clone from increased further, and I started working more heavily in Go, which, at the time, placed it's own requirements on the location of your Git clones.

Grit is the logical evolution of those original scripts into a standalone project that clones from multiple Git sources. It's hacked together, there are no tests, and there are other more general solutions for navigating your filesystem; but this works for me. I've published it to GitHub in case it works for you too.

How?

If you want to get a sense of what Grit does without having to read or understand anything, try this:

  1. Download grit from the releases page or install with Homebrew
  2. Clone a repository with grit clone <repo> maybe try grit clone jmalloc/grit
  3. Find a repository with grit index find <repo>

Joke's on you! Grit only knows about that one repository. Now you have to read a bunch of stuff anyway ...

Configuration

Grit is useful because it knows where you want to clone repositories from. But it's not a magician, it only knows after you tell it, which is less impressive.

By default, Grit looks for a configuration file at ~/.config/grit.toml, which at its most basic is a list of named clone sources, such as:

[clone.sources]
my-company = "git@git.example.com:{{slug}}.git"

If you only use GitHub you don't need to define any additional sources, and so the configuration file can be omitted entirely. For a complete list of the available configuration directives, see the example configuration file.

From now on, I'll assume you're using the configuration shown above, which is a bit silly, but here we are. Rest assured, all paths and filenames used by Grit are probably 100% configurable, more or less.

Cloning a repository

There are two parts to Grit. One of them is the clone command which clones repositories. It's like git clone but without any of the flexibility or power. That's minimalism.

The clone command accepts a single argument, the repository "slug". The slug is the part of the repository URL that a standard-issue human would use to identify a repository. For GitHub, this is the familiar username/repository syntax. For sources configured in grit.toml, it's the part represented by the string {{slug}} in the URL.

Try it out:

grit clone jmalloc/grit

Grit will print the absolute path of the local clone, which should look something like:

/Users/james/grit/github.com/jmalloc/grit

I'm assuming a lot about your system configuration and your given name, just roll with it.

By the way, you can pass a complete Git URL instead of just the slug and Grit will clone it into the correct location, even if the URL does not match any of your configured sources.

Querying the index

Whenever you clone a repository with Grit, the repository is added to the index. The index is a database mapping repository slugs to directories. The index is also that second part of Grit that I mentioned back when we were talking about cloning. I haven't forgotten.

The index can be queried to find a repository by slug:

grit index find jmalloc/grit

Or by the part after the slash, let's call this the repository name:

grit index find grit

Just like the clone command, index find prints the clone path. If there is more than one matching path, it just prints them all. It's relentless.

If nothing is found, Grit exits with a non-zero exit code. The universal signal that you should re-think your actions.

Selecting between multiple clones

"What happens if I have two repositories with the same name?", you ask me in a contrived way perfectly suited to this next example.

Well, try this...

First, clone the mojombo/grit repository; an unsuspecting pawn in our example that has nothing to do with jmalloc/grit. Then, change to the directory of your "grit" clone.

grit clone mojombo/grit
grit cd grit

If you've followed all the steps until now, and I haven't messed up the examples too badly, you should be presented with a list of matching repositories:

Use the arrow keys to navigate: ↓ ↑ → ←
? :
  ▸ github.com/jmalloc/grit
    [current] github.com/mojombo/grit

This time, Grit isn't content to blindly print paths to the terminal. Oh no! This time Grit wants to know what's on your mind. It prints a list of all matching directories and eagerly awaits your decision. Navigate to your selection using the arrow keys and press enter.

Grit diligently prints the absolute path to your selection, just like before. Interminable!

Ok, so Grit prints paths to the terminal, I get it.

Anybody can print to a terminal, even a programmer! Hell, Grit's cd command doesn't even change the current directory!

That's what you sound like right now. Shh bby is ok.

What we need is some glue that makes Grit's cd command behave like the real cd command, and that glue is:

eval "$(grit shell-integration)"

Add this line to your .bash_profile file and many of the following things will happen:

  1. You'll get auto-completion of all command names and indexed repository slugs
  2. grit cd will start working the way you expect
  3. grit clone will also change the current directory after cloning

To achieve the latter two, Grit needs to execute shell commands in the context of it's parent shell (the one you ran Grit from). It does this by writing the commands to a separate file which is then sourced by the parent shell. The details can be seen by running grit shell-integration directly from your terminal.

What next?

My colleagues are helping me iron out some of the kinks. Undoubtedly there are still some changes to come, but Grit is feature complete insofar as it already does everything that my original shell scripts could do.

If you find Grit useful, or have a feature request or bug-report, please don't hesitate to create a new issue.

Documentation

Index

Constants

View Source
const (
	// DefaultBranchName is used when cloning an empty repository for the
	// initial setup.
	DefaultBranchName = "main"
)

Variables

This section is empty.

Functions

func EndpointExists

func EndpointExists(ep Endpoint) (ok bool, err error)

EndpointExists returns true if url is a Git repository.

func EndpointFromRemote

func EndpointFromRemote(cfg *config.RemoteConfig) (ep *transport.Endpoint, url string, err error)

EndpointFromRemote returns the endpoint used to fetch from r.

func EndpointIsSCP

func EndpointIsSCP(s string) bool

EndpointIsSCP returns true if s is an SSH URL given in "SCP" style, that is git@github.com:jmalloc/grit.git, as opposed to ssh://git@github.com/jmalloc/grit.git.

func EndpointToDir

func EndpointToDir(base string, ep *transport.Endpoint) string

EndpointToDir returns the absolute path for a clone of a repository.

func EndpointToSCP

func EndpointToSCP(ep *transport.Endpoint) (string, error)

EndpointToSCP converts a normalized ssh:// endpoint URL to an SCP-style URL.

func EndpointToSlug

func EndpointToSlug(ep *transport.Endpoint) string

EndpointToSlug returns the "slug" from ep.

func MergeSlug

func MergeSlug(ep *transport.Endpoint, s string) *transport.Endpoint

MergeSlug returns a copy of ep with the slug changed to s. If s has less path atoms then the existing slug it is merged with the existing slug such that the original number of path atoms are retained.

func ParseEndpointOrSlug

func ParseEndpointOrSlug(s string) (ep *transport.Endpoint, isEndpoint bool, err error)

ParseEndpointOrSlug returns an endpoint if s contains a valid endpoint URL. If s is a "slug", isEndpoint is false.

func ReplaceSlug

func ReplaceSlug(ep *transport.Endpoint, s string) *transport.Endpoint

ReplaceSlug returns a copy of ep with the slug changed to s.

Types

type Config

type Config struct {
	Clone struct {
		Root          string                      `toml:"root"`
		Sources       map[string]EndpointTemplate `toml:"sources"`
		DefaultBranch string                      `toml:"default-branch"`
	} `toml:"clone"`
	Index struct {
		Paths []string `toml:"paths"`
		Store string   `toml:"store"`
	} `toml:"index"`
}

Config holds Grit configuration.

func LoadConfig

func LoadConfig(file string) (c Config, err error)

LoadConfig loads the Grit configuration from a file.

type Endpoint

type Endpoint struct {
	// The actual URL used to clone the repository.
	// This string will match the URL template from the configuration as closely
	// as possible.
	Actual string

	// The substituted and normalized endpoint template. SCP-style Git URLs
	// are converted to ssh:// URLs.
	Normalized *transport.Endpoint
}

Endpoint represents a Git clone endpoint, resolved from an EndpointTemplate.

type EndpointTemplate

type EndpointTemplate string

EndpointTemplate is template for a Git repository URL.

func (EndpointTemplate) IsMatch

func (t EndpointTemplate) IsMatch(e *transport.Endpoint) bool

IsMatch returns true if may have been derived from the endpoint template.

func (EndpointTemplate) Resolve

func (t EndpointTemplate) Resolve(slug string) (ep Endpoint, err error)

Resolve returns a URL from the template.

func (EndpointTemplate) Validate

func (t EndpointTemplate) Validate() error

Validate returns an error if the template is invalid.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL