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.10
~> 2.7
~> 12.10
 Project Readme

Gitt

Provides a monadic Object API around the Git CLI with full access to all functionality found when using Git natively. This includes convenience methods for answering fully parsed and enhanced commits, tags, trailers, and so forth. This project is an extraction of work originally implemented within the following projects:

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
  • Developer Certificate of Origin
  • 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.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 enhanced 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.inspect         # Allows you to inspect the current instance.
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.tags            # Answers enhanced tag records.
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_push       # Pushes local tags to remote git.
git.uncommitted     # Parses `COMMIT_EDITMSG` file and answers the unsaved commit message.

Commands

Should you want to use individual commands instead of interacting with the Gitt 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 to this approach is 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 enhanced tag records. Can take any argument accepted with `--list`.
tag.index

# 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 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:0x00015c70
#   author_email = "brooke@alchemists.io",
#   author_name = "Brooke Kuhlmann",
#   authored_at = "1731517717",
#   authored_relative_at = "28 seconds ago",
#   body = "",
#   body_lines = [],
#   body_paragraphs = [],
#   committed_at = "1731517717",
#   committed_relative_at = "28 seconds ago",
#   committer_email = "brooke@alchemists.io",
#   committer_name = "Brooke Kuhlmann",
#   deletions = 11,
#   encoding = "",
#   files_changed = 1,
#   fingerprint = "F2BC49BC4FFB9A48",
#   fingerprint_key = "D1488588D2DEDF73E62F07A1F2BC49BC4FFB9A48",
#   insertions = 14,
#   lines = [
#     "Added version release notes"
#   ],
#   notes = "",
#   raw = "Added version release notes\n",
#   sha = "0f1e2387ed89d6dab95af384096c95bc04b28e9b",
#   signature = "Good",
#   subject = "Added version release notes",
#   trailers = []
# >

You get a Struct with the following attributes:

  • author_email: Stores the author email.

  • author_name: Stores the author name.

  • authored_at: Stores local time of when the commit was made.

  • author_relative_at: Stores the relative time of when the commit was made.

  • 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).

  • committed_at: Stores local time of when the commit was updated.

  • committed_relative_at: Stores the relative time of when the commit was updated.

  • committer_email: Stores the committer email.

  • committer_name: Stores the committer name.

  • deletions: Stores number of deleted lines.

  • encoding: Stored encoding. Blank if UTF-8 and filled if otherwise.

  • files_changed: Stores number of files changed.

  • fingerprint: Stores the fingerprint used when creating a secure commit.

  • fingerprint_key: Stores the fingerprint key used when creating a secure commit.

  • insertions: Stores the number inserted lines.

  • lines: Stores each line of the commit message as an array.

  • notes: Stores commit note (if any.

  • raw: Stores the raw commit message (subject + message).

  • sha: Stores the commit SHA.

  • signature: Stores the signature type and level of security.

  • subject: Stores the commit subject.

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

Tag

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

# #<Struct:Gitt::Models::Tag:0x0003a5c0
#   author_email = "brooke@alchemists.io",
#   author_name = "Brooke Kuhlmann",
#   authored_at = "1671892451",
#   authored_relative_at = "1 year, 11 months ago",
#   body = "* Added Dry Monads gem - Brooke Kuhlmann\n\n-----BEGIN PGP SIGNATURE-----\n",
#   committed_at = "1671997684",
#   committed_relative_at = "1 year, 11 months ago",
#   committer_email = "brooke@alchemists.io",
#   committer_name = "Brooke Kuhlmann",
#   sha = "662f32b2846c7bd4f153560478f035197f5279d5",
#   signature = "-----BEGIN PGP SIGNATURE-----\n",
#   subject = "Version 1.0.0",
#   trailers = [],
#   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.

  • committed_at: Stores committer creation date.

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

  • committer_email: Stores committer email.

  • committer_name: Store committer name.

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

  • signature: Stores the signature when the tag was securely created.

  • subject: Stores the subject.

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

  • 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