Wilson Mar bio photo

Wilson Mar

Hello. Hire me!

Email me Calendar Skype call 310 320-7878

LinkedIn Twitter Gitter Google+ Youtube

Github Stackoverflow Pinterest

As if you’re an author at a book signing


This article describes how you can sign and tag commits in Git and push them to GitHub – the enterprise approach to using Git and GitHub.

Many teams request that commits be digitally signed to provide “non-repudiation” (make “I didn’t do it” not possible), and tagged for unique reference in git commands for traceability through the whole DevOps workflow.

I want you to feel confident that you’ve mastered this skill. That’s why this takes a hands-on approach where you type in commands and we explain the responses and possible troubleshooting. This is a “deep dive” because all details are presented.

Like a good music DJ, I’ve carefully arranged the presentation of concepts into a sequence for easy learning, so you don’t have to spend as much time as me making sense of the flood of material around this subject.

Sentences that begin with PROTIP are a high point of this website to point out wisdom and advice from experience. NOTE point out observations that many miss. Search for them if you only want “TL;DR” (Too Long Didn’t Read) highlights.

Stuck? Contact me and I or one of my friends will help you.

Typed git commands are currently shown here. TODO: Actions to achieve the same results in GUI IDEs will be added.


To associate a signing key you generate with your commits you push from git client to GitHub:

  1. git branch
  2. git add
  3. git commit
  4. git tag
  5. git push code
  6. git push tag

Click on each to go directly to it.

Install on Windows

Download the installer from

Generate Signing Key on Mac

First, install the tool used to generate signing keys.

Install GPGTools

On local Git:

  1. On a Mac, install GPGTools Suite from Homebrew specs (rather than downloading from https://www.gpgtools.org):

    brew cask install -g gpgtools

    The response:

    ==> Caveats
    Cask gpgtools installs files under "/usr/local".  The presence of such
    files can cause warnings when running "brew doctor", which is considered
    to be a bug in homebrew-cask.
    ==> Downloading https://releases.gpgtools.org/GPG_Suite-2016.07_v2.dmg
    ==> Verifying checksum for Cask gpgtools
    ==> Running installer for gpgtools; your password may be necessary.
    ==> Package installers may write to any location; options such as --appdir are ignored.
  2. Provide your password for the rest:

    ==> installer: Package name is GPG Suite
    ==> installer: Installing at base path /
    ==> installer: The install was successful.
    2016-07-30 17:48:16: [fixGpgHome] started with arguments: mac /Users/mac/.gnupg
    2016-07-30 17:48:16: Overwrite UID: mac
    2016-07-30 17:48:16: Overwrite GNUPGHOME: /Users/mac/.gnupg
    d2e82a39aaef128c61a91b1ca08d9931922d3327  /usr/local/MacGPG2/bin/gpg2
    990cae62c6aaf4529c54e28b5c929ade0245f6ee  /usr/local/MacGPG2/bin/gpg-agent
    2016-07-30 17:48:16: [fixGpgHome] Fixing '/Users/mac/.gnupg'...
    gpg: WARNING: unsafe ownership on configuration file `/Users/mac/.gnupg/gpg.conf'
    2016-07-30 17:48:16: [fixGpgHome] Fixing done
    2016-07-30 17:48:16: [fixGpgHome] fixGPGAgent started
    2016-07-30 17:48:16: [fixGpgHome] Fixing '/Users/mac/.gnupg/gpg-agent.conf'...
    2016-07-30 17:48:16: [fixGpgHome] Found working pinentry at: /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac
    2016-07-30 17:48:16: [fixGpgHome] Add new pinentry
    2016-07-30 17:48:16: [fixGpgHome] Start gpg-agent.
    2016-07-30 17:48:16: [fixGpgHome] UID = 0
    2016-07-30 17:48:16: [fixGpgHome] Start gpg-agent using uid: 'mac'
    gpg-agent[44407]: directory `/Users/mac/.gnupg/private-keys-v1.d' created
    GPG_AGENT_INFO=/Users/mac/.gnupg/S.gpg-agent:44408:1; export GPG_AGENT_INFO;
    gpg-agent[44408]: gpg-agent (GnuPG/MacGPG2) 2.0.30 started
    2016-07-30 17:48:16: [fixGpgHome] fixGPGAgent done
    2016-07-30 17:48:16: [fixGpgHome] done
    🍺  gpgtools was successfully installed!
  3. Validate install:

    gpg --list-keys

    Sample response:

    pub   2048D/00D026C4 2010-08-19 [expires: 2018-08-19]
    uid       [ unknown] GPGTools Team <team@gpgtools.org>
    uid       [ unknown] GPGMail Project Team (Official OpenPGP Key) <gpgmail-devel@lists.gpgmail.org>
    uid       [ unknown] GPGTools Project Team (Official OpenPGP Key) <gpgtools-org@lists.gpgtools.org>
    uid       [ unknown] [jpeg image of size 5871]
    sub   2048g/DBCBE671 2010-08-19 [expires: 2018-08-19]
    sub   4096R/0D9E43F5 2014-04-08 [expires: 2024-01-02]
  4. On a Mac: Verify that installation added the folder and files:

    ls ~/.gnupg

    The response:

    S.gpg-agent    gpg.conf    pubring.gpg    random_seed    trustdb.gpg
    gpg-agent.conf    private-keys-v1.d pubring.gpg~      secring.gpg

    On Windows, if you use “msysGit”, it comes with gpg. In Cygwin, get gnupg in Cygwin setup.

    Create GPG key

  5. Define git global user settings (if you haven’t already):

    git config --global user.name "Wilson Mar"
    git config --global user.email wilsonmar@gmail.com

    NOTE: The gpg program uses this information.

    This only needs to be done once since it’s saved.

  6. Verify:

    git config --global -l

    Sample response:

    user.name=Wilson Mar
  7. Create a PGP key associated with your global:

    gpg --gen-key

    The response:

    gpg (GnuPG/MacGPG2) 2.0.30; Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Please select what kind of key you want:
    (1) RSA and RSA (default)
    (2) DSA and Elgamal
    (3) DSA (sign only)
    (4) RSA (sign only)
  8. Select RSA by typing 1 and pressing Enter. This appears:

    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 
  9. Press Enter to accept 2048. This appears:

    Requested keysize is 2048 bits   
    Please specify how long the key should be valid.
          0 = key does not expire
       <n>  = key expires in n days
       <n>w = key expires in n weeks
       <n>m = key expires in n months
       <n>y = key expires in n years
  10. Press Enter to accept 0 for a key that lives forever.

    PROTIP: Most enterprises have a Security Standard that defines time frames when keys should be refreshed, which are getting shorter over time as computers get faster at breaking encryption.

    The response:

    Is this correct? (y/N) 
  11. Press y to accept it. You’ll then see:

    GnuPG needs to construct a user ID to identify your key.
    Real name: 
  12. Type your name, press Enter, type your email, press Enter, type a Comment, and press Enter. You’ll then see:

    Email address: wilsonmar@gmail.com
    Comment: Hi                       
    You selected this USER-ID:
     "Wilson Mar (Hi) <wilsonmar@gmail.com>"
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? 
  13. Press O (capital o) to say OK. A pop-up dialog apprears with this message:

    You need a Passphrase to protect your secret key.  
  14. Open a text file and type your passphrase there, the copy the text and paste it in the dialog.

    PROTIP: Copying from a file ensures that typos won’t be getting you locked out.

    PROTIP: Enterprises usually have a security standard for strong pass phrases such as at least 5 words and contain numbers or special characters.

  15. Move your mouse immediately after clicking OK.

    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.
    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.

    Note the key ID:

    gpg: key 2E23C648 marked as ultimately trusted
    public and secret key created and signed.
    gpg: checking the trustdb
    gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
    gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
    pub   2048R/2E23C648 2016-07-31
       Key fingerprint = 5D40 1E5F 0E5E 75EB 5F99  B96D 4E00 1337 2E23 C648
    uid       [ultimate] Wilson Mar <wilsonmar@gmail.com>
    sub   2048R/80E44BBF 2016-07-31

    NOTE: The key just created is not stored in your present working directory.

  16. List the keys again for the physical location of gpg’s keychain:

    gpg --list-keys

    Snippet of a sample response:

    pub   2048R/2E23C648 2016-07-31
    uid       [ultimate] Wilson Mar <wilsonmar@gmail.com>

    Configure Git Signing Key

  17. Set a default signing key globally (replacing “2E23C648” with yours):

    git config --global user.signingkey 2E23C648

    No response is returned. So:

  18. Verify

    git config --global -l

    Sample response contains:


    Backup and Share Public key

    In order for others to verify your signature:

  19. Be at the same directory (as before).

  20. Export your public key to the present working directory (a GitHub repository folder) as a sharable file (replacing the email address in this example):

    gpg --armor --export wilsonmar@gmail.com > wilsonmar@gmail.com.pub

  21. Optionally, view the file (substituting your own file’s name):

    cat wilsonmar@gmail.com.pub

    NOTE: The file contents begins with “—–BEGIN PGP PUBLIC KEY BLOCK—–”.

    You can email or even post this on a public website.

  22. Better yet, store the key as a Git object to hold key data.

    git hash-object -w wilsonmar@gmail.com.pub

  23. Copy the response (a hash) and paste it at the end of this command:

    git tag username-pub-rsa 7ed5bd7f134819ea5a3d90a1d5ec7f83a7545381

  24. Copy the response and paste it to the end of this command:

    git tag wilsonmar@gmail.com.pub-rsa 7ed5bd7f134819ea5a3d90a1d5ec7f83a7545381

    No response is returned.

  25. Verify the tags now:

    git tag

Again, all the above is done only once.

Git Branch

PROTIP: Create a branch to make changes so that the commit submitted could be evaluated on its own. Keeping a master branch “clean” enables different changes to be isolated in various branches.

  1. Create a new branch “edit256” in the current repo during git checkout, which updates files in Git’s working tree to match the version in the index or the specified tree.

    git checkout -b edit256

    Git Add

  2. Get a list of branches (with a command in singlar case):

    git branch

  3. Make a change to a file in the repo.

    REMEMBER NOTE: When a change is made to a file, that file is removed from Git’s stage.

  4. Get a list of what has changed:

    git status

  5. Add files changed into Git’s Stage to make it eligible for Git commit:

    git add . -A

    Instead of “.” representing all files, you can specify a single file to selectively stage for a special commit.

    The -A also acts on files which need to be deleted, which the -a option of git commit does not do.

    Git commit options

  6. Be aware of all the options to the commit command:

    git commit -h

    The response:

    usage: git commit [<options>] [--] <pathspec>...
     -q, --quiet           suppress summary after successful commit
     -v, --verbose         show diff in commit message template
    Commit message options
     -F, --file <file>     read message from file
     --author <author>     override author for commit
     --date <date>         override date for commit
     -m, --message <message>
                           commit message
     -c, --reedit-message <commit>
                           reuse and edit message from specified commit
     -C, --reuse-message <commit>
                           reuse message from specified commit
     --fixup <commit>      use autosquash formatted message to fixup specified commit
     --squash <commit>     use autosquash formatted message to squash specified commit
     --reset-author        the commit is authored by me now (used with -C/-c/--amend)
     -s, --signoff         add Signed-off-by:
     -t, --template <file>
                           use specified template file
     -e, --edit            force edit of commit
     --cleanup <default>   how to strip spaces and #comments from message
     --status              include status in commit message template
     -S, --gpg-sign[=<key-id>]
                           GPG sign commit
    Commit contents options
     -a, --all             commit all changed files
     -i, --include         add specified files to index for commit
     --interactive         interactively add files
     -p, --patch           interactively add changes
     -o, --only            commit only specified files
     -n, --no-verify       bypass pre-commit hook
     --dry-run             show what would be committed
     --short               show status concisely
     --branch              show branch information
     --porcelain           machine-readable output
     --long                show status in long format (default)
     -z, --null            terminate entries with NUL
     --amend               amend previous commit
     --no-post-rewrite     bypass post-rewrite hook
     -u, --untracked-files[=<mode>]
                           show untracked files, optional modes: all, normal, no. (Default: all)

    NOTE: “pre-commit hook” is a feature available in GitHub Enterprise to:

    • Require commit messages to follow a specific pattern or format, such as including a valid ticket number or being over a certain length.
    • Lock a branch or repository by rejecting all pushes.
    • Prevent sensitive data from being added to the repository by blocking keywords, patterns or filetypes.
    • Prevent a PR author from merging their own changes.

    Sign a Commit

  7. PROTIP: If you’re switching among several email/GitHub accounts, in your git command specify the signing key for the account being used:

    git commit -a -m"Issue 234 signed" --gpg-sign=2E23C648

    PROTIP: Alternately, use a custom command such as gitc defined below.

    The first time a signature is used, a dialog such as this pops up:

    PROTIP: Check “Save in Keychain” so you don’t have to paste in the passphrase every time you commit.

    Subsequent uses display a message such as this:

    You need a passphrase to unlock the secret key for
    user: "Wilson Mar <wilsonmar@gmail.com>"
    2048-bit RSA key, ID 2E23C648, created 2016-07-31

    Either way, a message such as this in response:

    [master 6f3e993] update gpg signed
     2 files changed, 322 insertions(+), 103 deletions(-)
  8. Validate the signature of a commit according to this doc:

    git log --show-signature -n 2

    The “-n 2” limits output to 2 lines.

    Sample response:

    commit 0d04dee2cfd85d5c53e16c6347884691b4b9fb8b
    gpg: Signature made Sat Jul 30 18:36:10 2016 MDT using RSA key ID 2E23C648
    gpg: Good signature from "Wilson Mar <wilsonmar@gmail.com>" [ultimate]
    Author: Wilson Mar <wilsonmar@gmail.com>
    Date:   Sat Jul 30 18:35:13 2016 -0600
  9. Press q to quit the listing if it extends beyond one screen.

Tag a specific Git commit

On local Git:

  1. In Terminal, be in your repo’s folder.
  2. For more information, see Git docs or get help (with one dash) associated with the tag command:

    git tag -help

    The response:

    usage: git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]
    or: git tag -d <tagname>...
    or: git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]
       [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
    or: git tag -v <tagname>...
     -l, --list            list tag names
     -n[<n>]               print <n> lines of each tag message
     -d, --delete          delete tags
     -v, --verify          verify tags
    Tag creation options
     -a, --annotate        annotated tag, needs a message
     -m, --message <message>
                           tag message
     -F, --file <file>     read message from file
     -s, --sign            annotated and GPG-signed tag
     --cleanup <mode>      how to strip spaces and #comments from message
     -u, --local-user <key-id>
                           use another key to sign the tag
     -f, --force           replace the tag if exists
     --create-reflog       create a reflog
    Tag listing options
     --column[=<style>]    show tag list in columns
     --contains <commit>   print only tags that contain the commit
     --merged <commit>     print only tags that are merged
     --no-merged <commit>  print only tags that are not merged
     --sort <key>          field name to sort on
     --points-at <object>  print only tags of the object
     --format <format>     format to use for the output

    This also appears if there is an error in the command entered.

    List commit hashes

  3. PROTIP: Get a list of commits over the last day so you can highlight and copy the hash code associated with the commit you want to tag:

    git log --pretty=format:"%h %s %ad" --graph --since=1.days --date=relative

    PROTIP: Rather than typing this long command every time, use a custom command such as gits defined below.

    Sample response:

           * 366121d add git-commits 1 hours ago 
           * 0d04dee New feature #232 10 hours ago
  4. Add an annotated signed tag to associate with the hash of a commit (above):

    git tag -a v01.04 366121d -m "my version 01.04" -s -q

    PROTIP: Come up with a standard on your team on whether to zero-pad leading zeroes for sorting. Git and GitHub are smart enough to sort what comes before and after dots separately. But other programs may not be that smart.

    The “-q” is added to quiet the summary signing response, such as this example:

    You need a passphrase to unlock the secret key for
    user: "Wilson Mar <wilsonmar@gmail.com>"
    2048-bit RSA key, ID 2E23C648, created 2016-07-31
  5. List tags (with the command in the singular):

    git tag

    Git Push code

  6. List locations to push:

    git remote -v

    Sample response:

    origin   https://github.com/wilsonmar/wilsonmar.github.io.git (fetch)
    origin   https://github.com/wilsonmar/wilsonmar.github.io.git (push)

    If you forked a repo, you would also define an “upstream” location for the original repo.

  7. Transfer all commits to the remote server that are not already there:

    git push origin develop

    NOTE: “origin” location does not need to be specified if it’s the default. The “master” branch does not need to be specified if it’s the default of origin.

    PROTIP: It’s a good habit to specify the location and branch with a push because enterprise developers often switch among several ones.

    The branch needs to be specified if it’s to “upstream” location or other non-origin location.

    NOTE: Push to certain branches can trigger hooks defined in GitHub.

    Sample response:


Git Push tags


Extract and Verify tag

After signed and tagged commits are pushed to GitHub, someone can pull them locally.

  1. Extract the key from a tag and add it to their keyring for tag verification:

    git cat-file blob username-pub-rsa | gpg --import

  2. To obtain information about a specific tag:

    git tag -v v01.04

    A sample response:

    object 47337b453001a963b260df64848c8602cc61e304
    type commit
    tag v01.04
    tagger Wilson Mar <wilsonmar@gmail.com> 1469956897 -0600
    my version 01.04
    gpg: Signature made Sun Jul 31 03:21:38 2016 MDT using RSA key ID 2E23C648
    gpg: Good signature from "Wilson Mar <wilsonmar@gmail.com>" [ultimate]

    Push tags

    NOTE: A separate push command is needed to push tags up to GitHub.

  3. Transfer all tags to the remote server (GitHub):

    git push origin --tags

    CAUTION: A separate request is necessary to push tags than code.

Remove tags

  1. Optionally, to remove a tag object locally:

    git tag -d wilsonmar@gmail.com.pub-rsa

    Sample response:

    Deleted tag 'wilsonmar@gmail.com.pub-rsa' (was 7ed5bd7)
  2. Optionally, to remove the tag on GitHub:

    git push origin :refs/tags/wilsonmar@gmail.com.pub-rsa

Custom Git commands

Custom commands can be defined as an alias command in the operating system or as a custom Git sub-command defined in a .gitattributes file for a specific repo.

Since the command is related to use of Git, that is preferred.

gits alias command

PROTIP: Define a command-line alias for custom commands which do not require options to be specified.

  1. First make sure that the command is available for use:


    The desired expected response is “command not found”.

  2. Use a text editor to edit your Mac’s .bash_profile (substituting nano for atom, subl, or whatever you have installed):

    nano ~/.bash_profile

    Copy this to add to the bottom of the file:

    alias gits='clear;git status;git log --pretty=format:"%h %s %ad" --graph --since=1.days --date=relative'

    Semicolons separate different commands.

    In nano press ‘control+o’ to write the file out. Press Enter to confirm. Press ‘control+x’ to exit the file.

  3. Refresh the bash shell environment:

    source ~/.bash_profile

More about this technique:

  • https://coolestguidesontheplanet.com/make-an-alias-in-bash-shell-in-os-x-terminal/

Custom Script with parameters

PROTIP: Define a custom command script which receives an annotation as a parameter.

  1. First make sure that the command you want is available for use. For example:


    In this example, “c” is for commit, “w” for wilson, “g” for gmail.

    The desired expected response is “command not found”.

  2. In a text editor, create a new file containing:

    ((!$#)) && echo No annotation parameter, so command ignored! && exit 1
    git commit -a -m"$1" --gpg-sign=2E23C648
    git push
    The line starting with "((!$#))" exits if the required parameter is not provided.
    The $1 is a parameter variable whose value is provided in the first parameter after the command.
    Remove "git push" and/or "gits" as you see fit.
  3. On a Mac: Save the file named gitw.sh.

    The .sh specifies that the file is a shell file extension on a Mac.

    Alternately, .cmd specifies that the file is a command-file on Windows.

  4. Give the file executable permissions:

    chmod a+x gitcwg.sh

  5. Try it:

    ./gitcwg "issue #235"

    More on this topic:

    • http://thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps/

More Resources

Other blogs about this topic:

  • http://candidtim.github.io/git/2012/11/27/git-signed-tags.html
  • https://ssd.eff.org/en/module/how-use-pgp-mac-os-x
  • https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work
  • https://ariejan.net/2014/06/04/gpg-sign-your-git-commits/


From the Git Real series by Gregg Pollack of CodeSchool.com:

  1. Git Real - Introduction 8:19 about different people working on the same file.

  2. Git Real - Staging & Remotes

  3. Git Real - Cloning & Branching

  4. Git Real - Collaboration Basics

  5. Git Real - Branching

  6. Git Real - Rebase blong to us

  7. Git Real - History and Configuration

22 Videos from the GitHub Training channel’s 2013 GitHub & Git Foundations series by Matthew McCullough (@matthewmccullough), Tim McCullough (@timmccullough), Brett Beer (@brntbeer), and Tim Berglund (@tlberglund). Filmed with multiple cameras and Lounge Lizard music:

  1. Introduction

  2. Setup

  3. Config

  4. Init

  5. Diff

  6. Log

  7. Remove

  8. Move

  9. Ship of Theseus

  10. Ignore

  11. Branch

  12. Checkout

  13. Merge

  14. Network

  15. GUI

  16. Intro to GitHub

  17. Forking

  18. Pull Requests

  19. Reset

  20. Reflog

  21. Rebase

More on DevOps

This is one of a series on DevOps:

  1. DevOps_2.0
  2. User Stories for DevOps

  3. Choices for DevOps Technologies
  4. Java DevOps Workflow
  5. AWS DevOps (CodeCommit, CodePipeline, CodeDeploy)
  6. AWS server deployment options

  7. Digital Ocean
  8. Cloud regions
  9. AWS Virtual Private Cloud
  10. Azure Cloud Powershell

  11. Git and GitHub vs File Archival
  12. Git Commands and Statuses
  13. Data Security GitHub
  14. Git Commit, Tag, Push
  15. Git Utilities
  16. GitHub API

  17. TFS vs. GitHub

  18. Jenkins Server Setup
  19. Jenkins Plug-ins
  20. Jenkins Freestyle jobs
  21. Jenkins2 Pipeline jobs using Groovy code in Jenkinsfile

  22. Dockerize apps
  23. Docker Setup
  24. Docker Build

  25. Maven on MacOSX

  26. Powershell Ecosystem
  27. Powershell on MacOS
  28. Powershell Desired System Configuration

  29. Ansible

  30. MySQL Setup

  31. SonarQube static code scan

  32. API Management Microsoft
  33. API Management Amazon

  34. Scenarios for load