bash wrapper around 'git commit' to automatically bump (Python) package CalVer and create matching CalVer tag on new commit
First of all I'd like to recommend shellcheck (online, GitHub) as a very useful tool to detect errors (even small misspellings of variables) and possible misbehaviours, etc.. With this you would have been warned about double-quoting your variables to prevent accidental splitting.

#! /bin/bash


There is no need to export these variables, since they won't be used after the termination of the script.
I'd personally stay clear from all caps variable names, if they are not environment variables. (By this convention, shellcheck will not complain for these variables if they are unset, as it assumes these to be provided by the shell.)
In principle, there is no need to double-quote a subshell, but it does not hurt.

timestamp="$( date '+%Y.%m.%d.%H%M' )"
verpath='.verpath'


Since you have just set the variable, this if clause will never trigger, as it is never empty. You probably would like to check whether the file exists (and is readable).  


if [ -z $VERPATH ]
then
  # Complain and exit
  echo "ERROR: Path to files with versions to update must be provided in {repo root}/.verpath"
  echo " "
  exit 1
fi



I second switching to the bash built-in test [[ expression ]].
For debugging purposes I try to stick with positive evaluations, usually inserting a statement that it works as intended.
While in a simple script like this, echo is perfectly fine, I still recommend looking at printf for more complex things. I'd probably use something like the following:

if [[ -f "$verpath" &amp;&amp; -r "$verpath" ]] ; then
  :  # do nothing (or give a debug statement)
else
  # complain and exit
  printf 'ERROR: Paths to files with versions to update should be provided in %s.\n\n' "$verpath"
  exit 1
fi


Be aware that read without the option -r will mangle backslashes (see SC2162). If for whatever reason the carriage return is missing from the last line, it will be ignored, better use the following while loop, where -n tests for a non-zero length string:

while read -r verfile || [[ -n "$verfile" ]] ; do
  : # do something
done &lt; "$verpath"


Like in the other answer suggested, I also prefer to keep track of temporary files, instead of assuming they have been created as intended. This will also spare you of reading in the file again.
Since you are making backups, I would also switch to a different ending like .bak. Initialise the array first:

declare -a backups


I'd again check whether the file exists and is readable.
Since these are backups, I'd also use the -a option to cp to archive them. I'd advise on exiting if the command fails. You might also want to consider checking whether the target backup file does already exist and exit if it does.
The inline substitution with sed might have a catch, if there are fewer of more spaces in the search pattern. I think you could be a bit more greedy.

if [[ -f "$varfile" &amp;&amp; -r "$varfile" ]] ; then 
  printf 'INFO: Updating %s.\n' "$varfile"
  # Add backup file to array
  backups+=( "$varfile.bak" )
  [[ -e "${backups[-1]} ]] &amp;&amp; { printf 'WARNING: backup %s exists.\n' "${backups[-1]}" ; exit 1 ; }
  cp -a -- "$varfile" "${[backups[-1]}" || { printf 'ERROR: backup of %s failed.\n' "$varfile" ; exit 1 ; }
  sed -i "s/^__version__.*$/__version__ = '$timestamp'/" "$verfile"
  git add "$verfile" || { printf 'ERROR: adding %s to repository failed\n' "$verfile" ; exit 1 ; }
  printf '\n'
else
  printf 'INFO: file %s not found.\n\n' "$verfile"
fi

# So user can see what was updated
sleep 2s


The bash built-in $@ is an array, it needs to be double-quoted to prevent resplitting. On the other hand, $* is a string, i.e. it looses the elements of an array. Unquoted, it will also be resplit, which is hardly ever anything you would want.
Always check the exit state of a command directly to prevent it being overwritten.

# Actually do the commit, passing through any parameters
if git commit "$@"
then
# Commit succeeded: tag HEAD with $timestamp and delete backup file(s).
  git tag -f "$TIMESTAMP" || { printf 'WARNING: Adding tag failed.\n' ; }
  rm -f -- "${backups[@]}"
else
# Commit failed: restore the backups to its original location.
  for file in "${backups[@]}" ; do
    mv -- "$file" "${file%.bak}" &amp;&amp; git reset HEAD "$file" &amp;&gt; /dev/null 
  done
fi




Generally I do all file handlings much more verbose, wrapping everything into a function and switch off the output of that function as necessary, i.e.

#! /bin/bash
mybackup ()
{
  while [[ -n $1 ]] ; do
    : # Things to do
    source="$1"
    shift
    target="$source.bak"
    cp -vp "$source" "$target" &gt;&amp;3 2&gt;&amp;3
  done
  : # more things to do ...
}

if [[ "$1" == "-s" ]] ; then
  exec 3&gt; /dev/null
  shift
else
  exec 3&gt;&amp;1
fi

mybackup "$@"


This would also make things simpler if you were to create logfiles of your script, but that is beyond this review.

Right now your versioning depends on an auxiliary file, that you probably create manually. I don't know how large your repositories are and how often you are using the versioning statements. It might be better to either check every file, or hardcode the ones that need to be checked into the script, instead of an external file.