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:
- Features
- Requirements
- Setup
- Usage
- Commands
- Branch
- Config
- Log
- Tag
- Models
- Commit
- Tag
- Trailer
- RSpec
- Git Commit
- Git Repository
- Temporary Directory
- Commands
- Development
- Tests
- License
- Security
- Code of Conduct
- Contributions
- Developer Certificate of Origin
- Versions
- Community
- Credits
Features
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 ofGitt::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 ofGitt::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
-
Built with Gemsmith.
-
Engineered by Brooke Kuhlmann.