Project

gitt

0.0
The project is in a healthy, maintained state
A monadic Object API for the Git CLI.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

~> 1.0
~> 2.6
 Project Readme

Gitt

Provides a monadic Object API to the Git CLI. This project is also an extraction of work originally implemented within the following projects:

This project doesn’t try to encompass the vast feature set natively provided by Git but only exposes the most used features, limited as this may be. Over time, more of the Git feature set will be implemented but it’ll most likely happen slowly and organically. If you are looking for alternatives to this gem, then you might find the following of interest:

Table of Contents
  • Features
  • Requirements
  • Setup
  • Usage
    • Commands
      • Branch
      • Config
      • Log
      • Tag
    • Models
      • Commit
      • Tag
      • Trailer
    • RSpec
      • Git Commit
      • Git Repository
      • Temporary Directory
  • Development
  • Tests
  • License
  • Security
  • Code of Conduct
  • Contributions
  • Versions
  • Community
  • Credits

Features

  • Wraps a subset of native Git commands with additional enhancements to improve your working experience.

  • Answers monads you can pipe together for more complex workflows.

  • Provides optional RSpec shared contexts that speed up the testing of your own Git related implementations.

Requirements

Setup

To set up the project, run:

bin/setup

Usage

At a high level, this project provides a centralized Object API via a single object: Repository. Example:

git = Gitt::Repository.new

git.branch          # Equivalent to `git branch <arguments>`.
git.branch_default  # Answers default branch.
git.branch_name     # Answers current branch.
git.call            # Allows you to run any Git command.
git.commits         # Answers commit records.
git.config          # Equivalent to `git config <arguments>`.
git.exist?          # Answers if current directory is a Git repository or not.
git.get             # Equivalent to `git config get`.
git.log             # Equivalent to `git log <arguments>`.
git.origin?         # Answers if repository has an origin or not.
git.set             # Equivalent to `get config set`.
git.tag             # Equivalent to `git tag <arguments>`.
git.tag?            # Answers if local or remote tag exists.
git.tag_create      # Create a new tag.
git.tag_last        # Answers last tag created.
git.tag_local?      # Answers if local tag exists?
git.tag_remote?     # Answers if remote tag exists?
git.tag_show        # Answers information about a single tag.
git.tagged?         # Answers if the repository has any tags.
git.tags            # Answers all tags.
git.tags_push       # Pushes local tags to remote git.
git.uncommitted     # Parses a file and answers an unsaved commit message.

Commands

Should you want to use individual commands instead of interacting with the Repository object, you can leverage any of the objects in the Commands namespace which — at a minimum — use the Command Pattern. Here are the specific commands which are enhanced further:

Handles branches.

branch = Gitt::Commands::Branch.new

# Answers branch default (via Git `init.defaultBranch` configuration) of if blank.
branch.default  # Success "main"

# Answers branch default fallback if unset or error is detected.
branch.default "source"  # Success "source"

# Accepts any argument you'd send to `git branch`. Example:
branch.call "--list"  # Success "  main\n"

# Answers current branch
branch.name  # Success "major"

Handles global and local configurations.

config = Gitt::Commands::Config.new

# Accepts any argument you'd send to `git config`. Example:
config.call "--get", "rebase.abbreviateCommands"  # Success "true\n"

# Answers value for key with support for fallback value or block manipulation.
config.get "user.name"                                     # Success "Brooke Kuhlmann"
config.get "user.unknown", "fallback"                      # Success "fallback"
config.get("user.unknown") { |value| value + "fallback" }  # "fallback"

# Answers true or false if origin is defined.
config.origin?                                             # true

# Sets configuration key and value.
config.set "user.demo", "test"                             # Success "test"

Handles commit history.

log = Gitt::Commands::Log.new

log.call "--oneline", "-1"  # Success "5e21a9866827 Added documentation\n"

The Log class provides two other methods but they require a more detailed explanation. The first is Log#all which answers an array of commits (records) upon success and accepts the same arguments as given to #call.

commit = log.all

The second, is:

commit log.uncommitted ".git/COMMIT_EDITMSG"

The above will answer a single commit record. This is great for building a commit object from an unsaved commit message. The only disadvantage of this approach is that you will get template commits which are always stripped out by Git when processing a saved commit.

Handles the tagging/versioning of commits.

tag = Gitt::Commands::Tag.new

# Accepts any argument you'd send to `git tag`.
# Example: tag.call "--list"
stdout, stderr, status = tag.call

# Creates a new tag.
tag.create "0.0.0", "Version 0.0.0"

# Answers true or false base on whether local and remote tag exist.
tag.exist? "0.1.0"

# Answers last tag for git.
tag.last

# Answers if local tag exists.
tag.local? "0.1.0"

# Pushes tags to remote git.
tag.push

# Answers if remote tag exists.
tag.remote? "0.1.0"

# Answers details about a specific tag.
tag.show "1.0.0"

# Answers true or false based on whether repository is tagged.
tag.tagged?

Models

In order to have access to rich data from the Git client, there are several models available to you.

Commit

An instance of Gitt::Models::Commit is what is answered back to when using Gitt::Repository via the #commits or #uncommitted methods. In each case, you’ll either get an array of records, a single record, or a failure depending on the result. Here’s an example of a single record:

# #<struct Gitt::Models::Commit
#  author_date_relative="2 days ago",
#  author_email="demo@example.com",
#  author_name="Demo User",
#  body="Necessary to explain recent changes.\n",
#  body_lines=["Necessary to explain recent changes."],
#  body_paragraphs=["Necessary to explain recent changes."],
#  message="Updated documentation with new functionality\n\nNecessary to explain recent changes.\n",
#  sha="5e21a9866827bf5c68bd445ea01b3837a3936b45",
#  subject="Updated documentation with new functionality",
#  trailers=[],
#  trailers_index=nil>

You get a Struct with the following attributes:

  • author_date_relative: Stores the relative date of when the commit was made.

  • author_email: Stores the author email.

  • author_name: Stores the author name.

  • body: Stores the commit body which excludes the subject and leading space.

  • body_lines: Stores each line of the body in an array.

  • body_paragraphs: Stores each paragraph of the body as an array (i.e. broken by double new lines).

  • message: Stores the entire, raw, commit message (i.e. subject and body).

  • sha: Stores the commit SHA.

  • subject: Stores the commit subject.

  • trailers: Stores any commit trailers as an array of Gitt::Models::Trailer records.

  • trailers_index: Stores the starting index of trailers within the commit message.

Tag

An instance of Gitt::Models::Tag is what is answered back to when using Gitt::Repository via the #tag_parse method. Here’s an example:

# #<struct Gitt::Models::Tag
#  author_email="demo@example.com",
#  author_name="Brooke Kuhlmann",
#  authored_at="1671892451",
#  authored_relative_at="1 year ago",
#  body="A demo body.",
#  committed_at="1671997684",
#  committed_relative_at="1 year ago",
#  committer_email="demo@example.com",
#  committer_name="Brooke Kuhlmann",
#  sha="662f32b2846c7bd4f153560478f035197f5279d5",
#  subject="Version 1.0.0",
#  version="1.0.0">

You get a Struct with the following attributes:

  • author_email: Stores author email.

  • author_name: Store author name.

  • authored_at: Stores author creation date.

  • authored_relative_at: Stores author creation date relative to current time.

  • body: Stores body of tag which can be sentences, multiple paragraphs, and/or signature information.

  • committer_email: Stores committer email.

  • committer_name: Store committer name.

  • committed_at: Stores committer creation date.

  • committed_relative_at: Stores committer creation date relative to current time.

  • sha: Stores the commit SHA for which this tag labels

  • subject: Stores the subject.

  • version: Stores the version.

Trailer

A trailer is nested within a commit record when trailer information exists. Example:

#<struct Gitt::Models::Trailer key="Issue", delimiter=":", space=" ", value="123">

The attributes break down as follows:

  • key: Answers the key.

  • delimiter: Answers the delimiter which must be a colon but can be missing if invalid.

  • space: Answers either a space or an empty string with the former being invalid.

  • value: Answers the value associated with the key.

RSpec

For fans of RSpec, this gem provides shared contexts you can use within your own test suites. These shared contexts are optional, not required for you by default, and must be manually required to use.

Git Commit

Provides a default git_commit record of Gitt::Models::Commit with minimal information for testing purposes and can be used as follows:

require "gitt/rspec/shared_contexts/git_commit"

describe Demo do
  include_context "with Git commit"
end

Git Repository

Provides a simple Git repository with a single commit for testing purposes. This repository is set up and torn down around each spec. The repository is built within your project’s tmp directory and provides a git_repo_dir pathname you can interact with. Here’s how to use it:

require "gitt/rspec/shared_contexts/git_repo"
require "refinements/pathname"

describe Demo do
  include_context "with Git repository"

  using Refinements::Pathname

  it "is a demo" do
    git_repo_dir.change_dir { # Your expectation goes here. }
  end
end

Temporary Directory

Provides a temporary directory (i.e. tmp/rspec) for creating directories and or files you want set up and torn down around each spec. Access to the temp_dir pathname is also provided for you. Here’s how to use it:

require "gitt/rspec/shared_contexts/temp_dir"
require "refinements/pathname"

describe Demo do
  include_context "with temporary directory"

  using Refinements::Pathname

  it "is a demo" do
    temp_dir.change_dir { # Your expectation goes here. }
  end
end

💡 The Git Repository shared context — mentioned above — includes this shared context by default so you don’t have to manually include this shared context when using the Git Repository shared context.

Development

To contribute, run:

git clone https://github.com/bkuhlmann/gitt
cd gitt
bin/setup

You can also use the IRB console for direct access to all objects:

bin/console

Tests

To test, run:

bin/rake

Credits