Git is a version-control system for tracking changes in computer files and coordinating work on those files among multiple people.
Dexter  Goodwin

Dexter Goodwin


Command Line tool for Generating A Changelog From Git Tags


Command line tool for generating a changelog from git tags and commit history. Used by Modernizr, Netlify, Neutrino and Velocity.js.  


npm install -g auto-changelog


Simply run auto-changelog in the root folder of a git repository. git log is run behind the scenes in order to parse the commit history.

Usage: auto-changelog [options]


  -o, --output [file]                 # output file, default:
  -c, --config [file]                 # config file location, default: .auto-changelog
  -t, --template [template]           # specify template to use [compact, keepachangelog, json], default: compact
  -r, --remote [remote]               # specify git remote to use for links, default: origin
  -p, --package                       # use version from package.json as latest release
  -v, --latest-version [version]      # use specified version as latest release
  -u, --unreleased                    # include section for unreleased changes
  -l, --commit-limit [count]          # number of commits to display per release, default: 3
  -b, --backfill-limit [count]        # number of commits to backfill empty releases with, default: 3
      --commit-url [url]              # override url for commits, use {id} for commit id
      --issue-url [url]               # override url for issues, use {id} for issue id
      --merge-url [url]               # override url for merges, use {id} for merge id
      --compare-url [url]             # override url for compares, use {from} and {to} for tags
      --issue-pattern [regex]         # override regex pattern for issues in commit messages
      --breaking-pattern [regex]      # regex pattern for breaking change commits
      --merge-pattern [regex]         # add custom regex pattern for merge commits
      --ignore-commit-pattern [regex] # pattern to ignore when parsing commits
      --tag-pattern [regex]           # override regex pattern for version tags
      --tag-prefix [prefix]           # prefix used in version tags, default: v
      --starting-version [tag]        # specify earliest version to include in changelog
      --starting-date [yyyy-mm-dd]    # specify earliest date to include in changelog
      --ending-version [tag]          # specify latest version to include in changelog
      --sort-commits [property]       # sort commits by property [relevance, date, date-desc, subject, subject-desc], default: relevance
      --release-summary               # display tagged commit message body as release summary
      --unreleased-only               # only output unreleased changes
      --hide-empty-releases           # hide empty releases
      --hide-credit                   # hide auto-changelog credit
      --handlebars-setup [file]       # handlebars setup file
      --append-git-log [string]       # string to append to git log command
      --append-git-tag [string]       # string to append to git tag command
      --prepend                       # prepend changelog to output file
      --stdout                        # output changelog to stdout
  -V, --version                       # output the version number
  -h, --help                          # output usage information

# Write log to in current directory

# Write log to using keepachangelog template
auto-changelog --output --template keepachangelog

# Disable the commit limit, rendering all commits for every release
auto-changelog --commit-limit false


auto-changelog is designed to be as flexible as possible, providing a clear changelog for any project. There are only two absolute requirements:

  • You should be using git 1.7.2 or later
  • All versions should be tagged using semver tag names – this happens by default when using npm version

There are some less strict requirements to improve your changelog:

What you might do if you’re clever

Install auto-changelog to dev dependencies:

npm install auto-changelog --save-dev
# or
yarn add auto-changelog --dev

Add auto-changelog -p && git add to the version scripts in your package.json:

  "name": "my-awesome-package",
  "version": "1.0.0",
  "devDependencies": {
    "auto-changelog": "*"
  "scripts": {
    "version": "auto-changelog -p && git add"

Using -p or --package uses the version from package.json as the latest release, so that all commits between the previous release and now become part of that release. Essentially anything that would normally be parsed as Unreleased will now come under the version from package.json

Now every time you run npm version, the changelog will automatically update and be part of the version commit.

Advanced Usage

URL Overrides

Links to commits, issues, pull requests and version diffs are automatically generated based on your remote URL. GitHub, GitLab, BitBucket and Azure DevOps are all supported. If you have an unusual remote or need to override one of the link formats, use --commit-url, --issue-url or --merge-url with an {id} token. For custom version diffs, use --compare-url with {from} and {to} tokens.

# Link all issues to redmine
auto-changelog --issue-url{id}

# Link to custom diff page
auto-changelog --compare-url{from}...{to}

Add to an existing changelog

If you’d like to keep an existing changelog below your generated one, just add <!-- auto-changelog-above --> to your current changelog. The generated changelog will be added above this token, and anything below will remain.


You can set any option in package.json under the auto-changelog key, using camelCase options.

  "name": "my-awesome-package",
  "version": "1.0.0",
  "scripts": {
    // ...
  "auto-changelog": {
    "output": "",
    "template": "keepachangelog",
    "unreleased": true,
    "commitLimit": false

You can also store config options in an .auto-changelog file in your project root:

  "output": "",
  "template": "keepachangelog",
  "unreleased": true,
  "commitLimit": false

Note that any options set in package.json will take precedence over any set in .auto-changelog.

Tag prefixes

Use --tag-prefix [prefix] if you prefix your version tags with a certain string:

# When all versions are tagged like my-package/1.2.3
auto-changelog --tag-prefix my-package/

Tag patterns

By default, auto-changelog looks for valid semver tags to build a list of releases. If you are using another format (or want to include all tags), use --tag-pattern [regex]:

# When all versions are tagged like build-12345
auto-changelog --tag-pattern build-\d+

# Include any tag as a release
auto-changelog --tag-pattern .+

Breaking changes

If you use a common pattern in your commit messages for breaking changes, use --breaking-pattern to highlight those commits as breaking changes in your changelog. Breaking change commits will always be listed as part of a release, regardless of any --commit-limit set.

auto-changelog --breaking-pattern "BREAKING CHANGE:"

Custom issue patterns

By default, auto-changelog will parse GitHub-style issue fixes in your commit messages. If you use Jira or an alternative pattern in your commits to reference issues, you can pass in a custom regular expression to --issue-pattern along with --issue-url:

# Parse Jira-style issues in your commit messages, like PROJECT-418
auto-changelog --issue-pattern [A-Z]+-\d+ --issue-url{id}

Or, in your package.json:

  "name": "my-awesome-package",
  "auto-changelog": {
    "issueUrl": "{id}",
    "issuePattern": "[A-Z]+-\d+"

If you use a certain pattern before or after the issue number, like fixes {id}, just use a capturing group:

# "This commit fixes ISSUE-123" will now parse ISSUE-123 as an issue fix
auto-changelog --issue-pattern "[Ff]ixes ([A-Z]+-\d+)"

Custom templates

If you aren’t happy with the default templates or want to tweak something, you can point to a handlebars template in your local repo. Check out the existing templates to see what is possible.

Save changelog-template.hbs somewhere in your repo:

### Changelog
My custom changelog template. Don’t worry about indentation here; it is automatically removed from the output.

{{#each releases}}
  Every release has a {{title}} and a {{href}} you can use to link to the commit diff.
  It also has an {{isoDate}} and a {{niceDate}} you might want to use.
  {{#each merges}}
    - A merge has a {{message}}, an {{id}} and a {{href}} to the PR.
  {{#each fixes}}
    - Each fix has a {{commit}} with a {{commit.subject}}, an {{id}} and a {{href}} to the fixed issue.
  {{#each commits}}
    - Commits have a {{shorthash}}, a {{subject}} and a {{href}}, amongst other things.

Then just use --template to point to your template:

auto-changelog --template changelog-template.hbs

You can also point to an external template by passing in a URL:

auto-changelog --template

To see exactly what data is passed in to the templates, you can generate a JSON version of the changelog:

auto-changelog --template json --output changelog-data.json

commit-list helper

Use {{#commit-list}} to render a list of commits depending on certain patterns in the commit messages:

{{#each releases}}
  ### [{{title}}]({{href}})

  {{! List commits with `Breaking change: ` somewhere in the message }}
  {{#commit-list commits heading='### Breaking Changes' message='Breaking change: '}}
    - {{subject}} [`{{shorthash}}`]({{href}})

  {{! List commits that add new features, but not those already listed above }}
  {{#commit-list commits heading='### New Features' message='feat: ' exclude='Breaking change: '}}
    - {{subject}} [`{{shorthash}}`]({{href}})
headingA heading for the list, only renders if at least one commit matches
messageA regex pattern to match against the entire commit message
subjectA regex pattern to match against the commit subject only
excludeA regex pattern to exclude from the list – useful for avoiding listing commits more than once

Replacing text

To insert links or other markup to PR titles and commit messages that appear in the log, use the replaceText option in your package.json:

  "name": "my-awesome-package",
  "auto-changelog": {
    "replaceText": {
      "(ABC-\\d+)": "[`$1`]($1)"

Here, any time a pattern like ABC-123 appears in your log, it will be replaced with a link to the relevant issue in Jira. Each pattern is applied using string.replace(new RegExp(key, 'g'), value).

Handlebars setup file

The --handlebars-setup options allows you to point to a file to add custom Handlebars helpers, for use in custom templates using --template. Paths are relative to the directory in which you run auto-changelog.

auto-changelog --handlebars-setup setup.js --template custom-template.hbs

// setup.js
module.exports = function (Handlebars) {
  Handlebars.registerHelper('custom', function (context, options) {
    return 'custom helpers!'

// custom-template.hbs
Now you can use {{custom}}


What’s a changelog?


What does this do?

The command parses your git commit history and generates a changelog based on tagged versions, merged pull requests and closed issues. See a simple example in this very repo.

Why do I need it?

Because keeping a changelog can be tedious and difficult to get right. If you don’t have the patience for a hand-crafted, bespoke changelog then this makes keeping one rather easy. It also can be automated if you’re feeling extra lazy.

Download Details:

Author: Cookpete
Source Code: 
License: MIT license

#javascript #git #tags 

Command Line tool for Generating A Changelog From Git Tags
Dexter  Goodwin

Dexter Goodwin


Git-promise: Simple Wrapper to Run any Git Command


Simple wrapper that allows you to run any git command using a more intuitive syntax.


Please be cautious and aware of potential command injection vulnerabilities that will become an attack vector if user input flows unsanitized and uncontrolled into the git() function call.

For example:

const git = require("git-promise");
git("fetch origin --upload-pack=touch    /tmp/abcd", {cwd: '/tmp/example-git-repo'}).then((output) => console.log(output))

or consider the following input

const git = require("git-promise");
git("fetch origin --upload-pack=touch${IFS}/tmp/abcd-new", {cwd: '/tmp/example-git-repo'}).then((output) => console.log(output))

both of these serve as an example where user input will result in command injection attacks that create a new empty file at /tmp/abcd or /tmp/abcd-new.

See original security disclosure report for further context.

Getting Started

npm install git-promise --save

Once installed, you can use it in your JavaScript files like so:

const git = require("git-promise");

const branch = await git("rev-parse --abbrev-ref HEAD");
console.log(branch); // This is your current branch

The module will handle git exit code automatically, so

const git = require("git-promise");

try {
  await git("merge origin/master");
  // Everything was fine
} catch (err) {
  // Something went bad, maybe merge conflict?

err is an Error object augmented with code property. The following code:

try {
  await git('clone');
} catch (err) {
  console.log("ERROR CODE");

will log:

Cloning into 'notExistingExample'...
fatal: remote error: Repository does not exist
The requested repository does not exist, or you do not have permission to
access it.

Advanced usage

The git command accepts a second parameter that can be used to parse the output or to deal with non 0 exit code.

const git = require("git-promise");

const branch = await git("status -sb",
  (stdout) => stdout.match(/## (.*)/)[1]);
console.log(branch); // This is your current branch

The callback accepts 2 parameters, (stdout, error), where stdout is the output of the git command and error is either null or an Error in case the git command fails.

The return value of this function will be the resolved value of the promise.

If the error parameter is not specified, it'll be handled automatically and the promise will be rejected in case of non 0 error codes.

const git = require("git-promise");

git("merge-base --is-ancestor master HEAD", function (stdout, error) {
  if (!error) {
    // the branch we are on is fast forward to master
    return true;
  } else if (error.code === 1) {
    // no, it's not
    return false;
  } else {
    // some other error happened
    throw error;
}).then(function (isFastForward) {
}).catch(function (err) {
  // deal with the error

Argument parsing

Version 1.0 changes the way the input command is parsed, so instead of executing anything that gets passed as the first parameter, it makes sure that git is the only executable used.

git("status | grep hello") won't be executed as a shell command, but everything will be passed as arguments to git, likely resulting in an error in this specific case.

If your git command stops working after upgrading to version 1.0

  1. Make sure you're only executing git commands.
  2. Try passing an array of arguments instead of a string. For instance: git(["merge-base", "--is-ancestor", "master", "HEAD"]);.

Chaining commands

Imagine to be on a local branch which is not fast forward with master and you want to know which commit were pushed on master after the forking point:

const git = require("git-promise");

function findForkCommit () {
  return git("merge-base master HEAD", output => output.trim());

function findChanges (forkCommit) {
  return git("log " + forkCommit + "..master --format=oneline",
    output => output.trim().split("\n"));

const forkCommit = await findForkCommit();
const commits = await findChanges(forkCommit);

Working directory

By default all git commands run in the current working directory (i.e. process.cwd()).

You can use the following syntax to run a git command in different folder

const git = require("git-promise");

await git("blame file1.js", {cwd: "src/"});

Custom git executable

By default any command tries to use git in $PATH, if you have installed git in a funky location you can override this value using gitExec.

const git = require("git-promise");

await git("status", {gitExec: "/usr/local/sbin/git"});

Utility methods

This module comes with some utility methods to parse the output of some git commands

const util = require("git-promise/util");
  • util.extractStatus(output [, lineSeparator])

Parse the output of git status --porcelain and returns an object with

  branch: "current branch name, only if git status -b is used",
  index: {
    modified: ["list of files modified in the index"],
    added: ["list of files added in the index"],
    deleted: ["list of files deleted in the index"],
    renamed: ["list of files renamed in the index"],
    copied: ["list of files copied in the index"]
  workingTree: {
    modified: ["list of files modified in the local working tree"],
    added: ["list of files added / renamed / copied in the local working tree"],
    deleted: ["list of files deleted in the local working tree"]

The method works both with or without option -z.

  • util.hasConflict(output)

Try to determine if there's a merge conflict from the output of git merge-tree

const git = require("git-promise");
const util = require("git-promise/util");

git("merge-tree <root-commit> <branch1> <branch2>").then(function (stdout) {

Release History

  • 1.0.0 BREAKING CHANGE: The returned value is now a standard JavaScript Promise, not anymore a Q promise. BREAKING CHANGE: Internally the library switches from shell to execFile to avoid problems with non sanitized input commands. BREAKING CHANGE: Callbacks using 2 parameters now receive an error as second parameter instead of an error code.
  • 0.3.1 Fix current working directory not switching back when command exits with error
  • 0.3.0 Custom git executable with gitExec option
  • 0.2.0 Change current working directory
  • 0.1.0 Just started

Download Details:

Author: Piuccio
Source Code: 
License: MIT license

#javascript #git 

Git-promise: Simple Wrapper to Run any Git Command

Git-lfs: Git Extension for Versioning Large Files

Git Large File Storage

Git LFS is a command line extension and specification for managing large files with Git.

The client is written in Go, with pre-compiled binaries available for Mac, Windows, Linux, and FreeBSD. Check out the website for an overview of features.

Getting Started


You can install the Git LFS client in several different ways, depending on your setup and preferences.

  • Linux users. Debian and RPM packages are available from PackageCloud.
  • macOS users. Homebrew bottles are distributed, and can be installed via brew install git-lfs.
  • Windows users. Git LFS is included in the distribution of Git for Windows. Alternatively, you can install a recent version of Git LFS from the Chocolatey package manager.
  • Binary packages. In addition, binary packages are available for Linux, macOS, Windows, and FreeBSD.
  • Building from source. This repository can also be built from source using the latest version of Go, and the available instructions in our Wiki.

Note that Debian and RPM packages are built for all OSes for amd64 and i386. For arm64, only Debian packages for the latest Debian release are built due to the cost of building in emulation.


From binary

The binary packages include a script which will:

  • Install Git LFS binaries onto the system $PATH
  • Run git lfs install to perform required global configuration changes.
$ ./

From source

  • Ensure you have the latest version of Go, GNU make, and a standard Unix-compatible build environment installed.
  • On Windows, install goversioninfo with go install
  • Run make.
  • Place the git-lfs binary, which can be found in bin, on your system’s executable $PATH or equivalent.
  • Git LFS requires global configuration changes once per-machine. This can be done by running:
$ git lfs install

Verifying releases

Releases are signed with the OpenPGP key of one of the core team members. To get these keys, you can run the following command, which will print them to standard output:

$ curl -L | tar -Ozxf -

Once you have the keys, you can download the sha256sums.asc file and verify the file you want like so:

$ gpg -d sha256sums.asc | grep git-lfs-linux-amd64-v2.10.0.tar.gz | shasum -a 256 -c

For the convenience of distributors, we also provide a wider variety of signed hashes in the hashes.asc file. Those hashes are in the tagged BSD format, but can be verified with Perl's shasum or the GNU hash utilities, just like the ones in sha256sums.asc.

Example Usage

To begin using Git LFS within a Git repository that is not already configured for Git LFS, you can indicate which files you would like Git LFS to manage. This can be done by running the following from within a Git repository:

$ git lfs track "*.psd"

(Where *.psd is the pattern of filenames that you wish to track. You can read more about this pattern syntax here).

Note: the quotation marks surrounding the pattern are important to prevent the glob pattern from being expanded by the shell.

After any invocation of git-lfs-track(1) or git-lfs-untrack(1), you must commit changes to your .gitattributes file. This can be done by running:

$ git add .gitattributes
$ git commit -m "track *.psd files using Git LFS"

You can now interact with your Git repository as usual, and Git LFS will take care of managing your large files. For example, changing a file named my.psd (tracked above via *.psd):

$ git add my.psd
$ git commit -m "add psd"

Tip: if you have large files already in your repository's history, git lfs track will not track them retroactively. To migrate existing large files in your history to use Git LFS, use git lfs migrate. For example:

$ git lfs migrate import --include="*.psd" --everything

Note that this will rewrite history and change all of the Git object IDs in your repository, just like the export version of this command.

For more information, read git-lfs-migrate(1).

You can confirm that Git LFS is managing your PSD file:

$ git lfs ls-files
3c2f7aedfb * my.psd

Once you've made your commits, push your files to the Git remote:

$ git push origin main
Uploading LFS objects: 100% (1/1), 810 B, 1.2 KB/s
# ...
   67fcf6a..47b2002  main -> main

Note: Git LFS requires at least Git 1.8.2 on Linux or 1.8.5 on macOS.


If you've decided that Git LFS isn't right for you, you can convert your repository back to a plain Git repository with git lfs migrate as well. For example:

$ git lfs migrate export --include="*.psd" --everything

Note that this will rewrite history and change all of the Git object IDs in your repository, just like the import version of this command.

If there's some reason that things aren't working out for you, please let us know in an issue, and we'll definitely try to help or get it fixed.


Git LFS maintains a list of currently known limitations, which you can find and edit here.

Git LFS source code utilizes Go modules in its build system, and therefore this project contains a go.mod file with a defined Go module path. However, we do not maintain a stable Go language API or ABI, as Git LFS is intended to be used solely as a compiled binary utility. Please do not import the git-lfs module into other Go code and do not rely on it as a source code dependency.

Need Help?

You can get help on specific commands directly:

$ git lfs help <subcommand>

The official documentation has command references and specifications for the tool. There's also a FAQ on the wiki which answers some common questions.

If you have a question on how to use Git LFS, aren't sure about something, or are looking for input from others on tips about best practices or use cases, feel free to start a discussion.

You can always open an issue, and one of the Core Team members will respond to you. Please be sure to include:

  1. The output of git lfs env, which displays helpful information about your Git repository useful in debugging.
  2. Any failed commands re-run with GIT_TRACE=1 in the environment, which displays additional information pertaining to why a command crashed.


See for info on working on Git LFS and sending patches. Related projects are listed on the Implementations wiki page.

See also for info on how to submit reports of security vulnerabilities.

Download Details:

Author: git-lfs
Source Code: 
License: View license

#go #golang #git 

Git-lfs: Git Extension for Versioning Large Files

S3git: Git for Cloud Storage (or Version Control For Data)

s3git: git for Cloud Storage (or Version Control for Data)

s3git applies the git philosophy to Cloud Storage. If you know git, you will know how to use s3git!

s3git is a simple CLI tool that allows you to create a distributed, decentralized and versioned repository. It scales limitlessly to 100s of millions of files and PBs of storage and stores your data safely in S3. Yet huge repos can be cloned on the SSD of your laptop for making local changes, committing and pushing back.

Exactly like git, s3git does not require any server-side components, just download and run the executable. It imports the golang package s3git-go that can be used from other applications as well. Or see the Python module or Ruby gem.

Use cases for s3git

  • Build and Release Management (see example with all Kubernetes releases).
  • DevOps Scenarios
  • Data Consolidation
  • Analytics
  • Photo and Video storage

See use cases for a detailed description of these use cases.

Download binaries

DISCLAIMER: These are PRE-RELEASE binaries -- use at your own peril for now


Download s3git from

$ mkdir s3git && cd s3git
$ wget -q -O s3git
$ chmod +x s3git
$ export PATH=$PATH:${PWD}   # Add current dir where s3git has been downloaded to
$ s3git


Download s3git from

$ mkdir s3git && cd s3git
$ wget -q -O s3git
$ chmod +x s3git
$ export PATH=$PATH:${PWD}   # Add current dir where s3git has been downloaded to
$ s3git


Download s3git.exe from

C:\Users\Username\Downloads> s3git.exe

Building from source

Build instructions are as follows (see install golang for setting up a working golang environment):

$ go get -d
$ cd $GOPATH/src/ 
$ go install
$ s3git

BLAKE2 Tree Hashing and Storage Format

Read here how s3git uses the BLAKE2 Tree hashing mode for both deduplicated and hydrated storage (and here for info for BLAKE2 at scale).

Example workflow

Here is a simple workflow to create a new repository and populate it with some data:

$ mkdir s3git-repo && cd s3git-repo
$ s3git init
Initialized empty s3git repository in ...
$ # Just stream in some text
$ echo "hello s3git" | s3git add
Added: 18e622875a89cede0d7019b2c8afecf8928c21eac18ec51e38a8e6b829b82c3ef306dec34227929fa77b1c7c329b3d4e50ed9e72dc4dc885be0932d3f28d7053
$ # Add some more files
$ s3git add "*.mp4"
$ # Commit and log
$ s3git commit -m "My first commit"
$ s3git log --pretty

Push to cloud storage

$ # Add remote back end and push to it
$ s3git remote add "primary" -r s3://s3git-playground -a "AKIAJYNT4FCBFWDQPERQ" -s "OVcWH7ZREUGhZJJAqMq4GVaKDKGW6XyKl80qYvkW"
$ s3git push
$ # Read back content
$ s3git cat 18e6
hello s3git

Note: Do not store any important info in the s3git-playground bucket. It will be auto-deleted within 24-hours.

Directory versioning

You can also use s3git for directory versioning. This allows you to 'capture' changes coherently all the way down from a directory and subsequently go back to previous versions of the full state of the directory (and not just any file). Think of it as a Time Machine for directories instead of individual files.

So instead of 'saving' a directory by making a full copy into 'MyFolder-v2' (and 'MyFolder-v3', etc.) you capture the state of a directory and give it a meaningful message ("Changed color to red") as version so it is always easy to go back to the version you are looking for.

In addition you can discard any uncommitted changes that you made and go back to the last version that you have captured, which basically means you can (after committing) mess around in a directory and then be rest assured that you can always go back to its original state.

If you push your repository into the cloud then you will have an automatic backup and additionally you can easily collaborate with other people.

Lastly, it works of course with huge binary data too, so not just for text files as in the following 'demo' example:

$ mkdir dir-versioning && cd dir-versioning
$ s3git init .
$ # Just create a single file
$ echo "First line" > text.txt && ls -l
-rw-rw-r-- 1 ec2-user ec2-user 11 May 25 09:06 text.txt
$ #
$ # Create initial snapshot
$ s3git snapshot create -m "Initial snapshot" .
$ # Add new line to initial file and create another file
$ echo "Second line" >> text.txt && echo "Another file" > text2.txt && ls -l
-rw-rw-r-- 1 ec2-user ec2-user 23 May 25 09:08 text.txt
-rw-rw-r-- 1 ec2-user ec2-user 13 May 25 09:08 text2.txt
$ s3git snapshot status .
     New: /home/ec2-user/dir-versioning/text2.txt
Modified: /home/ec2-user/dir-versioning/text.txt
$ #
$ # Create second snapshot
$ s3git snapshot create -m "Second snapshot" .
$ s3git log --pretty
3a4c3466264904fed3d52a1744fb1865b21beae1a79e374660aa231e889de41191009afb4795b61fdba9c156 Second snapshot
77a8e169853a7480c9a738c293478c9923532f56fcd02e3276142a1a29ac7f0006b5dff65d5ca245255f09fa Initial snapshot
$ more text.txt
First line
Second line
$ more text2.txt
Another file
$ #
$ # Go back one version in time
$ s3git snapshot checkout . HEAD^
$ more text.txt
First line
$ more text2.txt
text2.txt: No such file or directory
$ #
$ # Switch back to latest revision
$ s3git snapshot checkout .
$ more text2.txt
Another file

Note that snapshotting works for all files in the directory including any subdirectories. Click the following link for a more elaborate repository that includes all releases of the Kubernetes project.

Clone the YFCC100M dataset

Clone a large repo with 100 million files totaling 11.5 TB in size (Multimedia Commons), yet requiring only 7 GB local disk space.

(Note that this takes about 7 minutes on an SSD-equipped MacBook Pro with 500 Mbit/s download connection so for less powerful hardware you may want to skip to the next section (or if you lack 7 GB local disk space, try a df -h . first). Then again it is quite a few files...)

$ s3git clone s3://s3git-100m -a "AKIAI26TSIF6JIMMDSPQ" -s "5NvshAhI0KMz5Gbqkp7WNqXYlnjBjkf9IaJD75x7"
Cloning into ...
Done. Totaling 97,974,749 objects.
$ cd s3git-100m
$ # List all files starting with '123456'
$ s3git ls 123456
$ s3git cat cafebad > olympic.jpg
$ # List and count total nr of files
$ s3git ls | wc -l

Fork that repo

Below is an example for alice and bob working together on a repository.

$ mkdir alice && cd alice
alice $ s3git clone s3://s3git-spoon-knife -a "AKIAJYNT4FCBFWDQPERQ" -s "OVcWH7ZREUGhZJJAqMq4GVaKDKGW6XyKl80qYvkW"
Cloning into .../alice/s3git-spoon-knife
Done. Totaling 0 objects.
alice $ cd s3git-spoon-knife
alice $ # add a file filled with zeros
alice $ dd if=/dev/zero count=1 | s3git add
Added: 3ad6df690177a56092cb1ac7e9690dcabcac23cf10fee594030c7075ccd9c5e38adbaf58103cf573b156d114452b94aa79b980d9413331e22a8c95aa6fb60f4e
alice $ # add 9 more files (with random content)
alice $ for n in {1..9}; do dd if=/dev/urandom count=1 | s3git add; done
alice $ # commit
alice $ s3git commit -m "Commit from alice"
alice $ # and push
alice $ s3git push

Clone it again as bob on a different computer/different directory/different universe:

$ mkdir bob && cd bob
bob $ s3git clone s3://s3git-spoon-knife -a "AKIAJYNT4FCBFWDQPERQ" -s "OVcWH7ZREUGhZJJAqMq4GVaKDKGW6XyKl80qYvkW"
Cloning into .../bob/s3git-spoon-knife
Done. Totaling 10 objects.
bob $ cd s3git-spoon-knife
bob $ # Check if we can access our empty file
bob $ s3git cat 3ad6 | hexdump
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
bob $ # add another 10 files
bob $ for n in {1..10}; do dd if=/dev/urandom count=1 | s3git add; done
bob $ # commit
bob $ s3git commit -m "Commit from bob"
bob $ # and push back
bob $ s3git push

Switch back to alice again to pull the new content:

alice $ s3git pull
Done. Totaling 20 objects.
alice $ s3git log --pretty
3f67a4789e2a820546745c6fa40307aa490b7167f7de770f118900a28e6afe8d3c3ec8d170a19977cf415d6b6c5acb78d7595c825b39f7c8b20b471a84cfbee0 Commit from bob
a48cf36af2211e350ec2b05c98e9e3e63439acd1e9e01a8cb2b46e0e0d65f1625239bd1f89ab33771c485f3e6f1d67f119566523a1034e06adc89408a74c4bb3 Commit from alice

Note: Do not store any important info in the s3git-spoon-knife bucket. It will be auto-deleted within 24-hours.

Here is an nice screen recording:


Happy forking!

You may be wondering about concurrent behaviour from

Integration with Minio

Instead of S3 you can happily use the Minio server, for example the public server at Just make sure you have a bucket created using mc (example below uses s3git-test):

$ mkdir minio-test && cd minio-test
$ s3git init 
$ s3git remote add "primary" -r s3://s3git-test -a "Q3AM3UQ867SPQQA43P2F" -s "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" -e ""
$ echo "hello minio" | s3git add
Added: c7bb516db796df8dcc824aec05db911031ab3ac1e5ff847838065eeeb52d4410b4d57f8df2e55d14af0b7b1d28362de1176cd51892d7cbcaaefb2cd3f616342f
$ s3git commit -m "Commit for minio test"
$ s3git push
Pushing 1 / 1 [==============================================================================================================================] 100.00 % 0

and clone it

$ s3git clone s3://s3git-test -a "Q3AM3UQ867SPQQA43P2F" -s "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" -e ""
Cloning into .../s3git-test
Done. Totaling 1 object.
$ cd s3git-test/
$ s3git ls
$ s3git cat c7bb
hello minio
$ s3git log --pretty
6eb708ec7dfd75d9d6a063e2febf16bab3c7a163e203fc677c8a9178889bac012d6b3fcda56b1eb160b1be7fa56eb08985422ed879f220d42a0e6ec80c5735ea Commit for minio test


Contributions are welcome! Please see

Key features

Easy: Use a workflow and syntax that you already know and love

Fast: Lightning fast operation, especially on large files and huge repositories

Infinite scalability: Stop worrying about maximum repository sizes and have the ability to grow indefinitely

Work from local SSD: Make a huge cloud disk appear like a local drive

Instant sync: Push local changes and pull down instantly on other clones

Versioning: Keep previous versions safe and have the ability to undo or go back in time

Forking: Ability to make many variants by forking

Verifiable: Be sure that you have everything and be tamper-proof (“data has not been messed with”)

Deduplication: Do not store the same data twice

Simplicity: Simple by design and provide one way to accomplish tasks

Command Line Help

$ s3git help
s3git applies the git philosophy to Cloud Storage. If you know git, you will know how to use s3git.

s3git is a simple CLI tool that allows you to create a distributed, decentralized and versioned repository.
It scales limitlessly to 100s of millions of files and PBs of storage and stores your data safely in S3.
Yet huge repos can be cloned on the SSD of your laptop for making local changes, committing and pushing back.

  s3git [command]

Available Commands:
  add         Add stream or file(s) to the repository
  cat         Read a file from the repository
  clone       Clone a repository into a new directory
  commit      Commit the changes in the repository
  init        Create an empty repository
  log         Show commit log
  ls          List files in the repository
  pull        Update local repository
  push        Update remote repositories
  remote      Manage remote repositories
  snapshot    Manage snapshots
  status      Show changes in repository

  -h, --help[=false]: help for s3git

Use "s3git [command] --help" for more information about a command.


Q Is s3git compatible to git at the binary level?
A No. git is optimized for text content with very nice and powerful diffing and using compressed storage whereas s3git is more focused on large repos with primarily non-text blobs backed up by cloud storage like S3.
Q Do you support encryption?
A No. However it is trivial to encrypt data before streaming into s3git add, eg pipe it through openssl enc or similar.
Q Do you support zipping?
A No. Again it is trivial to zip it before streaming into s3git add, eg pipe it through zip -r - . or similar.
Q Why don't you provide a FUSE interface?
A Supporting FUSE would mean introducing a lot of complexity related to POSIX which we would rather avoid.

Download Details:

Author: s3git
Source Code: 
License: Apache-2.0 license

#go #golang #git #decentralized 

S3git: Git for Cloud Storage (or Version Control For Data)
Sasha  Roberts

Sasha Roberts


Rugged: A Library for Accessing Libgit2 in Ruby


libgit2 bindings in Ruby

Rugged is A Library for Accessing Libgit2 in Ruby. It gives you the speed and portability of libgit2 with the beauty of the Ruby language.


libgit2 is a pure C implementation of the Git core methods. It's designed to be fast and portable. For more information about libgit2, check out libgit2's website or browse the libgit2 organization on GitHub.


Rugged is a self-contained gem. You can install it by running:

$ gem install rugged


You need to have CMake and pkg-config installed on your system to be able to build the included version of libgit2.

Debian, Including Ubuntu

All Debian-derived Linux distros provide apt:

$ sudo apt install libgit2-dev cmake pkg-config

Note that you only need libgit2-dev if you want to build with the system libgit2 rather than the vendored version. In this case, note that the major and minor versions of libgit2 and rugged must match.


On OS X, after installing Homebrew, you can get the required packages with:

$ brew install cmake pkg-config

Please follow the above in case installation of the gem fails with ERROR: CMake is required to build Rugged..


If you want to build Rugged with HTTPS and SSH support, check out the list of optional libgit2 dependencies.

To install rugged with SSH support ensure you have the LibSSH2 library present, then pass the required CMAKE_FLAGS:

CMAKE_FLAGS='-DUSE_SSH=ON' gem install rugged

Or pass the --with-ssh build option:

gem install rugged -- --with-ssh

If you're using bundler and want to bundle libgit2 with Rugged, you can use the :submodules option:

gem 'rugged', git: 'git://', submodules: true

If you would like to bundle rugged with SSH support add the --with-ssh build option to the bundler config:

bundle config build.rugged --with-ssh


To load Rugged, you'll usually want to add something like this:

require 'rugged'

Use the system provided libgit2

By default, Rugged builds and uses a bundled version of libgit2. If you want to use the system library instead, you can install rugged as follows:

gem install rugged -- --use-system-libraries

Or if you are using bundler:

bundle config build.rugged --use-system-libraries
bundle install

However, note that Rugged does only support specific versions of libgit2.


Rugged gives you access to the many parts of a Git repository. You can read and write objects, walk a tree, access the staging area, and lots more. Let's look at each area individually.



The repository is naturally central to Git. Rugged has a Repository class that you can instantiate with a path to open an existing repository :

repo ='path/to/my/repository')
# => #<Rugged::Repository:2228536260 {path: "path/to/my/repository/.git/"}>

You can create a new repository with init_at. Add a second parameter :bare to make a bare repository:

Rugged::Repository.init_at('.', :bare)

You can also let Rugged discover the path to the .git directory if you give it a subdirectory."/Users/me/projects/repo/lib/subdir/")
# => "/Users/me/projects/repo/.git/"

Once your Repository instantiated (in the following examples, as repo), you can access or modify it.

Accessing a Repository

# Does the given SHA1 exist in this repository?
# => true

# Boolean repository state values:
# => false
# => true
# => false
# => false

# Path accessors
# => "path/to/my/repository/.git/"
# => "path/to/my/repository/"

# The HEAD of the repository.
ref = repo.head
# => #<Rugged::Reference:2228467240 {name: "refs/heads/master", target:  #<Rugged::Commit:2228467250 {message: "helpful message", tree: #<Rugged::Tree:2228467260 {oid: 5d6f29220a0783b8085134df14ec4d960b6c3bf2}>}>

# From the returned ref, you can also access the `name`, `target`, and target SHA:
# => "refs/heads/master"
# => #<Rugged::Commit:2228467250 {message: "helpful message", tree: #<Rugged::Tree:2228467260 {oid: 5d6f29220a0783b8085134df14ec4d960b6c3bf2}>}>
# => "2bc6a70483369f33f641ca44873497f13a15cde5"

# Reading an object
object ='a0ae5566e3c8a3bddffab21022056f0b5e03ef07')
# => #<Rugged::OdbObject:0x109a64780>
# => 237
# => "tree 76f23f186076fc291742816721ea8c3e95567241\nparent 8e3c5c52b8f29da0adc7e8be8a037cbeaea6de6b\nauthor Vicent Mart\303\255 <> 1333859005 +0200\ncommitter Vicent Mart\303\255 <> 1333859005 +0200\n\nAdd `Repository#blob_at`\n"
# => :commit

Writing to a Repository

There's a few ways to write to a repository. To write directly from your instantiated repository object:

sha = repo.write(content, type)

You can also use the Commit object directly to craft a commit; this is a bit more high-level, so it may be preferable:

oid = repo.write("This is a blob.", :blob)
index = repo.index
index.add(:path => "", :oid => oid, :mode => 0100644)

options = {}
options[:tree] = index.write_tree(repo)

options[:author] = { :email => "", :name => 'Test Author', :time => }
options[:committer] = { :email => "", :name => 'Test Author', :time => }
options[:message] ||= "Making a commit via Rugged!"
options[:parents] = repo.empty? ? [] : [ ].compact
options[:update_ref] = 'HEAD'

Rugged::Commit.create(repo, options)


Object is the main object class - it shouldn't be created directly, but all of these methods should be useful in their derived classes.

obj = repo.lookup(sha)
obj.oid  # object sha
obj.type # One of :commit, :tree, :blob or :tag

robj = obj.read_raw
str  =
int  = robj.len

There are four base object types in Git: blobs, commits, tags, and trees. Each of these object types have a corresponding class within Rugged.

Commit Objects

commit = repo.lookup('a0ae5566e3c8a3bddffab21022056f0b5e03ef07')
# => #<Rugged::Commit:2245304380>

# => "Add `Repository#blob_at`\n"

# => Sat Apr 07 21:23:25 -0700 2012
# => {:email=>"", :name=>"Vicent Mart\303\255", :time=>Sun Apr 08 04:23:25 UTC 2012}

# => #<Rugged::Tree:2245269740>

# => [#<Rugged::Commit:2245264600 {message: "Merge pull request #47 from isaac/remotes\n\nAdd Rugged::Repository#remotes", tree: #<Rugged::Tree:2245264240 {oid: 6a2aee58a41fa007d07aa55565e2231f9b39b4a9}>]

You can also write new objects to the database this way:

author = {:email=>"", :time=>, :name=>"Vicent Mart\303\255"}

    :author => author,
    :message => "Hello world\n\n",
    :committer => author,
    :parents => ["2cb831a8aea28b2c1b9c63385585b864e4d3bad1"],
    :tree => some_tree,
    :update_ref => "HEAD") #=> "f148106ca58764adc93ad4e2d6b1d168422b9796"

Tag Objects

tag  = repo.lookup(tag_sha)

object =
sha    =
str    = tag.target_type # :commit, :tag, :blob
str    =        # "v1.0"
str    = tag.message
person = tag.tagger

Tree Objects

tree = repo.lookup('779fbb1e17e666832773a9825875300ea736c2da')
# => #<Rugged::Tree:2245194360>

# number of tree entries

tree[0]           # or...
tree.first        # or...
# => {:type=>:blob, :oid=>"99e7edb53db9355f10c6f2dfaa5a183f205d93bf", :filemode=>33188, :name=>".gitignore"}

The tree object is an Enumerable, so you can also do stuff like this:

tree.each { |e| puts e[:oid] }
tree.sort { |a, b| a[:oid] <=> b[:oid] }.map { |e| e[:name] }.join(':')

And there are some Rugged-specific methods, too:

tree.each_tree { |entry| puts entry[:name] }  # list subdirs
tree.each_blob { |entry| puts entry[:name] }  # list only files

You can also write trees with the TreeBuilder:

oid = repo.write("This is a blob.", :blob)
builder =
builder << { :type => :blob, :name => "", :oid => oid, :filemode => 0100644 }

options = {}
options[:tree] = builder.write

options[:author] = { :email => "", :name => 'Test Author', :time => }
options[:committer] = { :email => "", :name => 'Test Author', :time => }
options[:message] ||= "Making a commit via Rugged!"
options[:parents] = repo.empty? ? [] : [ ].compact
options[:update_ref] = 'HEAD'

Rugged::Commit.create(repo, options)

Blob Objects

Blob objects represent the data in the files of a Tree Object.

blob = repo.lookup('e1253910439ea902cf49be8a9f02f3c08d89ac73')
blob.content # => Gives you the content of the blob.

Streaming Blob Objects

There is currently no way to stream data from a blob, because libgit2 itself does not (yet) support streaming blobs out of the git object database. While there are hooks and interfaces for supporting it, the default file system backend always loads the entire blob contents into memory.

If you need to access a Blob object through an IO-like API, you can wrap it with the StringIO class. Note that the only advantage here is a stream-compatible interface, the complete blob object will still be loaded into memory. Below is an example for streaming a Blob using the Sinatra framework:

# Sinatra endpoint
get "/blobs/:sha" do
  repo =
  blob = repo.lookup params[:sha]

    "Vary" => "Accept",
    "Connection" => "keep-alive",
    "Transfer-Encoding" => "chunked",
    "Content-Type" => "application/octet-stream",

  stream do |out| do |chunk|
      out << chunk

Commit Walker

Rugged::Walker is a class designed to help you traverse a set of commits over a repository.

You first push head SHAs onto the walker, and then call next to get a list of the reachable commit objects one at a time. You can also hide() commits if you are not interested in anything beneath them (useful in situations like when you're running something like git log master ^origin/master).

walker =
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE) # optional
walker.each { |c| puts c.inspect }

Index ("staging") area

We can inspect and manipulate the Git Index as well. To work with the index inside an existing repository, instantiate it by using the Repository.index method instead of manually opening the Index by its path.

index =

# Re-read the index file from disk.

# Count up index entries.
count = index.count

# The collection of index entries.

# Iterating over index entries.
index.each { |i| puts i.inspect }

# Get a particular entry in the index.

# Unstage.

# Stage. Also updates existing entry if there is one.

# Stage. Create ientry from file in path, updates the index.


You can access references through the Rugged::ReferenceCollection object returned by Repository#references.

ref = repo.references["refs/heads/master"]

sha = ref.target_id
str = ref.type   # :direct
str =   # "refs/heads/master"

You can also easily iterate over all references:

repo.references.each do |ref|

Or only over references that match the given pattern (glob):

repo.references.each("refs/tags/*") do |ref|

It is also easy to create, update, rename or delete a reference:

ref = repo.references.create("refs/heads/unit_test", some_commit_sha)

repo.references.update(ref, new_sha) # or...
repo.references.update("refs/heads/unit_test", new_sha)

repo.references.rename(ref, "refs/heads/blead") # or...
repo.references.rename("refs/heads/unit_test", "refs/heads/blead")

repo.references.delete(ref) # or...
repo.references.delete("refs/heads/unit_test") # or...

Finally, you can access the reflog for any branch:

ref = repo.references["refs/heads/master"]
entry = ref.log.first
sha   = entry[:id_old]
sha   = entry[:id_new]
str   = entry[:message]
prsn  = entry[:committer]


The Rugged::BranchCollection object returned by Repository#branches will help you with all of your branch-related needs.

Iterate over all branches:

# => ["master", "origin/HEAD", "origin/master", "origin/packed"]

# => ["master"]

# => ["origin/HEAD", "origin/master", "origin/packed"]

Look up branches and get attributes:

branch = repo.branches["master"] # => 'master'
branch.canonical_name # => 'refs/heads/master'

Look up the id for the target of a branch:

# => "36060c58702ed4c2a40832c51758d5344201d89a"

Creation and deletion:

branch = repo.branches.create("test_branch", "HEAD")

repo.branches.rename("test_branch", "new_branch") # or...
repo.branches.rename("refs/heads/test_branch", "new_branch") # or...
repo.branches.rename(ref, "new_branch") # or...

repo.branches.delete("test_branch") # or...
repo.branches.delete("refs/heads/test_branch") # or...
repo.branches.delete(ref) # or...


There are various ways to get hands on diffs:

# Diff between two subsequent commits
diff_commits = commit_object.parents[0].diff(commit_object)

# Diff between two tree objects
diff_trees = tree_object_a.diff(tree_object_b)

# Diff between index/staging and current working directory
diff_index = repository.index.diff

# Diff between index/staging and another diffable (commit/tree/index)
diff_index_diffable = repository.index.diff(some_diffable)

When you already have a diff object, you can examine it:

# Get patch
=> "diff --git a/foo1 b/foo1\nnew file mode 100644\nindex 0000000..81b68f0\n--- /dev/null\n+++ b/foo1\n@@ -0,0 +1,2 @@\n+abc\n+add line1\ndiff --git a/txt1 b/txt1\ndeleted file mode 100644\nindex 81b68f0..0000000\n--- a/txt1\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-abc\n-add line1\ndiff --git a/txt2 b/txt2\nindex a7bb42f..a357de7 100644\n--- a/txt2\n+++ b/txt2\n@@ -1,2 +1,3 @@\n abc2\n add line2-1\n+add line2-2\n"

# Get delta (faster, if you only need information on what files changed)
diff.each_delta{ |d| puts d.inspect }
#<Rugged::Diff::Delta:70144372137380 {old_file: {:oid=>"0000000000000000000000000000000000000000", :path=>"foo1", :size=>0, :flags=>6, :mode=>0}, new_file: {:oid=>"81b68f040b120c9627518213f7fc317d1ed18e1c", :path=>"foo1", :size=>14, :flags=>6, :mode=>33188}, similarity: 0, status: :added>
#<Rugged::Diff::Delta:70144372136540 {old_file: {:oid=>"81b68f040b120c9627518213f7fc317d1ed18e1c", :path=>"txt1", :size=>14, :flags=>6, :mode=>33188}, new_file: {:oid=>"0000000000000000000000000000000000000000", :path=>"txt1", :size=>0, :flags=>6, :mode=>0}, similarity: 0, status: :deleted>
#<Rugged::Diff::Delta:70144372135780 {old_file: {:oid=>"a7bb42f71183c162efea5e4c80597437d716c62b", :path=>"txt2", :size=>17, :flags=>6, :mode=>33188}, new_file: {:oid=>"a357de7d870823acc3953f1b2471f9c18d0d56ea", :path=>"txt2", :size=>29, :flags=>6, :mode=>33188}, similarity: 0, status: :modified>

# Detect renamed files
# Note that the status field changed from :added/:deleted to :renamed
diff.each_delta{ |d| puts d.inspect }
#<Rugged::Diff::Delta:70144372230920 {old_file: {:oid=>"81b68f040b120c9627518213f7fc317d1ed18e1c", :path=>"txt1", :size=>14, :flags=>6, :mode=>33188}, new_file: {:oid=>"81b68f040b120c9627518213f7fc317d1ed18e1c", :path=>"foo1", :size=>14, :flags=>6, :mode=>33188}, similarity: 100, status: :renamed>
#<Rugged::Diff::Delta:70144372230140 {old_file: {:oid=>"a7bb42f71183c162efea5e4c80597437d716c62b", :path=>"txt2", :size=>17, :flags=>6, :mode=>33188}, new_file: {:oid=>"a357de7d870823acc3953f1b2471f9c18d0d56ea", :path=>"txt2", :size=>29, :flags=>6, :mode=>33188}, similarity: 0, status: :modified>

# Merge one diff into another (mutating the first one)

# Write a patch into a file (or any other object responding to write)
# Note that the patch as in diff.patch will be written, it won't be applied
file ='/some/file', 'w')

Config files

It's also easy to read and manipulate the Git config file data with Rugged.

# Read values

# Set values
repo.config[''] = true

# Delete values

General methods

Rugged also includes a general library for handling basic Git operations. One of these is converting a raw sha (20 bytes) into a readable hex sha (40 characters).

# => "\277\336Y\315\320\337\254\035\211(\024\366j\225d\032\275\212\037\257"

=> "bfde59cdd0dfac1d892814f66a95641abd8a1faf"

Alternative backends

You can store bare repositories in alternative backends instead of storing on disk. (see redbadger/rugged-redis for an example of how a rugged backend works).

a_backend = MyProject::CustomObjectDB(opt1: 'setting', opt2: 'setting')

repo = Rugged::Repository.init_at('repo_name', :bare, backend: a_backend)

# or

repo = Rugged::Repository.bare('repo_name', backend: a_backend)


Fork libgit2/rugged on GitHub, make it awesomer (preferably in a branch named for the topic), send a pull request.


Simply clone and install:

$ git clone
$ cd rugged
$ bundle install
$ rake compile
$ rake test


We encourage you to use StackOverflow for any questions or concerns regarding Rugged. Please tag your questions with the rugged keyword.

For bug reports, please open a ticket on the GitHub issue tracker.


Author: libgit2
Source code:
License: MIT license

#ruby  #ruby-on-rails #git 

Rugged: A Library for Accessing Libgit2 in Ruby
Sasha  Roberts

Sasha Roberts


Git Lint: A Command Line interface for Linting Git Commits With Ruby

Git Lint

Git Lint is a command line interface for linting Git commits by ensuring you maintain a clean, easy to read, debuggable, and maintainable project history. Having a consistent commit history leads to improved code reviews and is a perfect companion to tools like Milestoner for versioning and producing automated release notes of your deploys.


Enforces a Git Rebase Workflow.

Enforces a clean and consistent Git commit history.

Supports Git default branch configuration.

Provides a customizable suite of analyzers.

Provides Git Hook support for local use.

Provides Continuous Integration (CI) support.





To install, run:

gem install git-lint


Command Line Interface (CLI)

From the command line, type: git-lint --help

  -a, --analyze [options]                  Analyze current branch commits.
  -c, --config ACTION                      Manage gem configuration. Actions: edit or view.
  -h, --help                               Show this message.
      --hook PATH                          Hook for analyzing unsaved commits.
  -v, --version                            Show gem version.

      --sha HASH                           Analyze specific commit SHA.

To check if your Git commit history is clean, run: git-lint --analyze. It will exit with a failure if at least one issue with error severity is detected.

This gem does not check commits on your default branch (i.e. main). This is intentional as you would, generally, not want to rewrite or fix commits on the main branch. This gem is best used on feature branches as it automatically detects all commits made since creation of the feature branch.

Here is an example workflow, using gem defaults with issues detected:

cd example
git checkout -b test
touch text.txt
git add --all .
git commit --message "This is a bogus commit message that is also terribly long and will word wrap"
git-lint --analyze


Running Git Lint...

83dbad531d84a184e55cbb38c5b2a4e5fa5bcaee (Brooke Kuhlmann, 0 seconds ago): This is a bogus commit message that is also terribly long and will word wrap.
  Commit Body Presence Warning. Use minimum of 1 line (non-empty).
  Commit Subject Length Error. Use 72 characters or less.
  Commit Subject Prefix Error. Use: /Fixed/, /Added/, /Updated/, /Removed/, /Refactored/.
  Commit Subject Suffix Error. Avoid: /\./, /\?/, /\!/.

1 commit inspected. 4 issues detected (1 warning, 3 errors).


This gem provides optional Rake tasks. They can be added to your project by adding the following requirement to the top of your Rakefile:

require "git/lint/rake/setup"

Now, when running bundle exec rake -T, you’ll see git_lint included in the list.

If you need a concrete example, check out the Rakefile of this project for details.

Default Branch

Your default branch configuration is respected no matter if it is set globally or locally. If the default branch is not set then Git Lint will fall back to master for backwards compatibility. When the next major version is released, the default branch fallback will change from master to main. You can set your default branch at any time by running the following from the command line:

git config --add init.defaultBranch main

💡 When setting your default branch, ensure you use a consistent Git configuration across all of your environments.


This gem can be configured via a global configuration:


It can also be configured via XDG environment variables. The default configuration is:

    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
    :minimum: 2
    :enabled: true
    :severity: :error
      - "\\*"
      - "•"
    :enabled: true
    :severity: :error
    :includes: "\\-"
    :enabled: true
    :severity: :error
    :includes: "\\-"
    :enabled: true
    :severity: :error
      - "(f|F)ix(es|ed)?\\s\\#\\d+"
      - "(c|C)lose(s|d)?\\s\\#\\d+"
      - "(r|R)esolve(s|d)?\\s\\#\\d+"
    :enabled: false
    :severity: :warn
    :enabled: true
    :severity: :error
    :maximum: 72
    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
      - "absolutely"
      - "actually"
      - "all intents and purposes"
      - "along the lines"
      - "at this moment in time"
      - "basically"
      - "each and every one"
      - "everyone knows"
      - "fact of the matter"
      - "furthermore"
      - "however"
      - "in due course"
      - "in the end"
      - "last but not least"
      - "matter of fact"
      - "obviously"
      - "of course"
      - "really"
      - "simply"
      - "things being equal"
      - "would like to"
      - "\\beasy\\b"
      - "\\bjust\\b"
      - "\\bquite\\b"
      - "as\\sfar\\sas\\s.+\\sconcerned"
      - "of\\sthe\\s(fact|opinion)\\sthat"
    :enabled: false
    :severity: :warn
    :minimum: 1
    :enabled: true
    :severity: :error
    :includes: "\\-"
    :enabled: true
    :severity: :error
    :maximum: 72
    :enabled: true
    :severity: :error
    :delimiter: " "
      - Fixed
      - Added
      - Updated
      - Removed
      - Refactored
    :enabled: true
    :severity: :error
      - "\\."
      - "\\?"
      - "\\!"
    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
    :enabled: true
    :severity: :error
      - "Co-Authored-By"
    :enabled: true
    :severity: :error
    :minimum: 2

Feel free to take this default configuration, modify, and save as your own custom configuration.yml.


By default, most analyzers are enabled. Accepted values are true or false. If you wish to disable a analyzer, set it to false.

Severity Levels

By default, most analyzers are set to error severity. If you wish to reduce the severity level of a analyzer, you can set it to warn instead. Here are the accepted values and what each means:

warn: Will count as an issue and display a warning but will not cause the program/build to fail. Use this if you want to display issues as reminders or cautionary warnings.

error: Will count as an issue, display error output, and cause the program/build to fail. Use this setting if you want to ensure bad commits are prevented.

Regular Expressions

Some analyzers support include or exclude lists. These lists can consist of strings, regular expressions, or a combination thereof. Regardless of your choice, all lists are automatically converted to regular expression for use by the analyzers. This means a string like "example" becomes /example/ and a regular expression of "\\AExample.+" becomes /\AExample.+/.

If you need help constructing complex regular expressions for these lists, try launching an IRB session and using or Regexp.escape to experiment with the types of words/phrases you want to turn into regular expressions. For purposes of the YAML configuration, these need to be expressed as strings with special characters escaped properly for internal conversion to a regular expression.

Git Hooks

This gem supports Git Hooks.

It is highly recommended you manage Git Hooks as global scripts as it’ll reduce project maintenance costs for you. To configure global Git Hooks, add the following to your $HOME/.gitconfig:

  hooksPath = ~/.git_template/hooks

Then you can customize Git Hooks for all of your projects. Check out these examples.

If a global configuration is not desired, you can add Git Hooks at a per project level by editing any of the scripts within the .git/hooks directory of the repository.

Commit Message

The commit-msg hook — which is the best way to use this gem as a Git Hook — is provided as a --hook option. Usage:

git-lint --hook PATH

As shown above, the --hook command accepts a file path (i.e. .git/COMMIT_EDITMSG) which is provided to you by Git within the .git/hooks/commit-msg script. Here is a working example of what that script might look like:

#! /usr/bin/env bash

set -o nounset
set -o errexit
set -o pipefail

if ! command -v git-lint > /dev/null; then
   printf "%s\n" "[git]: Git Lint not found. To install, run: gem install git-lint."
   exit 1

git-lint --hook "${BASH_ARGV[0]}"

Whenever you attempt to add a commit, Git Lint will check your commit for issues prior to saving it.

Post Commit

The post-commit hook is possible via the --analyze command. Usage:

git-lint --analyze --shas SHA

The post-commit hook can be used multiple ways but, generally, you’ll want to check the last commit made. Here is a working example which can be used as a .git/hooks/post-commit script:

#! /usr/bin/env bash

set -o nounset
set -o errexit
set -o pipefail

if ! command -v git-lint > /dev/null; then
   printf "%s\n" "[git]: Git Lint not found. To install, run: gem install git-lint."
   exit 1

git-lint --analyze --shas $(git log --pretty=format:%H -1)

Whenever a commit has been saved, this script will run Git Lint to check for issues.

Continuous Integration (CI)

This gem automatically configures itself for known CI build servers (see below for details). If you have a build server that is not listed, please log an issue or provide an implementation with support.

Calculation of commits is done by reviewing all commits made on the feature branch since branching from main.

Circle CI

Detection and configuration happens automatically by checking the CIRCLECI environment variable. No additional setup required!

GitHub Actions

Detection happens automatically by checking the GITHUB_ACTIONS environment variable as supplied by the GitHub environment. The only configuration required is to add a .github/workflows/git_lint.yml to your repository with the following contents:

name: Git Lint

on: pull_request

    runs-on: ubuntu-latest
      image: ruby:latest
      - name: Checkout
        uses: actions/checkout@v2
          fetch-depth: '0'
          ref: ${{github.head_ref}}
      - name: Install
        run: gem install git-lint
      - name: Analyze
        run: git-lint --analyze

The above will ensure Git Lint runs as an additional check on each Pull Request.

Netlify CI

Detection and configuration happens automatically by checking the NETLIFY environment variable. No additional setup required!


The following details the various analyzers provided by this gem to ensure a high standard of commits for your project.

Commit Author Capitalization


Ensures author name is properly capitalized. Example:

# Disallowed
jayne cobb
dr. simon tam

# Allowed
Jayne Cobb
Dr. Simon Tam

Commit Author Email


Ensures author email address exists. Git requires an author email when you use it for the first time too. This takes it a step further to ensure the email address loosely resembles an email address.

# Disallowed

# Allowed

Commit Author Name

trueerrorminimum: 2

Ensures author name consists of, at least, a first and last name. Example:

# Disallowed

# Allowed
Kaywinnet Lee Frye

Commit Body Bullet

trueerrorexcludes: ["\\*", "•"]

Ensures commit message bodies use a standard Markdown syntax for bullet points. Markdown supports the following syntax for bullets:

* -

It’s best to use dashes for bullet point syntax as stars are easier to read when used for emphasis. This makes parsing the Markdown syntax easier when reviewing a Git commit as the syntax used for bullet points and emphasis are now, distinctly, unique.

Commit Body Bullet Capitalization

trueerrorincludes: ["\\-"]

Ensures commit body bullet lines are capitalized. Example:

# Disallowed
- an example bullet.

# Allowed
- An example bullet.

Commit Body Bullet Delimiter

trueerrorincludes: ["\\-"]

Ensures commit body bullets are delimited by a space. Example:

# Disallowed
-An example bullet.

# Allowed
- An example bullet.

Commit Body Leading Line


Ensures there is a leading, empty line, between the commit subject and body. Generally, this isn’t an issue but sometimes the Git CLI can be misused or a misconfigured Git editor will smash the subject line and start of the body as one run-on paragraph. Example:

# Disallowed

Curabitur eleifend wisi iaculis ipsum.
Pellentque morbi-trist sentus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu_libero sit amet quam
egestas semper. Aenean ultricies mi vitae est. Mauris placerat's eleifend leo. Quisque et sapien
ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, orn si amt wit.

# Allowed

Curabitur eleifend wisi iaculis ipsum.

Pellentque morbi-trist sentus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu_libero sit amet quam
egestas semper. Aenean ultricies mi vitae est. Mauris placerat's eleifend leo. Quisque et sapien
ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, orn si amt wit.

Commit Body Line Length

trueerrormaximum: 72

Ensures each line of the commit body doesn’t force you to scroll horizontally to read the message. This allows commit messages to remain readable and is especially handy when commit messages are read via the command line or email clients.

Commit Body Paragraph Capitalization


Ensures each paragraph of the commit body is capitalized. Example:

# Disallowed
curabitur eleifend wisi iaculis ipsum.

# Allowed
Curabitur eleifend wisi iaculis ipsum.

Commit Body Phrase

trueerrorexcludes: (see configuration)

Ensures non-descriptive words/phrases are avoided in order to keep commit message bodies informative and specific. The exclude list is case insensitive. Detection of excluded words/phrases is case insensitive as well. Example:

# Disallowed

Obviously, the existing implementation was too simple for my tastes. Of course, this couldn't be
allowed. Everyone knows the correct way to implement this code is to do just what I've added in
this commit. Easy!

# Allowed

Necessary to fix due to a bug detected in production. The included implementation fixes the bug
and provides the missing spec to ensure this doesn't happen again.

Commit Body Presence

falsewarnminimum: 1

Ensures a minimum number of lines are present within the commit body. Lines with empty characters (i.e. whitespace, carriage returns, etc.) are considered to be empty.

Automatically ignores fixup! commits as they are not meant to have bodies.

Commit Body Single Bullet

trueerrorincludes: "\\-"

Ensures a single bullet is never used when a paragraph could be used instead. Example:

# Disallowed

- Pellentque morbi-trist sentus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
  quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu_libero sit amet quam.

# Allowed

Pellentque morbi-trist sentus et netus et malesuada fames ac turpis egestas. Vestibulum tortor
quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu_libero sit amet quam.

Commit Body Tracker Shorthand

trueerrorexcludes: (see configuration)

Ensures commit body doesn’t use issue tracker shorthand. The exclude list defaults to GitHub Issues but can be customized for any issue tracker.

There are several reasons for excluding issue tracker links from commit bodies:

Not all issue trackers preserve issues (meaning they can be deleted). This makes make reading historic commits harder to understand why the change was made when the reference no longer works.

When disconnected from the internet or working on a laggy connection, it’s hard to understand why a commit was made when all you have is a shorthand issue reference with no supporting context.

During the course of a repository’s life, issue trackers can be replaced (rare but does happen). If the old issue tracker service is no longer in use, none of the commit body shorthand will be of any relevance.

Instead of using tracker shorthand syntax, take the time to write a short summary as to why the commit was made. Doing this will make it easier to understand why the commit was made, keeps the commit self-contained, and makes learning about/debugging the commit faster.

Commit Subject Length

trueerrormaximum: 72

Ensures the commit subject length is no more than 72 characters in length. This default is more lenient than the 50/72 rule as it gives one the ability to formulate a more descriptive subject line without being too wordy or suffer being word wrapped.

Automatically ignores fixup! or squash! commit prefixes when calculating subject length.

Commit Subject Prefix

trueerrorincludes: (see below)
  delimiter: " "

Ensures each commit subject uses consistent prefixes that explain what is being committed. The includes are case sensitive and default to the following prefixes:

Fixed - Identifies what was fixed. The commit should be as small as possible and consist of changes to implementation and spec only. In some cases this might be a single line change. The important point is the change is applied to existing code which corrects behavior that wasn’t properly implemented earlier.

Removed - Identifies what was removed. The commit should be as small as possible and consist only of removed lines/files from the existing implementation. This might also mean breaking changes requiring the publishing of a major version release in the future.

Added - Identifies what was added. The commit should be as small as possible and consist of implementation and spec. Otherwise, it might be a change to an existing file which adds new behavior.

Updated - Identifies what was updated. The commit should be as small as possible and not add or fix existing behavior. This can sometimes be a grey area but is typically reserved for updates to documentation, code comments, dependencies, etc.

Refactored - Identifies what was refactored. Refactoring is for changing code structure without changing observable behavior. The commit should be as small as possible and not mix multiple kinds of changes at once. Refactored code should never break existing implementation behavior or corresponding specs because, if that happens, then one of the other four prefixes is what you want to use instead.

In practice, it is quite rare to need a prefix other than what has been detailed above to explain what is being committed. These prefixes are not only short and easy to remember but also have the added benefit of categorizing the commits for building release notes, change logs, etc. This becomes handy when coupled with another tool, Milestoner, for producing consistent project milestones and Git tag histories. For a deeper dive on subject prefixes and good commit messages in general, please read about commit anatomy to learn more. 🎉

Each prefix is delimited by a space which is the default setting but can be customized if desired. Whatever you choose for a delimiter will not affect Git’s special bang prefixes as described in the tip below.

💡 This analyzer automatically ignores amend!, fixup!, or squash! commit prefixes when used as a Git Hook in order to not disturb interactive rebase workflows.

Commit Subject Suffix

trueerrorexcludes: ["\\.", "\\?", "\\!"]

Ensures commit subjects are suffixed consistently. The exclude list is case sensitive and prevents the use of punctuation. This is handy when coupled with a tool, like Milestoner, which automates project milestone releases.

Commit Trailer Collaborator Capitalization


Ensures collaborator name is properly capitalized. Example:

# Disallowed
shepherd derrial book

# Allowed
Shepherd Derrial Book

Commit Trailer Collaborator Duplication


Ensures collaborator trailers are not duplicated. Example:

# Disallowed
Co-Authored-By: Shepherd Derrial Book <>
Co-Authored-By: Shepherd Derrial Book <>

# Allowed
Co-Authored-By: Malcolm Reynolds <>
Co-Authored-By: Shepherd Derrial Book <>

Commit Trailer Collaborator Email


Ensures collaborator email address is valid for commit trailer.

# Disallowed
Co-Authored-By: River Tam <invalid>

# Allowed
Co-Authored-By: River Tam <>

Commit Trailer Collaborator Key

falseerrorincludes: ["Co-Authored-By"]

Ensures collaborator trailer key is correct format.

# Disallowed
Co-authored-by: River Tam <>

# Allowed
Co-Authored-By: River Tam <>

Commit Trailer Collaborator Name

falseerrorminimum: 2

Ensures collaborator name consists of, at least, a first and last name. Example:

# Disallowed
Co-Authored-By: River <>

# Allowed
Co-Authored-By: River Tam <>

Style Guide

In addition to what is described above and automated for you, the following style guide is also worth considering:


Use a Git Rebase Workflow instead of a Git Merge Workflow.

Use git commit --amend when fixing a previous commit, addressing code review feedback, etc.

Use git commit --fixup when fixing an earlier commit, addressing code review feedback, etc., and don’t need to modify the original commit message.

Use git commit --squash when fixing an earlier commit, addressing code review feedback, etc., and want to combine multiple commit messages into a single commit message. Avoid using squash to blindly combine multiple commit messages without editing them into a single, coherent message.

Use git rebase --interactive when cleaning up commit history, order, messages, etc. This should be done prior to submitting a code review or when code review feedback has been addressed and you are ready to rebase onto main.

Use git push --force-with-lease instead of git push --force when pushing changes after an interactive rebasing session.

Avoid checking in development-specific configuration files (add to .gitignore instead).

Avoid checking in sensitive information (i.e. security keys, passphrases, etc).

Avoid "WIP" (a.k.a. "Work in Progress") commits and/or code review labels. Be confident with your code and colleagues' time. Use branches, stashes, etc. instead — share a link to a feature branch diff if you have questions/concerns during development.

Avoid using Git Submodules. This practice leads to complicated project cloning, deployments, maintenance, etc. Use separate repositories to better organize and split out this work. Sophisticated package managers, like Bundler, exist to manage these dependencies better than what multiple Git Submodules can accomplish.

Avoid using Git LFS for tracking binary artifacts/resources. These files are not meant for version control and lead to large repositories that are time consuming to clone/deploy. Use storage managers like Git Annex, Amazon S3, or LakeFS which are better suited for binary assets that don’t change often.


Ensure signed commits, pushes, and tags are enabled within your global Git Configuration to reduce an attack vector. Run the following commands to enable:

git config --global commit.gpgSign true
git config --global push.gpgSign true
git config --global tag.gpgSign true

⚠️ GitHub, unfortunately, doesn’t support signed pushes so you might need to leave that configuration disabled.


Use a commit subject that explains what is being committed.

Use a commit message body that explains why the commit is necessary. Additional considerations:

If the commit has a dependency to the previous commit or is a precursor to the commit that will follow, make sure to explain that.

Include links to dependent projects, stories, etc. if available.

Use small, atomic commits:

Easier to review and provide feedback.

Easier to review implementation and corresponding tests.

Easier to document with detailed subjects (especially when grouped together in a pull request).

Easier to reword, edit, squash, fix, or drop when interactively rebasing.

Easier to combine together versus tearing apart a larger commit into smaller commits.

Use logically ordered commits:

Each commit should tell a story and be a logical building block to the next commit.

Each commit should, ideally, be the implementation plus corresponding test. Avoid committing changes that are a jumble of mixed ideas as they are hard to decipher and a huge insult not only to the reviewer but your future self.

Each commit, when reviewed in order, should be able to explain how the feature or bug fix was completed and implemented properly.

Keep refactored code separate from behavioral changes. This makes the review process easier because you don’t have to sift through all the line and format changes to figure out what is new or changed.


Use feature branches for new work.

Maintain branches by rebasing upon main on a regular basis.


Use tags to denote milestones/releases:

Makes it easier to record milestones and capture associated release notes.

Makes it easier to compare differences between versions.

Provides a starting point for debugging production issues (if any).


Avoid rebasing a shared branch. If you must do this, clear communication should be used to warn those ahead of time, ensure that all of their work is checked in, and that their local branch is deleted first.


Use hooks to augment and automate your personal workflow such as checking code quality, detecting forgotten debug statements, etc.

Use hooks globally rather than locally per project. Doing this applies the same functionality across all projects automatically, reduces maintenance per project, and provides consistency across all projects. This can best be managed via your Dotfiles.

Avoid forcing global or local project hooks as a team-wide mandate. Hooks are a personal tool much like editors or other tools one choose to do their work. For team consistency, use a continuous integration build server instead.

Code Reviews

For an in depth look at how to conduct code reviews, please read my article on this subject to learn more.


To contribute, run:

git clone
cd git-lint

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



To test, run:

bundle exec rake



Code of Conduct





Built with Gemsmith.

Engineered by Brooke Kuhlmann.

Author: bkuhlmann
Source code:
License:  View license
#git #ruby  #ruby-on-rails #github 

Git Lint: A Command Line interface for Linting Git Commits With Ruby
Sasha  Roberts

Sasha Roberts


Git Whence: Find Which Merge A Commit Came From with Ruby

Find the merge and pull request a commit came from, also finding straight cherry-picks.


gem install git-whence

or as standalone binary (needs any ruby)

curl -sL > /usr/local/bin/git-whence && \
chmod +x /usr/local/bin/git-whence && \
git-whence --version


git-whence 6d37485
Merge pull request #10486 from foo/bar

git-whence 6d37485 -o
-> open browser on github pull request page



Michael Grosser
License: MIT

Author: grosser
Source code:
License: MIT license

#ruby  #ruby-on-rails #git #github 

Git Whence: Find Which Merge A Commit Came From with Ruby
Sasha  Roberts

Sasha Roberts


Git Spelunk: Dig Through Git Blame History With Ruby


This is git-spelunk, an interactive tool for exploring blame history.

Install with gem install git_spelunk (requires Ruby 2+).


git blame is a great tool for determining code responsibility. But sometimes when you fire up git-blame, you'll find yourself in a loop of git-blame, git-show, and then git-blame again in order to find the true owner of a line of code. git-spelunk is the tool for situations like this, when you want to skip past syntactic and refactoring commits to find the true owner of a line, or see how a piece of source code has evolved over time.


It's easier to show you.

git spelunk lib/git_spelunk/offset.rb

git spelunk, main page

You can see we've highlighted line 45. git-spelunk here is telling us that this line was introduced in commit 33465d2. You can also see that all other lines that were involved in 33465d2 are picked out in green. The output of git show is present as well for adding more context to the information.

Now we press '['. What we're asking here is "show me the file just before 33465d2" was introduced, essentially replacing the content of the current screen with git blame [file] 33465d2~1.

git spelunk, back one

There's other stuff to do; you can hit "s" to do a git show of the commit underneath the cursor, you can search and page through the file like you would with "less".

Author: osheroff
Source code:
License: MIT license

#ruby  #ruby-on-rails #github #git 

Git Spelunk: Dig Through Git Blame History With Ruby
Sasha  Roberts

Sasha Roberts


Git Reflow: An Automated Quality Control Workflow for Agile Teams

git-reflow – Automate your git workflow
(2015 Fukuoka Ruby Award Winner)  

If your workflow looks like this:

  1. Create a feature branch
  2. Write great code
  3. Create a pull request against master
  4. Get 'lgtm' through a code review
  5. Merge to master (squashed by default, but can be overridden; why we prefer squash-merge)
  6. Delete the feature branch

Reflow will make your life easier.

Reflow automatically creates pull requests, ensures the code review is approved, and merges finished branches to master with a great commit message template.

Usage Overview

Create and switch to new branch nh-branchy-branch:

    $ git reflow start nh-branchy-branch

Create a pull request for your branch against master or a custom base-branch:

    $ git reflow review

If your code is approved, merge to base-branch and delete the feature branch:

    $ git reflow deliver


  • Enforce code reviews across your team.
  • Know that your entire team delivers code the same way.
  • Reduce the knowledge needed to deliver great code.
  • Have a commit history that's clean and actually usable.
  • Revert features with ease (if needed).
  • Work with diverse teams with less worry about different processes.
  • Stop searching for other git workflows. Reflow covers 90% of your needs without junk you'll never use.


  • Automatically create pull requests to master
  • Automatically ensure that your code is reviewed before merging
  • Start with sensible commit messages by default
  • Squash merge feature branches because results are more important than details
  • Automatically clean up obsolete feature branches after a successful merge


Editor When reviewing the title and body for a new pull request, or reviewing the commit message when delivering a feature, we will open a temporary file with your default editor. We will use git's chosen editor first (git config core.editor), then we try your EDITOR environment variable, and lastly we fallback on "vim". If you would like to use an editor of your choice, we recommend setting it with git's config. As an example, to use Atom as your editor for all git commands:

$ git config --global core.editor "atom --wait"

See GitHub's full article on associating text editors with Git for more information on adding this.

Getting Started

On your first install, you'll need to setup your Github credentials. These are used only to get an OAuth token that we will store in a reflow-specific git config file. We use this token so we can create pull requests from the command line.

$ gem install git_reflow

... installs gem ...

$ git reflow setup
Please enter your GitHub username: nhance
Please enter your GitHub password (we do NOT store this):

Your GitHub account was successfully setup!

This is safe to run multiple times. We don't care. We save this information in a special git configuration file (~/.gitconfig.reflow) that get's included into your global ~/.gitconfig file.

For usage with Github Enterprise, or other custom configurations, see our Advanced Usage Guide.

Starting a feature branch


This sets up a feature branch remotely and brings a local copy to your machine. Yeah, you can do this by hand pretty easily, so skip this command if you want. This is just a handy shortcut with no magic.

git reflow start nh-branch-name

git reflow start takes in the name of the new branch name that you want to create your feature on. In addition, it takes in an optional flag of a base-branch name (--base). If you don't pass in this parameter, then it will look up the reflow.base-branch git configuration or default to "master". The base branch name is the base branch that you want to base your feature off of. This ensures that every time you start a new base branch, it will be based off of your latest remote base.

git reflow start nh-branch-name --base base-branch-name

[PROTIP] Use your initials at the beginning of each branch so your team knows who is responsible for each. My initials are N.H., so all of my branches start with nh-

Refreshing your current branch based on your base branch

git reflow refresh

This command updates your feature-branch and base-branch according to the remote-location and then merges your base-branch into your feature-branch. This is just a handy command to keep your branches up to date at any point in time if someone else has committed to the base-branch or the remote.

git reflow refresh -r <remote-location> -b <base-branch>

You pass in the name of the remote to fetch from and the name of the base-branch that you would like to merge into your feature-branch. The remote-location defaults to origin and the base-branch defaults to master. This command also takes in remote and branch name as flag options.

Note: If no base-branch argument is provided, then we'll look for a reflow.base-branch git configuration and fallback to master as the default.

Reviewing your work


git reflow review

All of our work is reviewed by our team. This helps spread knowledge to multiple parties and keeps the quality of our code consistent.

The review step creates a pull request for the currently checked out feature branch against master. That's all you want to do most of the time. We assume you know what you're doing, so if you need something different, do it by hand.

After making commits to your branch, run review. Didn't push it up? No problem, we'll do it for you.

git reflow review -t <title> -m <message> <base-branch>

Note: -t and -m are optional, as is the base-branch argument. If no base-branch is provided, then we'll look for a reflow.base-branch git configuration and fallback to master as the default.

If you do not pass the title or message options to the review command, you will be given an editor to write your PR request commit message, similar to git commit. The first line is the title, the rest is the body.

$ git reflow review

Review your PR:

[lib] updates review command to address issues
Submit pull request? (Y): <enter>
git fetch origin master
 * branch            master     -> FETCH_HEAD

git push origin rj_test
Everything up-to-date

Successfully created pull request #6: rj_test
Pull Request URL:
Would you like to push this branch to your remote repo and cleanup your feature branch? y<enter>

We output the pull request URL so you can distribute it to your team.

How it works

Behind the scenes, this is how review works:

git fetch origin

Are we up-to-date with changes from master? If not, fail with "master has newer changes".


git push origin current-branch # Updates remote branch

Do we have pull request? If not, create it and print "Pull request created at http://pull-url/". If so, print the url for the existing request.

Checking your branch status


git reflow status <base-branch>

Note: If no base-branch is provided, then we'll look for a reflow.base-branch git configuration and fallback to master as the default.

Sometimes you start working on a branch and can't get back to it for a while. It happens. Use +status+ to check on the status of your work.

$ git reflow status

Here's the status of your review:
  branches:     reenhanced:nh-readme-update -> reenhanced:master
  number:       35
  reviewed by:

[notice] No one has reviewed your pull request.

This gives you details on who's reviewed your pull request. If someone has participated in reviewed, but not given you an approval, this will tell you. status prevents you from having to open a browser to find out where your pull request is at. But in case you want to take a look, we give you the option to open it for you.

Delivering approved code


git reflow deliver <base-branch>

Note: If no base-branch argument is provided, then we'll look for a reflow.base-branch git configuration and fallback to master as the default.

Also: This documentation is for the process for the github "remote" merge process via the github_api. For the bitbucket standard or github manual process (used when the user applies -f force flag to the "remote" merge via the github_api), please go to section B.

You kick butt. You've got your code reviewed and now you're ready to merge it down to master and deploy. Reflow's deliver command will take care of all of the steps for you to make this happen.

Reflow's deliver requires you to have a pull request, so you'll be protected on those mornings when the coffee isn't working yet. We built this to get in your way and make you follow the process. If you don't like it, do it by hand. You already know what you're doing.

You'll be presented with a pre-filled commit message based on the body of your pull request with references to the pull request and reviewers.

Want to clean up your feature branch afterwards? You'll be prompted after you edit your commit message if you want to clean up your feature-branch on Github. If you answer no, then your feature-branch will exist for you to clean it up later.

This is what it looks like:

$ git reflow deliver
Here's the status of your review:
    branches:     simonzhu24:test1234 -> simonzhu24:master
    number:       51
    reviewed by:  @codenamev

This is the current status of your Pull Request. Are you sure you want to deliver? yes

Merging pull request #51: 'last commit message', from 'simonzhu24:test1234' into 'simonzhu24:master'
git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

[success] Pull Request successfully merged.
Would you like to cleanup your feature branch? yes
git pull origin master
remote: Counting objects: 1, done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), done.
 * branch            master     -> FETCH_HEAD
   0d8f5e0..f853efa  master     -> origin/master
Updating 0b6782f..f853efa
Fast-forward | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

git push origin :test1234
 - [deleted]         test1234

git branch -D test1234
Deleted branch test1234 (was e130c7a).
Nice job buddy.

How it works

This is what we do behind the scenes when you do deliver

  • Does a pull request exist?
    • If not, stop here. You need to run review.
  • Has the review been completed? Did we get an approval from everyone who's participated?
    • If not, show a list of authors that need to approve.
  • If the review is done, it's time to merge. Here's what happens:
    1. First, we use the github_api gem to merge the pull request (see here for how we do this).

Notice: we will do a squash-merge by default. You can customize the merge method for your project.

  1. Next, we prompt you if you want to cleanup: Would you like cleanup your feature branch?

If yes, then we'll update the base-branch and delete the feature-branch

git pull origin <base-branch>
git push origin :<feature-branch>
git branch -D <feature-branch>

If 'no', then just stop here. The user can clean up his branch locally.

This is what the process looks like for Bitbucket or if you force deliver (git reflow deliver -f):

  * branch            master     -> FETCH_HEAD
  Merging pull request #36: 'Enforce at least one LGTM before delivery', from 'reenhanced:nh-fail-without-lgtm' into 'reenhanced:master'
  Already up-to-date.
  Switched to branch 'nh-fail-without-lgtm'
  Switched to branch 'master'
  Updating c2ec1b1..f90e111
   Squash commit -- not updating HEAD
   lib/git_reflow.rb | 71 +++++++++++++++++++++++++++----------------------------
   1 file changed, 35 insertions(+), 36 deletions(-)
  [master d1b4dd5] Enforces LGTM before deliver, even with no comments.
   1 file changed, 35 insertions(+), 36 deletions(-)
  Merge complete!
  Would you like to push this branch to your remote repo and cleanup your feature branch? y
  Counting objects: 7, done.
  Delta compression using up to 16 threads.
  Compressing objects: 100% (4/4), done.
  Writing objects: 100% (4/4), 1.38 KiB, done.
  Total 4 (delta 2), reused 0 (delta 0)
     c2ec1b1..d1b4dd5  master -> master

   - [deleted]         nh-fail-without-lgtm

  Deleted branch nh-fail-without-lgtm (was f90e111).

This is what the default commit message looks like:

  Enforces LGTM before deliver, even with no comments.
  Removes the need to review the pull request yourself if you make a

  Better error message if setup fails.

  Bug fixes.

  Closes #36

  LGTM given by: @codenamev

  Squashed commit of the following:

  commit f90e111
  Author: Nicholas Hance <>
  Date:   Thu Jul 11 15:33:29 2013 -0400

If the review is done, it's time to merge. Here's what happens:

  1. First, update our local master so we don't get conflicts
git checkout master
git pull origin master
  1. Next, merge our feature branch (in our case squash-merge)
git merge --squash nh-branch-name
  1. Now, we'll apply a little magic so we have a great commit message by default. Your editor will open and you'll see a nice default for your commit message based on the pull request body.
git commit

Note: We use .git/COMMIT_EDITMSG by default for populating this. See the Full List of Configuration for how you can change this.

  1. Once you've saved the commit, we'll make sure you want to continue.
Merge complete!
Would you like to push this branch to your remote repo and cleanup your feature branch?
  • If 'yes', then we'll push to base-branch(default: master) and delete the feature-branch
git pull origin master
git push origin master
git push origin :nh-branch-name
git branch -D nh-branch-name
  • If 'no', then just stop here. The user can reset his local base-branch. And we're done!

Guiding Principles

Your workflow should resemble the following:

You should already know what you're doing. We assume you know how to use git.

The master branch is your codebase. You don't need multiple branches for code you want to use.

master should remain stable at all times. The entire team depends on it.

No direct commits to master. All work happens in feature branches. From a single commit to hundreds.

All feature branches are reviewed via pull requests.

Looks Good To Me. All feature branches require approval. We check both Github's Approvals, and look for the string 'LGTM' in a comment on the pull request to know it's ready to merge.

If you make a new commit in your branch, you require another review.

Depending on your git config constants.minimumApprovals setting, which we specify in your ~/.gitconfig.reflow (created upon reflow setup), you can have the following:

minimumApprovalsApplied Restrictions
""All participants in a pull request must approve the pull request.
"0"0 approvals required for you to merge PR.
"1"You need a minimum of 1 LGTM and the last comment on your PR must be an LGTM.
"2"You need a minimum of 2 LGTM and the last comment on your PR must be an LGTM.

Once approved, your feature-branch is merged to your base-branch. This makes the history of the base-branch extremely clean and easy to follow.

git blame becomes your friend. You'll know who to blame and can see the full context of changes. Squash commits to base-branch mean every commit represents the whole feature, not a "typo fix".


In order to streamline delivery you can set the following git config to:

  1. always clean up the remote feature branch after the PR is merged git config --global --add "reflow.always-cleanup-remote" "true"
  2. always clean up the local feature branch after the PR is merged git config --global --add "reflow.always-cleanup-local" "true"
  3. always deliver without further prompt git config --global --add "reflow.always-deliver" "true"

See our Advanced Usage for more ways you can customize your workflow.


Git-reflow's default process isn't meant to fit every team, which is why we've introduced Custom Workflows. With a custom workflow, you can:

  • Add hooks to be run before, or after any command
  • Use one of our pre-configured workflows as a basis for your own
  • Override any of the default commands
  • Create new commands


Pull requests are welcome. Please fork and submit. We use this tool every single day and as long as what you want to add doesn't change our workflow, we are happy to accept your updates. Feel free to add your Github username to the list below.


  • @codenamev
  • @armyofgnomes
  • @nhance

Built by Reenhanced:

Looking for a capable team for your project? Get in touch. We're looking to grow.

Licensed using the MIT license. Do whatever you like with this, but don't blame us if it breaks anything. You're a professional, and you're responsible for the tools you use.

Author: reenhanced
Source code:
License: MIT license

#git #github #ruby  #ruby-on-rails 

Git Reflow: An Automated Quality Control Workflow for Agile Teams
Sasha  Roberts

Sasha Roberts


Git Curate: Peruse and Delete Git Branches Ergonomically for Ruby

git curate



After a while, my local repo becomes cluttered with branches, and git branch outputs an awkwardly long list. I want to delete some of those branches to bring that list back under control; but I can’t always remember which branches I want to keep from the branch names alone; and inspecting them one at a time and then running git branch -D in a separate step, is painful.

git curate is intended to ease this pain. It steps you through the local branches of a repo one at a time, outputting the following information about each:

  • Last commit date
  • Last commit hash
  • Last commit author
  • Last commit subject
  • Whether the branch has been merged into the current HEAD
  • The status of the branch relative to the upstream branch it is tracking (if any)

You can then select whether to delete or keep each branch as you go.

NOTE git curate does not run git fetch prior to generating its output. If you want to be sure that the “Status vs upstream” column reflects the latest state of the upstream branches as per their remote repository, you should run git fetch first.


You’ll need Ruby (v2.4.9 or greater) installed. Run:

gem install git_curate

to install the executable.

Note git_curate uses the rugged library, which comes with a native C extension, libgit2. Installation via gem install git_curate will trigger this extension to be compiled; this may take a few minutes, depending on your machine.

If you receive an error like ERROR: Failed to build gem native extension, it’s probably because your system lacks certain prerequisites needed for building libgit2, for example cmake. To fix this, first follow the installation instructions for rugged; then run gem install git_curate again.


From within a git repo, run:

git curate

This will step you through your local branches one at a time, outputting some information about each, and asking you whether to keep or delete each branch.

At each branch, enter “k”—or simply press Enter—to keep the branch and move to the next one; or enter “d” to select the branch for deletion.

Entering “e” will end the session immediately, deleting all selected branches; and “a” will abort the session without deleting any branches. Once the final branch has been considered, any selected branches will be immediately deleted.

Note the branch you are currently on will not be included in the list, as git does not allow you to delete the branch you’re on. (The same applies to any branches that are currently checked out in other worktrees.)

If you just want to view the information about your local branches without stepping through them interactively, enter git curate --list or git curate -l. Your current branch will be included in this list in this case.

You can also pass --merged to see only those branches merged into current HEAD; or --no-merged to see only those branches not merged into current HEAD.


Bug reports and pull requests are welcome on GitHub.

To start working on git_curate, git clone and cd into your fork of the repo, then run bin/setup to install dependencies.

To run the test suite, run bundle exec rake spec. For a list of other Rake tasks, run bundle exec rake -T.

Author: matt-harvey
Source code:
License: MIT license

#ruby  #ruby-on-rails #git #github 

Git Curate: Peruse and Delete Git Branches Ergonomically for Ruby
Sasha  Roberts

Sasha Roberts


Git Auto Bisect: Find The Commit That Broke Master for Ruby

Find the first broken commit without having to learn git bisect.

  • automagically bundles if necessary
  • stops at first bad commit
  • takes binary steps (HEAD~1, HEAD~2, HEAD~4, HEAD~8, ...)


gem install git-autobisect

or as standalone binary (needs any ruby)

curl -sL > /usr/local/bin/git-autobisect && \
chmod +x /usr/local/bin/git-autobisect && \
git-autobisect --version


cd your project
# run a test that has a non-0 exit status
git-autobisect 'rspec spec/models/user_spec.rb'
... grab a coffee ...
---> The first bad commit is a4328fa
git show


-m, --max N                    Inspect commits between HEAD..HEAD~<max>
-s, --start N                  Use N (instead of 1) as initial step and keep multiplying by 2
--step N                       Use N as step (instead of multiplying by 2
--no-bundle                    Do not bundle even if a Gemfile exists


  • do not fail if test file is missing [ ! -f spec/my_spec.rb ] || rspec spec/my_spec.rb


  • option for max-step-size so you can use a finer grained approach
  • option to disable bundle check || bundle injection
  • option to consider a build failed if it finishes faster then x seconds


  • bundle && bundle exec rake
  • Tests run a lot faster without bundle exec


Michael Grosser
License: MIT

Author: grosser
Source code:

#ruby  #ruby-on-rails #git 

Git Auto Bisect: Find The Commit That Broke Master for Ruby
Monty  Boehm

Monty Boehm


Git_jll.jl: An Autogenerated Package Constructed using BinaryBuilder

Git_jll.jl (v2.27.0+1)

This is an autogenerated package constructed using BinaryBuilder.jl. The originating build_tarballs.jl script can be found on Yggdrasil, the community build tree.

For more details about JLL packages and how to use them, see BinaryBuilder.jl documentation.


The tarballs for Git_jll.jl have been built from these sources:


Git_jll.jl is available for the following platforms:

  • Linux(:aarch64, libc=:glibc) (aarch64-linux-gnu)
  • Linux(:aarch64, libc=:musl) (aarch64-linux-musl)
  • Linux(:armv7l, libc=:glibc, call_abi=:eabihf) (armv7l-linux-gnueabihf)
  • Linux(:armv7l, libc=:musl, call_abi=:eabihf) (armv7l-linux-musleabihf)
  • Linux(:i686, libc=:glibc) (i686-linux-gnu)
  • Linux(:i686, libc=:musl) (i686-linux-musl)
  • Windows(:i686) (i686-w64-mingw32)
  • Linux(:powerpc64le, libc=:glibc) (powerpc64le-linux-gnu)
  • MacOS(:x86_64) (x86_64-apple-darwin14)
  • Linux(:x86_64, libc=:glibc) (x86_64-linux-gnu)
  • Linux(:x86_64, libc=:musl) (x86_64-linux-musl)
  • FreeBSD(:x86_64) (x86_64-unknown-freebsd11.1)
  • Windows(:x86_64) (x86_64-w64-mingw32)


The following JLL packages are required by Git_jll.jl:


The code bindings within this package are autogenerated from the following Products:

  • ExecutableProduct: git

Author: JuliaBinaryWrappers
Source Code: 
License: View license

#julia #git 

Git_jll.jl: An Autogenerated Package Constructed using BinaryBuilder
Monty  Boehm

Monty Boehm


Git.jl: Use Command-line Git in Your Julia Packages


Git.jl allows you to use command-line Git in your Julia packages. You do not need to have Git installed on your computer, and neither do the users of your packages!

Git.jl provides a Git binary via Git_jll.jl. The latest version of Git.jl requires at least Julia 1.6.

Git.jl is intended to work on any platform that supports Julia, including (but not limited to) Windows, macOS, Linux, and FreeBSD.


julia> using Git

julia> run(`$(git()) clone`)

This can equivalently be written with explicitly split arguments as

julia> run(git(["clone", ""]))

to bypass the parsing of the command string. Of course, one can also do

julia> import Git

julia> const git = Git.git()

julia> run(`$git clone`)

This is thread-safe since Cmd objects are not stateful. However, note that the git command object won't notice any changes to environmental variables (like GIT_CURL_VERBOSE for example) since it was created.

To read a single line of output from a git command you can use readchomp,

julia> cd("General")

julia> readchomp(`$(git()) remote get-url origin`)

and readlines for multiple lines.

julia> readlines(`$(git()) log`)


  • This work was supported in part by National Institutes of Health grants U54GM115677, R01LM011963, and R25MH116440. The content is solely the responsibility of the authors and does not necessarily represent the official views of the National Institutes of Health.

Author: JuliaVersionControl
Source Code: 
License: MIT license

#julia #git 

Git.jl: Use Command-line Git in Your Julia Packages
Anne  de Morel

Anne de Morel


Erreur : Src Refspec Master Ne Correspond à Aucun - Réparer Dans Git

Lorsque vous travaillez avec Git, vous pouvez rencontrer une erreur indiquant que "src refspace master ne correspond à aucun".

Voici ce que signifie l'erreur et comment vous pouvez la résoudre.

Que src refspec master does not match anysignifie Git ?

Vous pouvez obtenir cette erreur lorsque vous essayez de déclencher un push d'un référentiel local vers un référentiel maître comme celui-ci :

git push origin master

Cette erreur peut se produire pour différentes raisons.

La raison la plus probable pour laquelle cette erreur se produit est que la masterbranche n'existe pas.

Vous avez peut-être cloné un nouveau référentiel et la branche par défaut est main, il n'y a donc pas de branche principale lorsque vous essayez de la pousser.

Vous pouvez afficher les branches distantes connectées à un dépôt local en utilisant la git branch -bcommande comme ceci :

git branch -b

# results
#  origin/main
#  origin/feat/authentication
#  origin/other branches ...

Avec les résultats ci-dessus, vous pouvez voir qu'il n'y a pas de masterréférentiel ( origin/master). Ainsi, lorsque vous essayez de pousser vers ce référentiel, vous obtiendrez "l'erreur de respécialisation".

Ce résultat s'applique également à toute autre branche qui n'existe pas. Disons, par exemple, que j'apporte des modifications et que je pousse vers une hellobranche distante qui n'existe pas :

git add .
git commit -m "new changes"
git push origin hello

Cette commande produira l'erreur suivante :

error: src refspec hello does not match any

Comment réparer l'erreur "src refspec master ne correspond à aucun"

Vous savez maintenant que la masterbranche n'existe pas. La solution à cette erreur consiste soit à créer une masterbranche locale et distante vers laquelle vous pouvez pousser le commit, soit à pousser le commit vers une branche existante - peut-être main.

Vous pouvez créer une masterbranche distante sur un site Web géré par Git (comme GitHub) ou vous pouvez le faire directement depuis votre terminal comme ceci :

git checkout -b master

# add commit

git push origin master

Ces commandes créeront une masterbranche localement. Et en appuyant sur origin master, la masterbranche sera également créée à distance.

Mais si vous ne souhaitez pas créer de masterbranche, vous pouvez utiliser la branche par défaut existante (qui peut être main) à la place.


Donc, si vous obtenez l' Error: src refspec master does not match anyerreur lorsque vous essayez de pousser vers le maître, la raison la plus viable est que la masterbranche n'existe pas.

Source :

#git #error 

Erreur : Src Refspec Master Ne Correspond à Aucun - Réparer Dans Git

Erro: Src Refspec Master Não Corresponde A Nenhum – Corrigir No Git

Ao trabalhar com o Git, você pode encontrar um erro que diz "src refspace master não corresponde a nenhum".

Veja o que o erro significa e como você pode resolvê-lo.

O que src refspec master does not match anysignifica no Git?

Você pode receber este erro ao tentar acionar um push de um repositório local para um repositório mestre como este:

git push origin master

Esse erro pode ocorrer por diferentes motivos.

A razão mais provável para esse erro ocorrer é que a masterramificação não existe.

Talvez você tenha clonado um novo repositório e o branch padrão seja main, então não há branch master quando você tenta fazer push dele.

Você pode exibir as ramificações remotas conectadas a um repositório local usando o git branch -bcomando assim:

git branch -b

# results
#  origin/main
#  origin/feat/authentication
#  origin/other branches ...

Com os resultados acima, você pode ver que não há masterrepositório ( origin/master). Portanto, quando você tentar enviar para esse repositório, receberá o "erro de especificação".

Este resultado também se aplica a qualquer outro ramo que não exista. Digamos, por exemplo, que eu faça alterações e empurre para uma helloramificação remota que não existe:

git add .
git commit -m "new changes"
git push origin hello

Este comando produzirá o seguinte erro:

error: src refspec hello does not match any

Como corrigir o erro "src refspec master não corresponde a nenhum"

Agora você está ciente de que a masterramificação não existe. A solução para esse erro é criar um masterbranch local e remoto para o qual você possa enviar o commit ou enviar o commit para um branch existente – talvez main.

Você pode criar uma masterramificação remota em um site gerenciado pelo Git (como o GitHub) ou pode fazer isso diretamente do seu terminal assim:

git checkout -b master

# add commit

git push origin master

Esses comandos criarão uma masterramificação localmente. E pressionando para origin master, a masterramificação também será criada remotamente.

Mas se você não quiser criar uma masterramificação, poderá usar a ramificação padrão existente (que pode ser main).


Portanto, se você receber o Error: src refspec master does not match anyerro ao tentar enviar para o mestre, o motivo mais viável é que a masterramificação não existe.


#git #error 

Erro: Src Refspec Master Não Corresponde A Nenhum – Corrigir No Git