{"id":18837065,"url":"https://github.com/bkuhlmann/gitt","last_synced_at":"2025-04-14T06:22:04.708Z","repository":{"id":65111624,"uuid":"582127550","full_name":"bkuhlmann/gitt","owner":"bkuhlmann","description":"A monadic Object API for the Git CLI.","archived":false,"fork":false,"pushed_at":"2025-03-25T02:10:42.000Z","size":355,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-13T09:48:28.077Z","etag":null,"topics":["git"],"latest_commit_sha":null,"homepage":"https://alchemists.io/projects/gitt","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bkuhlmann.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.adoc","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["bkuhlmann"]}},"created_at":"2022-12-25T19:42:51.000Z","updated_at":"2025-03-25T02:10:46.000Z","dependencies_parsed_at":"2023-02-19T01:01:39.904Z","dependency_job_id":"b16643e8-96e8-4f4a-a5f7-214113110893","html_url":"https://github.com/bkuhlmann/gitt","commit_stats":{"total_commits":47,"total_committers":1,"mean_commits":47.0,"dds":0.0,"last_synced_commit":"72c19547e1dd5d126bab3c4c3f43a7bf515d41cd"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgitt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgitt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgitt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgitt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bkuhlmann","download_url":"https://codeload.github.com/bkuhlmann/gitt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248830904,"owners_count":21168368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git"],"created_at":"2024-11-08T02:33:42.285Z","updated_at":"2025-04-14T06:22:04.697Z","avatar_url":"https://github.com/bkuhlmann.png","language":"Ruby","funding_links":["https://github.com/sponsors/bkuhlmann"],"categories":[],"sub_categories":[],"readme":":toc: macro\n:toclevels: 5\n:figure-caption!:\n\n:git_link: link:https://git-scm.com[Git]\n:rspec_link: link:https://rspec.info[RSpec]\n:struct_link: link:https://alchemists.io/articles/ruby_structs[Struct]\n\n= Gitt\n\nProvides a monadic Object API around the {git_link} 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:\n\n* link:https://alchemists.io/projects/git-lint[Git Lint]\n* link:https://alchemists.io/projects/milestoner[Milestoner]\n* link:https://alchemists.io/projects/rubysmith[Rubysmith]\n\nIf you are looking for alternatives to this gem, then you might find the following of interest:\n\n* link:https://github.com/ruby-git/ruby-git[Ruby Git]\n* link:https://github.com/libgit2/rugged[Rugged]\n\ntoc::[]\n\n== Features\n\n* Wraps native {git_link} commands with additional enhancements to improve your working experience.\n* Answers link:https://dry-rb.org/gems/dry-monads[monads] you can link:https://alchemists.io/articles/ruby_function_composition[pipe] together for more complex workflows.\n* Provides _optional_ {rspec_link} shared contexts that speed up the testing of your own Git related implementations.\n\n== Requirements\n\n. {git_link}\n. link:https://www.ruby-lang.org[Ruby]\n\n== Setup\n\nTo set up the project, run:\n\n[source,bash]\n----\nbin/setup\n----\n\n== Usage\n\nAt a high level, this project provides a centralized Object API via a single object: `Repository`. Example:\n\n[source,ruby]\n----\ngit = Gitt.new\n\ngit.branch             # Equivalent to `git branch \u003carguments\u003e`.\ngit.branch_default     # Answers default branch.\ngit.branch_name        # Answers current branch.\ngit.call               # Allows you to run any Git command.\ngit.commits            # Answers enhanced commit records.\ngit.config             # Equivalent to `git config \u003carguments\u003e`.\ngit.exist?             # Answers if current directory is a Git repository or not.\ngit.get                # Equivalent to `git config get`.\ngit.inspect            # Allows you to inspect the current instance.\ngit.log                # Equivalent to `git log \u003carguments\u003e`.\ngit.origin?            # Answers if repository has an origin or not.\ngit.set                # Equivalent to `get config set`.\ngit.tag                # Equivalent to `git tag \u003carguments\u003e`.\ngit.tags               # Answers enhanced tag records.\ngit.tag?               # Answers if local or remote tag exists.\ngit.tag_create         # Create a new tag.\ngit.tag_delete_local   # Deletes local tag.\ngit.tag_delete_remote  # Deletes remote tag.\ngit.tag_last           # Answers last tag created.\ngit.tag_local?         # Answers if local tag exists?\ngit.tag_remote?        # Answers if remote tag exists?\ngit.tag_show           # Answers information about a single tag.\ngit.tagged?            # Answers if the repository has any tags.\ngit.tags_push          # Pushes local tags to remote git.\ngit.uncommitted        # Parses `COMMIT_EDITMSG` file and answers the unsaved commit message.\n----\n\n💡 In general, the above (and individual commands below) support link:https://docs.ruby-lang.org/en/master/Process.html#method-c-spawn[Process#spawn] arguments where you can provide environment, command, arguments, and options (hash). Example: `git.tag({\"GIT_COMMITTER_DATE\" =\u003e \"2025-01-01 20:00:00\"}, \"0.0.0\", chdir: \"path/to/repo\")` This allows you to perform advanced operations where you might need to supply environment variables or options like changing directory (as shown in the example). Check the method signatures to learn more.\n\n=== Commands\n\nShould you want to use individual commands instead of interacting with the `Gitt` object, you\ncan leverage any of the objects in the `Commands` namespace which -- at a minimum -- use the link:https://alchemists.io/articles/command_pattern[Command Pattern]. Here are the specific commands which are enhanced further:\n\n==== link:https://git-scm.com/docs/git-branch[Branch]\n\nHandles branches.\n\n[source,ruby]\n----\nbranch = Gitt::Commands::Branch.new\n\n# Answers branch default (via Git `init.defaultBranch` configuration) of if blank.\nbranch.default  # Success \"main\"\n\n# Answers branch default fallback if unset or error is detected.\nbranch.default \"source\"  # Success \"source\"\n\n# Accepts any argument you'd send to `git branch`. Example:\nbranch.call \"--list\"  # Success \"  main\\n\"\n\n# Answers current branch\nbranch.name  # Success \"major\"\n----\n\n==== link:https://git-scm.com/docs/git-config[Config]\n\nHandles global and local configurations.\n\n[source,ruby]\n----\nconfig = Gitt::Commands::Config.new\n\n# Accepts any argument you'd send to `git config`. Example:\nconfig.call \"--get\", \"rebase.abbreviateCommands\"  # Success \"true\\n\"\n\n# Answers value for key with support for fallback value or block manipulation.\nconfig.get \"user.name\"                                     # Success \"Brooke Kuhlmann\"\nconfig.get \"user.unknown\", \"fallback\"                      # Success \"fallback\"\nconfig.get(\"user.unknown\") { |value| value + \"fallback\" }  # \"fallback\"\n\n# Answers true or false if origin is defined.\nconfig.origin?                                             # true\n\n# Sets configuration key and value.\nconfig.set \"user.demo\", \"test\"                             # Success \"test\"\n----\n\n==== link:https://git-scm.com/docs/git-log[Log]\n\nHandles commit history.\n\n[source,ruby]\n----\nlog = Gitt::Commands::Log.new\n\nlog.call \"--oneline\", \"-1\"  # Success \"5e21a9866827 Added documentation\\n\"\n----\n\nThe `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`.\n\n[source,ruby]\n----\ncommit = log.all\n----\n\nThe second, is:\n\n[source,ruby]\n----\ncommit log.uncommitted \".git/COMMIT_EDITMSG\"\n----\n\nThe 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.\n\n==== link:https://git-scm.com/docs/git-tag[Tag]\n\nHandles the tagging/versioning of commits.\n\n[source,ruby]\n----\ntag = Gitt::Commands::Tag.new\n\n# Accepts any argument you'd send to `git tag`.\n# Example: tag.call \"--list\"\nstdout, stderr, status = tag.call\n\n# Creates a new tag.\ntag.create \"0.0.0\", \"Version 0.0.0\"\n\n# Deletes local tag.\ntag.delete_local \"0.0.0\"\n\n# Deletes remote tag.\ntag.delete_remote \"0.0.0\"\n\n# Answers true or false base on whether local and remote tag exist.\ntag.exist? \"0.1.0\"\n\n# Answers enhanced tag records. Can take any argument accepted with `--list`.\ntag.index\n\n# Answers last tag for git.\ntag.last\n\n# Answers if local tag exists.\ntag.local? \"0.1.0\"\n\n# Pushes tags to remote git.\ntag.push\n\n# Answers if remote tag exists.\ntag.remote? \"0.1.0\"\n\n# Answers details about a specific tag.\ntag.show \"1.0.0\"\n\n# Answers true or false based on whether repository is tagged.\ntag.tagged?\n----\n\n=== Models\n\nIn order to have access to rich data from the Git client, there are several models available to you.\n\n==== Commit\n\nAn 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:\n\n[source,ruby]\n----\n# #\u003cStruct:Gitt::Models::Commit:0x00015c70\n#   author_email = \"brooke@alchemists.io\",\n#   author_name = \"Brooke Kuhlmann\",\n#   authored_at = \"1731517717\",\n#   authored_relative_at = \"28 seconds ago\",\n#   body = \"\",\n#   body_lines = [],\n#   body_paragraphs = [],\n#   committed_at = \"1731517717\",\n#   committed_relative_at = \"28 seconds ago\",\n#   committer_email = \"brooke@alchemists.io\",\n#   committer_name = \"Brooke Kuhlmann\",\n#   deletions = 11,\n#   encoding = \"\",\n#   files_changed = 1,\n#   fingerprint = \"F2BC49BC4FFB9A48\",\n#   fingerprint_key = \"D1488588D2DEDF73E62F07A1F2BC49BC4FFB9A48\",\n#   insertions = 14,\n#   lines = [\n#     \"Added version release notes\"\n#   ],\n#   notes = \"\",\n#   raw = \"Added version release notes\\n\",\n#   sha = \"0f1e2387ed89d6dab95af384096c95bc04b28e9b\",\n#   signature = \"Good\",\n#   subject = \"Added version release notes\",\n#   trailers = []\n# \u003e\n----\n\nYou get a {struct_link} with the following attributes:\n\n* `author_email`: Stores the author email.\n* `author_name`: Stores the author name.\n* `authored_at`: Stores local time of when the commit was made.\n* `author_relative_at`: Stores the relative time of when the commit was made.\n* `body`: Stores the commit body which excludes the subject and leading space.\n* `body_lines`: Stores each line of the body in an array.\n* `body_paragraphs`: Stores each paragraph of the body as an array (i.e. broken by double new lines).\n* `committed_at`: Stores local time of when the commit was updated.\n* `committed_relative_at`: Stores the relative time of when the commit was updated.\n* `committer_email`: Stores the committer email.\n* `committer_name`: Stores the committer name.\n* `deletions`: Stores number of deleted lines.\n* `encoding`: Stored encoding. Blank if UTF-8 and filled if otherwise.\n* `files_changed`: Stores number of files changed.\n* `fingerprint`: Stores the fingerprint used when creating a secure commit.\n* `fingerprint_key`: Stores the fingerprint key used when creating a secure commit.\n* `insertions`: Stores the number inserted lines.\n* `lines`: Stores each line of the commit message as an array.\n* `notes`: Stores commit note (if any.\n* `raw`: Stores the raw commit message (subject + message).\n* `sha`: Stores the commit SHA.\n* `signature`: Stores the signature type and level of security.\n* `subject`: Stores the commit subject.\n* `trailers`: Stores trailers as an array of `Gitt::Models::Trailer` records.\n\n==== Tag\n\nAn instance of `Gitt::Models::Tag` is what is answered back to when using `Gitt` via the `#tags` method, for example. Here's an example:\n\n[source,ruby]\n----\n# #\u003cStruct:Gitt::Models::Tag:0x0003a5c0\n#   author_email = \"brooke@alchemists.io\",\n#   author_name = \"Brooke Kuhlmann\",\n#   authored_at = \"1671892451\",\n#   authored_relative_at = \"1 year, 11 months ago\",\n#   body = \"* Added Dry Monads gem - Brooke Kuhlmann\\n\\n-----BEGIN PGP SIGNATURE-----\\n\",\n#   committed_at = \"1671997684\",\n#   committed_relative_at = \"1 year, 11 months ago\",\n#   committer_email = \"brooke@alchemists.io\",\n#   committer_name = \"Brooke Kuhlmann\",\n#   sha = \"662f32b2846c7bd4f153560478f035197f5279d5\",\n#   signature = \"-----BEGIN PGP SIGNATURE-----\\n\",\n#   subject = \"Version 1.0.0\",\n#   trailers = [],\n#   version = \"1.0.0\"\n# \u003e\n----\n\nYou get a {struct_link} with the following attributes:\n\n*  `author_email`: Stores author email.\n*  `author_name`: Store author name.\n*  `authored_at`: Stores author creation date.\n*  `authored_relative_at`: Stores author creation date relative to current time.\n*  `body`: Stores body of tag which can be sentences, multiple paragraphs, and/or signature information.\n*  `committed_at`: Stores committer creation date.\n*  `committed_relative_at`: Stores committer creation date relative to current time.\n*  `committer_email`: Stores committer email.\n*  `committer_name`: Store committer name.\n*  `sha`: Stores the commit SHA for which this tag labels\n*  `signature`: Stores the signature when the tag was securely created.\n*  `subject`: Stores the subject.\n*  `trailers`: Stores trailers as an array of `Gitt::Models::Trailer` records.\n*  `version`: Stores the version.\n\n==== Trailer\n\nA trailer is nested within a commit record when trailer information exists. Example:\n\n[source,ruby]\n----\n#\u003cstruct Gitt::Models::Trailer key=\"Issue\", delimiter=\":\", space=\" \", value=\"123\"\u003e\n----\n\nThe attributes break down as follows:\n\n* `key`: Answers the key.\n* `delimiter`: Answers the delimiter which must be a colon but can be missing if invalid.\n* `space`: Answers either a space or an empty string with the former being invalid.\n* `value`: Answers the value associated with the key.\n\n=== RSpec\n\nFor fans of {rspec_link}, 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.\n\n==== Git Commit\n\nProvides a default `git_commit` record of `Gitt::Models::Commit` with minimal information for testing purposes and can be used as follows:\n\n[source,ruby]\n----\nrequire \"gitt/rspec/shared_contexts/git_commit\"\n\ndescribe Demo do\n  include_context \"with Git commit\"\nend\n----\n\n==== Git Tag\n\nProvides a default `git_tag` record of `Gitt::Models::Tag` with minimal information for testing purposes and can be used as follows:\n\n[source,ruby]\n----\nrequire \"gitt/rspec/shared_contexts/git_tag\"\n\ndescribe Demo do\n  include_context \"with Git tag\"\nend\n----\n\n==== Git Repository\n\nProvides 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:\n\n[source,ruby]\n----\nrequire \"gitt/rspec/shared_contexts/git_repo\"\nrequire \"refinements/pathname\"\n\ndescribe Demo do\n  include_context \"with Git repository\"\n\n  using Refinements::Pathname\n\n  it \"is a demo\" do\n    git_repo_dir.change_dir { # Your expectation goes here. }\n  end\nend\n----\n\n==== Temporary Directory\n\nProvides 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:\n\n[source,ruby]\n----\nrequire \"gitt/rspec/shared_contexts/temp_dir\"\nrequire \"refinements/pathname\"\n\ndescribe Demo do\n  include_context \"with temporary directory\"\n\n  using Refinements::Pathname\n\n  it \"is a demo\" do\n    temp_dir.change_dir { # Your expectation goes here. }\n  end\nend\n----\n\n💡 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.\n\n== Development\n\nTo contribute, run:\n\n[source,bash]\n----\ngit clone https://github.com/bkuhlmann/gitt\ncd gitt\nbin/setup\n----\n\nYou can also use the IRB console for direct access to all objects:\n\n[source,bash]\n----\nbin/console\n----\n\n== Tests\n\nTo test, run:\n\n[source,bash]\n----\nbin/rake\n----\n\n== link:https://alchemists.io/policies/license[License]\n\n== link:https://alchemists.io/policies/security[Security]\n\n== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]\n\n== link:https://alchemists.io/policies/contributions[Contributions]\n\n== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]\n\n== link:https://alchemists.io/projects/gitt/versions[Versions]\n\n== link:https://alchemists.io/community[Community]\n\n== Credits\n\n* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].\n* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkuhlmann%2Fgitt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbkuhlmann%2Fgitt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkuhlmann%2Fgitt/lists"}