Support for pairing with git
Branch: master
Clone or download
jszwedko Merge pull request #69 from philals/patch-1
Adds link to VSCode extention
Latest commit 1a37e98 Jun 26, 2018
Type Name Latest commit message Commit time
Failed to load latest commit information.
git-duet-commit Git-duet installs prepare-commit-msg hook Feb 23, 2018
git-duet-install-hook Add trailer when a new co-author amends a commit Mar 15, 2018
git-duet-merge Git-duet installs prepare-commit-msg hook Feb 23, 2018
git-duet-pre-commit Switch to gvt now that GOVENDOREXPERIMENT exists Feb 7, 2016
git-duet-prepare-commit-msg Prepare-commit-msg hook does not set git config trailer.ifexists Mar 19, 2018
git-duet-revert Git-duet installs prepare-commit-msg hook Feb 23, 2018
git-duet Avoid duplicate Co-authored-by trailers Mar 15, 2018
git-solo Update location for getopt Jan 31, 2017
internal Git-duet installs prepare-commit-msg hook Feb 23, 2018
scripts Fix RubyMine wrapper script. Apr 14, 2017
test Fix error when running git-(solo|duet) outside of a git repo Apr 16, 2018
vendor Update location for getopt Jan 31, 2017
.gitignore Switch to gvt now that GOVENDOREXPERIMENT exists Feb 7, 2016
.travis.yml Bump Go version in .travis.yml to 1.10.2 Jun 2, 2018 Add CHANGELOG note for 0.6.0 Mar 21, 2018 Add top level .git-authors configuration Apr 18, 2017
LICENSE Include license and remove WIP label May 27, 2015 Adds link to VSCode extention Jun 24, 2018
configuration.go Fix error when running git-(solo|duet) outside of a git repo Apr 16, 2018
duet.go Switch to gvt now that GOVENDOREXPERIMENT exists Feb 7, 2016
exit_code.go Switch to gvt now that GOVENDOREXPERIMENT exists Feb 7, 2016
git_config.go Prepare-commit-msg hook does not set git config trailer.ifexists Mar 19, 2018


Build Status

Pair harmoniously! Working in a pair doesn't mean you've both lost your identity. git-duet helps with blaming/praising by using stuff that's already in git without littering your repo history with fictitous user identities. It does so by utilizing gits commit committer attributes to store the identity of the second pair.


$ cat <<-EOF > ~/.git-authors
  jd: Jane Doe; jane
  fb: Frances Bar
  domain: awesometown.local

$ git duet jd fb

$ touch foo

$ git duet-commit -a -m 'initial commit'
[master (root-commit) ce78563] initial commit
 Author: Jane Doe <jane@awesometown.local>
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 foo

$ git show --format=full
commit ce7856371e3e3a6d05f1b66af96f086df071e783
Author: Jane Doe <jane@awesometown.local>
Commit: Frances Bar <>

    initial commit

    Signed-off-by: Frances Bar <>

diff --git a/foo b/foo
new file mode 100644
index 0000000..e69de29



  1. See releases page for binary downloads, place in your $PATH.
  2. Install using Homebrew from the git-duet homebrew tap
  3. Build from source: GOVENDOREXPERIMENT=1 go get



Make an authors file with email domain, or if you're already using git pair, just symlink your ~/.pairs file over to ~/.git-authors. You can also set a repository specific authors file by placing a .git-authors file at the root of your git repository.

  jd: Jane Doe; jane
  fb: Frances Bar
  domain: awesometown.local

git duet will use the git pair YAML structure if it has to (the difference is the top-level key being pairs instead of authors) e.g.:

  jd: Jane Doe; jane
  fb: Frances Bar
  domain: awesometown.local

If you want your authors file to live somewhere else, just tell git-duet about it via the GIT_DUET_AUTHORS_FILE environmental variable, e.g.:

export GIT_DUET_AUTHORS_FILE=$HOME/.secret-squirrel/git-authors
# ...
git duet jd am


Set two authors (pairing):

git duet jd fb

Set one author (soloing):

git solo jd

Committing (needed to set --signoff and export environment variables):

$ git duet-commit -v [any other git options]

Reverting (needed to set --signoff and export environment variables):

git duet-revert -v [any other git options]

Merging (needed to set --signoff and export environment variables):

git duet-merge -v [any other git options]

Rebasing (resets the committer to the committer of the current pair):

git rebase -i --exec 'git duet-commit --amend'

Suggested aliases:

dci = duet-commit
drv = duet-revert
dmg = duet-merge
drb = rebase -i --exec 'git duet-commit --amend'

Note: git-duet only sets the configuration to use via git duet-commit, git duet-revert, and git duet-merge. Using git solo (or git duet) will not effect the configured and This allows git commit to be used normally outside of git-duet. You can set an environment variable, GIT_DUET_SET_GIT_USER_CONFIG to 1 to override this behavior and set the and fields.

Another option for git rebaseing with git-duet is to use courtesy of @pivotaljohn. This script uses git filter-branch to update the names of all of the authors/committers to the currently set pair. It acts on your active branch (using the passed ref as the start point).

"Co-authored-by" trailer support

This feature is experimental. Please open an issue if you find a bug or if you have suggestions how usability could be improved.

⚠️ If you use git commit -v with git < 2.14.0 you'll find that the Co-authored-by trailer is mistakenly added after the verbose commit output. Please upgrade git if you encounter this issue.

Set the environment variable GIT_DUET_CO_AUTHORED_BY to 1 if you want to have a "Co-authored-by" trailer for each co-author in your commit message rather than having a "Signed-off-by" trailer for the committer.

If GIT_DUET_CO_AUTHORED_BY is set, git duet will install a prepare-commit-msg hook file into the local repository by default. If, in addition, GIT_DUET_GLOBAL is set, git-duet will instead install the prepare-commit-msg hook file into git config --global init.templatedir. If the value git config --global init.templatedir is not set, git-duet will set it to $HOME/.git-template.

GIT_DUET_CO_AUTHORED_BY implicitly sets GIT_DUET_SET_GIT_USER_CONFIG so that git duet and git solo set the author for normal git commands.

The common workflow is as follows:

  • run git duet (which will install the prepare-commit-msg hook)
  • if you have GIT_DUET_GLOBAL set, run git init once in existing repos so that the hook file gets copied from the init.templatedir
  • thereafter, use the normal git commands (i.e. git commit, git merge rather than the git-duet-subcommands git duet-commit, git duet-merge)
  • the prepare-commit-msg hook will append a Co-authored-by trailer for each co-author

The above workflow ignores GIT_DUET_ROTATE_AUTHOR since there is no need to rotate authors because all authors get attribution in their Github's contribution graph as described here. If you do care about rotating author and co-author, you need to call git duet with the respective author initials.

When amending a commit and the co-author has changed, a new Co-authored-by trailer will get appended for that co-author. In order to avoid duplicate Co-authored-by trailers (i.e. trailers with the same co-author), set git config [--global] trailer.ifexists addIfDifferent to override the default value addIfDifferentNeighbor.

If you want to opt out of this feature, unsetting GIT_DUET_CO_AUTHORED_BY is not sufficient. You also need to manually delete the prepare-commit-msg hook file in your repo.

Global Config Support

If you're jumping between projects and don't want to think about managing them all individually, you can operate on the global git config:

git solo -g jd
git duet --global jd fb

If you do this habitually, you can set the GIT_DUET_GLOBAL environment variable to true to always operate on the global git config:

export GIT_DUET_GLOBAL=true # consider adding this to your shell profile
git solo jd

You can also set it to false to always operate on the local config, even if the global flag is used.

Rotating author/committer support

Sometimes while pairing you want to share the authorship love between the pairs. If you set GIT_DUET_ROTATE_AUTHOR=1 it will swap the author and committer (if there is one) on every git duet-commit (after the commit).

This operates on whichever config the authorship was drawn from (e.g. if the author/committer was set in the repository git config, it will rotate these even if GIT_DUET_GLOBAL is specified).

Mobbing support

Git duet supports more than 2 people working at a time by specifying more sets of initials:

git duet jd fb zp

If you do not set GIT_DUET_ROTATE_AUTHOR, then git-duet will use jd and fb as the author and committer respectively. If you have GIT_DUET_ROTATE_AUTHOR set then git-duet will rotate with each commit. The first commit will have jd as the author, and fb as the committer. The second commit will have fb as the author and zp as the committer and so on.

Note: This feature uses , as the delimiter which will fail to parse properly if the user's name or e-mail address contains a ,.

Email Configuration

By default, email addresses are constructed from the first initial and last name ( or optional username after a ;) plus email domain, e.g. with the following authors file:

  jd: Jane Doe; jane
  fb: Frances Bar
  domain: eternalstench.bog.local

After invoking:

git duet jd fb

Then the configured email addresses will show up like this:

git config
# -> jane@eternalstench.bog.local
git config duet.env.git-author-email
# -> jane@eternalstench.bog.local
git config duet.env.git-committer-email
# ->

A custom email template may be provided via the email_template config variable. The template should be a valid Go template string (see The object passed in has .Name, .Username, and .Initials.

Additional functions available to template:

  • toLower(s): lowercases string
  • toUpper(s): uppercases string
  • split(s, d): splits string on delimiter
  • replace(s, old, new, n): replaces old in s with new n times (set n to -1 to replace all)

If you need more complex logic, consider using the lookup function described below and awk.


  jd: Jane Doe
  fb: Frances Bar
email_template: '{{with replace .Name " " "-" -1}}{{toLower .}}{{end}}@hamster.local'

After invoking:

git duet jd fb

Then the configured email addresses will show up like this:

git config
# -> jane-doe@hamster.local
git config duet.env.git-author-email
# -> jane-doe@hamster.local
git config duet.env.git-committer-email
# -> frances-bar@hamster.local

If there are any exceptions to either the default format or a provided email_template config var, explicitly setting email addresses by initials is supported.

  jd: Jane Doe; jane
  fb: Frances Bar
  domain: awesometown.local
  jd: jane@awesome.local

Then Jane Doe's email will show up like this:

git solo jd
# ...
git config
# -> jane@awesome.local

Alternatively, if you have some other preferred way to look up email addresses by initials, name or username, just use that instead:

export GIT_DUET_EMAIL_LOOKUP_COMMAND="$HOME/bin/custom-ldap-thingy"
# ... do work
git duet jd fb
# ... observe emails being set via the specified executable

The initials, name, and username will be passed as arguments to the lookup executable. Anything written to standard output will be used as the email address:

$HOME/bin/custom-ldap-thingy 'jd' 'Jane Doe' 'jane'
# ->

If nothing is returned on standard output, email construction falls back to the decisions described above.

Order of Precedence

Since there are multiple ways to determine an author or committer's email, it is important to note the order of precedence used by git-duet:

  1. Email lookup executable configured via the GIT_DUET_EMAIL_LOOKUP_COMMAND environmental variable
  2. Email lookup from email_addresses in your configuration file
  3. Custom email address from Go template defined in email_template in your configuration file (see
  4. The username after the ;, followed by @ and the configured email domain
  5. The lower-cased first letter of the author or committer's first name, followed by . followed by the lower-cased last name of the author or committer, followed by @ and the configured email domain (e.g.

Git hook integration

If you'd like to regularly remind yourself to set the solo or duet initials, use git duet-pre-commit in your pre-commit hook:

(in $REPO_ROOT/.git/hooks/pre-commit)

exec git duet-pre-commit

The duet-pre-commit command will exit with a non-zero status if the cached author and committer settings are missing or stale. The default staleness cutoff is 20 minutes, but may be configured via the GIT_DUET_SECONDS_AGO_STALE environmental variable, which should be an integer of seconds, e.g.:

# ... do work for more than a minute
git commit -v
# ... pre-commit hook fires

If you want to use the default hook (as shown above), install it while in your repo like so:

git duet-install-hook pre-commit

Don't worry if you forgot you already had a pre-commit hook installed. The git duet-install-hook pre-commit command will refuse to overwrite it.

RubyMine integration

In order to have the author and committer properly set when committing via RubyMine, a git wrapper executable may be used to override any executions of git commit. Such an executable is available in the Git Duet repository, and may be installed somewhere in your $PATH like so:

curl -Ls -o ~/bin/rubymine-git-wrapper
chmod +x ~/bin/rubymine-git-wrapper

Given an install location of ~/bin/rubymine-git-wrapper as shown above, you would then update your RubyMine setting in Preferences => Version Control => Git to set Path to Git executable to the full path of ~/bin/rubymine-git-wrapper (with the ~ expanded). See issue #8 for more details.

VSCode extention

git-duet for VSCode, has been created by a member of the comunity. Please see the README for instructions/limitations. Please direct any issues to the extentions GitHub repo.

Differences from ruby git-duet

  • Running git solo or git duet with no initials outputs configuration in same format as when setting (env variables)
  • Does not set and (instead only sets namespaced variables) so that git commit continues to work as normal
  • Template format is now Go's text/template