Pre-Commit Scripts

Pre-commit scripts are run when the user clicks Commit Changes in the commit view, but before the commit is started (see the commit process for more information).

A pre-commit script can be run in one of two ways:

  1. Once for the entire working copy

    The script is run once. The POSIX path of the working copy's top folder is passed to the script as its only parameter.

    Pre-commit scripts are run even if no items are checked in the commit view's Changes list.

  2. Once for each checked item in the commit view's Changes list

    The script is run multiple times, once for each checked item in the commit view's Changes list. The POSIX path of the changed item is passed to the script as its only parameter.

    Cornerstone will abort script execution as soon as the first error is encountered.

The user is prompted when a pre-commit script raises an error — i.e. exits with a value other than zero — with the option to continue or cancel the commit.

Pre-commit scripts can therefore be used to validate changes before a commit.

Prepare Scripts vs. Pre-Commit Scripts

On the face of it, pre-commit scripts sound similar to prepare scripts. This is true when run once for the entire working copy. However there are significant differences when run once per item:

  1. Prepare scripts can modify the working copy to change the set of items to be committed. This is not the case with a pre-commit script.

    For example, if you need to change unmodified files (or revert changed files) — thus changing the set of files to be committed — then you should use a prepare script.

  2. The user may invest significant time writing their log message selecting the files for commit. Subsequently aborting the commit as a result of pre-commit validation would not be appropriate.

    Pre-commit validation is better suited to prepare scripts.

  3. Per-item pre-commit scripts are run once for each item to be committed. Unchecked items in the commit view's Changes list are not committed and are therefore not passed to the pre-commit script.

    Time-consuming operations should therefore be placed in pre-commit scripts.

  4. The user has the opportunity to cancel the commit while the commit view is displayed. Destructive, non-repeatable operations should therefore be run as pre-commit scripts.

Selecting a Script for Execution

Pre-commit scripts are defined for each working copy:

  1. Start a commit operation by selecting one or more changed items and choosing the Commit command from the Working Copy menu (key equivalent ⌘T).

  2. Expand the Options section at the bottom of the commit view.

  3. Locate the Before continuing options in the Actions section.

  4. Check the Run script button.

  5. Use the pop-up to choose the script to run.

  6. Specify whether the script should be run once for the entire working copy or once for each item.

Possible Uses for Pre-Commit Scripts

Example Script

Purpose

Create a 120px thumbnail image for the specified image and store it in a custom property called thumbnail. Do nothing if the input file is not an image

The script also demonstrates how to update the sidecar property (thumbnail:attributes) created by Cornerstone for file properties such that additional information about the thumbnail is displayed in the inspector

Type Item
Argument Path to an image file
#!/usr/bin/env bash

# Updates the thumbnail of the specified file and writes the sidecar
# property for the thumbnail such that information about the thumbnail
# is displayed in Cornerstone's inspector
#
# Usage:
#   GenerateThumbnail file
#
# Arguments:
#   $1 path to the file to generate a thumbnail for

dir="${1%/*}"

# =============================================================================
#
# Generates a thumbnail for file specified as $1 and stores
# it as the property named $2
#
# Arguments:
#   $1 The path of the file to generate a thumbnail for
#   $2 The name of the property to assign the thumbnail to
#   $3 The name of the thumbnail file
#   $4 The format of the thumbnail, e.g. "jpeg" or "png"
#
# =============================================================================

function generate_thumbnail()
{
  # Generate a 120px thumbnail and assign it to the specifed property
  
  sips -Z 120 -s format "$4" "$1" --out "$dir/$3"
  svn propset "$2" "$1" --file "$dir/$3"
  
  # Get file system and image information about the thumbnail file
  # and store it globally where it can be accessed later as needed

  eval $(stat -s "$dir/$3")
  sips -g allxml "$dir/$3" > "$dir/sips.plist"

  width=$(defaults read "$dir/sips" pixelWidth)
  height=$(defaults read "$dir/sips" pixelHeight)

  # Remove the temporary files created by this function

  rm "$dir/$3"
  rm "$dir/sips.plist"
}

# =============================================================================
#
# Generates a thumbnail attributes sidecar property and stores
# it as the property named $2:attributes
#
# Arguments:
#   $1 The path of the file to assign the property to
#   $2 The name of the property to assign the attributes to
#   $3 The name of the thumbnail file
#   $4 The UTI of the thumbnail file, e.g. "public.jpeg", or "public.png"
#
# =============================================================================

function generate_attributes()
{
  # Create or update the sidecar property withinformation about the 
  # thumnail we generated

  svn propget "$2" "$1" > "$dir/attrs.plist"

  # Generate a 48px thumbnail icon and format it as uppercase hex

  sips -Z 48 -s format png "$1" --out "$dir/icon.png"
  data=$(cat "$dir/icon.png" | hexdump -v -e '1/1 "%02x" ""' | tr "[:lower:]" "[:upper:]")

  # Assign file system and image information to the file's sidecar attributes property.
  # Note that the st_* vars are initialized by the call to stat in generate_thumbnail()

  defaults write "$dir/attrs" name "$3"
  defaults write "$dir/attrs" display-name "${3%.*}"
  defaults write "$dir/attrs" size $st_size
  defaults write "$dir/attrs" creation-date $(date -r $st_ctime +"%Y-%m-%dT%TZ")
  defaults write "$dir/attrs" modification-date $(date -r $st_mtime +"%Y-%m-%dT%TZ")
  defaults write "$dir/attrs" type "file"
  defaults write "$dir/attrs" kind "Preview Document"
  defaults write "$dir/attrs" immutable -bool false
  defaults write "$dir/attrs" uti "$4"
  defaults write "$dir/attrs" posix-permissions -int 420
  defaults write "$dir/attrs" extension-hidden -bool true
  defaults write "$dir/attrs" icon -data $data
  defaults write "$dir/attrs" additional-information "$width x $height"

  # Using 'defaults' to write to the attributes plist file causes the file to
  # be converted from xml to binary format. Convert the file back to xml format
  # using the plutil utility and then assign the plist file to the thumbnail's
  # sidecar attributes property

  plutil -convert xml1 "$dir/attrs.plist"
  svn propset "$2" "$1" --file "$dir/attrs.plist"

  # Remove temporary files created by this function

  rm "$dir/attrs.plist"
  rm "$dir/icon.png"
}

# =============================================================================
#
# Entry point for the script
#
# Arguments:
#   $1 The path of file to generate the thumbnail for
#   $2 The name of the property to assign the thumbnail to
#   $3 The format of the thumbnail file, e.g. "jpeg" or "png". The thumbnail's
#      file extension and UTI will also be derived from this value.
#   $4 Optional. Specify "--with-attrs" to store thumbnail attributes in a 
#      sidecar property
#
# =============================================================================

function run()
{
  # Check that the specified file is an image file, i.e. has a UTI that is
  # derived from "public.image". Exit silently if input file is not an image
  
  if [ $(mdls -name kMDItemContentTypeTree "$1" | grep -c "\"public.image\"") -eq 0 ]
  then
    return 0
  fi
  
  # Generate the thumbnail for the input file
  
  name="${1##*/}"
  name="${name%.*}-thumbnail.$3"
  
  generate_thumbnail "$1" "$2" "$name" "$3"

  # Generate attributes property for the thumbnail if the --with-attrs
  # argument was specified. Otherwise delete attributes property.

  attrs="$2:attributes"

  if [ "${4-}" == "--with-attrs" ]
  then
    generate_attributes "$1" "$attrs" "$name" "image.$3"
  else
    svn propdel --quiet "$attrs" "$1"
  fi
}

# Run the script by calling the run() entry point function. Omit the
# --with-attrs argument to not generate an ":attributes" sidecar property.

run "$1" "thumbnail" "jpeg" "--with-attrs"

$1 is used to reference the path of the item that will be committed. It is supplied as the script's single argument.

It is recommended that $1 be placed in quotation marks in order that the script functions correctly with paths containing space characters.