+# patchsync: Synchronizes a trunk, a branch, and a patch containing the
+# differences between them.
+# -- Matt McCutchen
+
+# If I had to update the version in the --version message separately, I would forget.
+PATCHSYNC_VERSION=2.4
+
+# usage: patchsync [--dry-run] <staging> [branch | patch]
+#
+# Patchsync is invoked on a "staging directory", which holds some configuration
+# (including the locations of the trunk, patch, and branch it is to synchronize)
+# and some synchronization state. It determines whether each of the trunk,
+# patch, and branch has changed since the last successful synchronization and
+# updates the patch or branch as appropriate:
+#
+# Changed since last sync Patchsync's behavior
+# -------------------------------------------------
+# Nothing Do nothing
+# Trunk only Update branch
+# Patch but not branch Update branch
+# Branch but not patch Update patch
+# Branch and patch Complain about conflict
+#
+# <staging>: path to the staging directory
+#
+# --dry-run: show what would happen without actually modifying the trunk, patch,
+# branch, or synchronization state
+#
+# {branch | patch}: force patchsync to update the specified thing from the
+# others instead of deciding automatically; you can use this argument to
+# revert or to resolve a conflict
+#
+# CAVEAT: Patchsync might make a mess if the trunk, patch, or branch is
+# modified in a way not hidden by the filters while patchsync is running!
+#
+# CAVEAT: Patchsync only notices creations, deletions, and modifications of
+# regular files in the trunk and branch, not other changes like empty directory
+# creations. If you make a change like that to the trunk, you can force
+# patchsync to update the branch.
+#
+# Staging directory format: A staging directory contains the following items:
+# "trunk", trunk directory or symlink to it
+# "patch", patch regular file or symlink to it
+# "branch", branch directory or symlink to it
+# [Why symlinks? Expose as much as possible to tools like symlinks(8).]
+# "settings", shell script defining the following shell functions:
+# - do_diff <trunk> <branch> <write-patch>: diff the specified trunk and
+# branch and write the patch to the specified file; define it to use
+# your favorite diff format
+# - example: exitoneok diff -urN $1 $2 \
+# | sed -re 's/^(\+\+\+|---) ([^\t]+).*$/\1 \2/' \
+# | exitoneok grep -v '^diff' >$3
+# - do_patch <patch> <convert-trunk-to-branch>: apply the patch to the
+# specified trunk; define it to understand your favorite diff format
+# - example: patch --no-backup-if-mismatch -d $2/ -p1 <$1
+# - Note: Patchsync runs these functions under "pipefail" and "set -e".
+# Caution: "set -e" is idiosyncratic; you may wish to && together
+# successive commands anyway. Patchsync provides an "exitoneok"
+# function you can use to treat an exit code of 1 as 0.
+# - There are several possible ways to handle failed hunks. The simplest
+# and safest is to make do_patch fail, but that's inconvenient for the
+# user, who must investigate the *.rej files in the staging directory
+# and either fix the patch or fix the branch and force updating the
+# patch. One could make do_patch succeed, but if the user then modifies
+# the branch, the failed hunks will merely be dropped from the patch,
+# which is probably unacceptable. The clever way is to let do_patch
+# succeed but make do_diff fail if any *.rej files exist in the branch.
+# "filters" (optional): rsync filters to use when accessing the trunk and
+# branch; hide filters apply to reading, protect filters to writing;
+# hint: you probably want to hide and protect build outputs
+#
+# Other usage: patchsync --new <trunk> <patch> <branch> <staging>
+# Mostly sets up a new staging directory for the given trunk, branch, and patch
+# at the given location. You still have to provide settings, and filters if
+# you want them.
+# - If one of the patch or branch exists, the other will be calculated when
+# you first synchronize.
+# - If both exist, you will get a conflict when you first synchronize and you
+# will need to specify which to update.
+# - If neither exists, you get an empty patch and a branch identical to the trunk.
+
+# Disable branch/.patchsync support because it's a bad idea in general, and the
+# cyclic symlink confuses Eclipse in particular. -- Matt 2006.11.30
+
+# Error handling
+function handle_error {
+ exec >&2
+ echo "Patchsync encountered an unexpected error! Aborting!"
+ echo "The failed command was: $1"
+ exit 2
+}
+trap 'handle_error "$BASH_COMMAND"' ERR
+set -o errtrace
+set -o pipefail
+
+# Make sure we have rsync.
+type rsync >/dev/null 2>&1 || \
+ { echo "Patchsync requires rsync, but there's no rsync on your path!" 1>&2; exit 1; }
+# If a cp2 is available, use it; otherwise define our own.
+type cp2 >/dev/null 2>&1 || function cp2 { exec rsync -rltE --chmod=ugo=rwx "$@"; }
+
+function exitoneok {
+ "$@" || [ $? == 1 ]
+}
+
+# wdpp_from <B> ==> the shortest relative prefix-path from directory B to the current directory
+# (prefix-path means it ends in a slash unless it's `' which means '.')
+# "patchsync" uses this to link-dest when copying the branch out.
+# "patchsync --new" uses it to reverse the staging dir path when creating symlinks.
+function wdpp_from {
+ AtoB="$1"
+ # Start with symlink-followed absolute prefix-paths without the initial slash.
+ # NOT bash builtin pwd; it tells us how we got here, not where we are
+ pA="$(/bin/pwd)/"
+ pA="${pA#/}"
+ pB="$(cd "$AtoB" && /bin/pwd)/"
+ pB="${pB#/}"
+ # Lop off the longest common prefix of components that we can.
+ # While first components are equal...
+ # (Empty correctly doesn't equal remaining)
+ while { [ -n "$pA" ] || [ -n "$pB" ]; } && [ "${pA%%/*}" == "${pB%%/*}" ]; do
+ # Remove them.
+ pA="${pA#*/}"
+ pB="${pB#*/}"
+ done
+ ans="$pA"
+ # Translate remaining components of $pB to ../s
+ while [ -n "$pB" ]; do
+ ans="../$ans"
+ pB="${pB#*/}"
+ done
+ # Double check; add dot to the end to enforce ending in a slash and handle empty ans
+ (cd "$AtoB" && [ "$ans." -ef /proc/self/fd/3 ]) 3<.
+ [ $? == 0 ]
+ # Yay
+ echo "$ans"
+}
+
+function hash_file {
+ # Lop off the filename and binary indicator
+ sha1sum -b "$1" | sed -re 's/^([^ ]*).*$/\1/'
+}
+
+function patchsync_sync {
+
+if [ "$1" == --dry-run ]; then
+ echo "Dry run mode."
+ dryrun=1
+ shift
+fi