#!/bin/sh
#
# p4
#
# Wrapper for Perforce "p4" command to extend it to support the
# more popular CVS update/commit model.
#
# Stick this in your $PATH before /usr/bin/p4.
#
# Author: Rick Richardson <rickr@mn.rr.com>
# http://http://home.mn.rr.com/richardsons/
#
# TODO (many):
# Handle CVS style options.
# TODO import:
# Handle repository vendortag releasetag args
# TODO rename:
# There is a Perforce problem if you do this:
# p4 rename xxx yyy; p4 commit; p4 rename yyy xxx; p4 commit
# The file will end up deleted. The only workaround I know of
# is to rename it three times (xxx->yyy->zzz->xxx).
#
P4=/usr/bin/p4
TMP=/tmp/p4blather
TMP=/tmp/p4blather$$
#
# Save the original value of P4CONFIG, so we can warn the user in
# our "p4 ws" command. Then set P4CONFIG if it wasn't already set.
#
saveP4CONFIG="$P4CONFIG"
if [ "$P4CONFIG" = "" ]; then
export P4CONFIG=.p4config
fi
#
# Report an error and exit
#
PROGNAME="$0"
error() {
echo "`basename $PROGNAME`: $1" >&2
exit 1
}
#
# Run the users favorite editor
#
editor() {
editor=vi
if [ "$P4EDITOR" != "" ]; then
editor="$P4EDITOR"
fi
$editor "$@"
}
#
# Version of "p4" command that eats the blather and returns just
# a valid exit status code. Yes, p4 needs an option to do this.
#
p4rc() {
$P4 -s "$@" >$TMP 2>&1
while read tag blather
do
case "$tag" in
error:)
rm -f $TMP
return 1
;;
esac
done < $TMP
rm -f $TMP
return 0
}
#
# Augmented "submit" command that allows a log message (with -m)
# and which allows more than one file argument. Apparently, users
# have asked Perforce for this going back to 1999, but they have
# been unresponsive. Another argument for open source software.
#
# N.B. the file/dir arguments are Perforce syntax.
#
p4submit() {
#
# Parse p4 submit options
#
IOPT=0
MOPT=0
COPT=
SOPT=
LOGMSG="<enter description here>"
LOGFILE=
while getopts "c:isF:m:" opt
do
case $opt in
c) COPT="$OPTARG";;
i) IOPT=1;;
s) SOPT="-s";;
F) LOGFILE="$OPTARG"; MOPT=1;;
m) LOGMSG="$OPTARG"; MOPT=1;;
esac
done
shift `expr $OPTIND - 1`
#
# handle p4 submit -c
#
if [ "$COPT" != "" ]; then
$P4 submit -c $COPT
return
fi
#
# For all other cases, prepare a changelog file...
#
TMP2=/tmp/p4files.$$
$P4 opened "$@" > $TMP2 2>/dev/null
if [ ! -s $TMP2 ]; then
rm -f $TMP2
echo "No files to submit from the default changelist."
return
fi
(
# Hey, this is great, we can put the log message up top
# where its quicker to get to in order to change.
echo "Description:"
if [ "$LOGFILE" = "" ]; then
echo " $LOGMSG"
else
cat $LOGFILE | sed 's/^/ /'
fi
$P4 change -o $SOPT 2>&1 | sed '/^Description/,$d'
echo "Files:"
sed -e 's/^/ /' -e 's/#/ #/' < $TMP2 | sort -u
rm -f $TMP2
) > $TMP
#
# If we are interactive, fire up the editor
#
if [ $MOPT = 0 -a $IOPT = 0 -a "$LOGFILE" = "" ]; then
sum=`md5sum $TMP`
editor $TMP
sum2=`md5sum $TMP`
if [ "$sum" = "$sum2" ]; then
echo "Error in submit specification."
echo "Change description missing. You must enter one."
echo -n "Hit return to continue..."
read answer
editor $TMP
sum2=`md5sum $TMP`
if [ "$sum" = "$sum2" ]; then
echo "Specification not corrected -- giving up."
rm -f $TMP
return
fi
fi
fi
#
# Feed the changelog file to the real p4 submit command
#
$P4 submit -i $SOPT < $TMP
rm -f $TMP
}
#
# Parse the global p4 options
#
P4ONLY=
while getopts "c:d:H:p:P:su:v:Vx:" opt
do
case $opt in
c) P4="$P4 -c $OPTARG";;
d) P4="$P4 -d $OPTARG";;
H) P4="$P4 -H $OPTARG";;
p) P4="$P4 -p $OPTARG";;
P) P4="$P4 -P $OPTARG";;
s) P4ONLY="$P4ONLY -s";;
u) P4="$P4 -u $OPTARG";;
v) P4="$P4 -v $OPTARG";;
x) P4ONLY="$P4ONLY -x $OPTARG";;
V) exec $P4 -V;;
esac
done
shift `expr $OPTIND - 1`
#
# Main program
#
case "$1" in
annotate)
shift
# TODO: handle CVS options
for file in "$@"
do
if [ -d "$file" ]; then
$P4 files $file/... |
while read pfile blather
do
p4pr.perl $pfile
done
else
p4pr.perl $file
fi
done
;;
commit)
shift
#
# Get CVS style args
#
LOGMSG=
LOGFILE=
RECURSE=1
while getopts "F:lm:Rh?" opt
do
case $opt in
# TODO: handle the rest of the CVS options
F) LOGFILE="$OPTARG";;
f) RECURSE=0;;
l) RECURSE=0;;
m) LOGMSG="$OPTARG";;
R) RECURSE=1;;
h|\?)
echo "Usage:"
echo " p4 commit [-n] [file|dir] ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
if [ $# = 0 ]; then
#
# Finger out which files need to be committed...
# ... ala CVS, current dir and lower only.
#
$P4 diff -se ./... 2>/dev/null |
while read file
do
p4rc edit $file || error "Can't p4 edit $file"
done
if [ $RECURSE = 1 ]; then
pfiles=./...
else
pfiles=./*
fi
else
#
# User supplied list of files/dirs to commit
#
pfiles=
for file in "$@"
do
if [ -d "$file" ]; then
$P4 diff -se $file/... 2>/dev/null |
while read dirfile
do
p4rc edit $dirfile ||
error "Can't p4 edit $dirfile"
done
pfiles="$pfiles $file/..."
else
p4rc edit $file || error "Can't p4 edit $file"
pfiles="$pfiles $file"
fi
done
fi
#
# Use our augmented version of p4 submit to do the dirty work
#
if [ "$LOGMSG" != "" ]; then
p4submit -m "$LOGMSG" $pfiles
elif [ "$LOGFILE" != "" ]; then
p4submit -F "$LOGFILE" $pfiles
else
p4submit $pfiles
fi
;;
import)
# TODO: handle repository vendortag releasetag args
find . -type f -print | $P4 -x - add
$P4 submit ./...
;;
log)
shift
# TODO: handle CVS options
# Convert directory names to p4 syntax
pfiles=
for file in "$@"
do
if [ -d "$file" ]; then
pfiles="$pfiles $file/..."
else
pfiles="$pfiles $file"
fi
done
if [ "$pfiles" = "" ]; then
pfiles=./...
fi
$P4 filelog -l $pfiles
;;
rename)
shift
if [ $# != 2 ]; then
error "Usage: p4 rename from to"
fi
FROM="$1"
TO="$2"
$P4 integrate $FROM $TO
$P4 delete $FROM
# TODO: theres a problem if you do this:
# p4 rename xxx yyy; p4 commit; p4 rename yyy xxx; p4 commit
# The file will end up deleted.
;;
stat)
shift
# Convert directory names to p4 syntax
pfiles=
for file in "$@"
do
if [ -d "$file" ]; then
pfiles="$pfiles $file/..."
else
pfiles="$pfiles $file"
fi
done
if [ "$pfiles" = "" ]; then
pfiles=./...
fi
echo "Files needing an update/sync..."
$P4 diff -sd $pfiles |
sed -e 's/^/ /'
$P4 sync -n $pfiles 2>&1 |
sed -e 's/^/ /' -e 's/File(s) up-to-date./None/' \
-e '/file(s) up-to-date./d'
echo
echo "Files needing a commit/submit..."
$P4 diff -se $pfiles |
sed -e 's/^/ /'
;;
submit)
#
# As a safety net, use unadorned $P4 submit if we can,
# otherwise, use our augmented version.
#
if [ $# -le 1 ]; then
exec $P4 "$@"
else
shift
p4submit "$@"
fi
;;
update)
# Do a sync and then a resolve
shift
#
# Get CVS style args
#
DATE=
while getopts "D:h?" opt
do
case $opt in
# TODO: handle the rest of the CVS options
D)
# Perforce has a very limited date syntax.
# Use the more flexible "date" command to parse
# the date we are given, then turn that into
# Perforce syntax.
DATE=`date -d "$OPTARG" "+@%Y/%m/%d:%H:%M:%S"`
if [ $? != 0 ]; then
exit 1
fi
;;
h|\?)
echo "Usage:"
echo " p4 submit [options] [file|dir] ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
if [ $# = 0 ]; then
# ala CVS, sync/resolve current dir and lower only.
# Pick up files that are missing...
$P4 diff -sd ./...$DATE | $P4 -x- sync -f
# Pick up files that have changed...
$P4 sync ./...$DATE
$P4 resolve ./...
else
# Convert directory names to p4 syntax
pfiles=
pdfiles=
for file in "$@"
do
if [ -d "$file" ]; then
pfiles="$pfiles $file/..."
pdfiles="$pfiles $file/...$DATE"
else
pfiles="$pfiles $file"
pdfiles="$pfiles $file$DATE"
fi
done
# Pick up files that are missing...
$P4 diff -sd $pfiles | $P4 -x- sync -f
# Pick up files that have changed...
$P4 sync $pdfiles
$P4 resolve $pfiles
fi
;;
ws)
shift
if [ $# = 0 ]; then
error "Usage: p4 ws <wsname> [serv:port [user [passwd]]]"
fi
if [ -f "$P4CONFIG" ]; then
error "$P4CONFIG exists. Remove it first"
fi
WSNAME="$1"
PORT="$2"
USER="$3"
PASSWD="$4"
if [ "$P4PORT" = "" ]; then
if [ "$PORT" = "" ]; then
echo "P4PORT=perforce:1666" > $P4CONFIG
else
echo "P4PORT=$PORT" > $P4CONFIG
fi
else
echo "P4PORT=$P4PORT" > $P4CONFIG
fi
echo "P4CLIENT=$WSNAME" >> $P4CONFIG
if [ "$USER" != "" ]; then
echo "P4USER=$USER" >> $P4CONFIG
fi
if [ "$PASSWD" != "" ]; then
echo "P4PASSWD=$PASSWD" >> $P4CONFIG
fi
$P4 client -o | sed 's/noallwrite/allwrite/' | $P4 client -i
$P4 client
if [ "$saveP4CONFIG" = "" ]; then
echo "Warning: Add 'export P4CONFIG=.p4config' to your .profile"
echo "Warning: for safety in case you stop using this wrapper"
fi
;;
help)
if [ $# = 1 ]; then
$P4 | sed '/p4 help env.*/a\
p4 help ext list extended commands provided by p4 wrapper'
elif [ $2 = "ext" ]; then
cat <<-EOF
Most extended subcommands take file and/or directory
arguments, recursing on the directories. If no arguments
are supplied to such a command, it will recurse on the
current directory (inclusive) by default. This is the
surprise-free behavior CVS users are used to.
Extended Perforce client commands:
p4 annotate file|dir ...
Make a listing of who changed what line.
p4 commit [file|dir] ...
Submit changed files to the depot.
p4 import
Add all files in current directory and below to
the depot.
p4 log file ...
List revision history of files.
p4 rename from to
Rename the file "from" as "to".
p4 stat [file|dir] ...
Report all files needing an update/sync
and/or a commit/submit.
p4 submit [-m logmsg] [p4filespec] ...
Augmented version of p4 submit that allows more than
one P4-style file specification. It also allows the
changelog description to be specified with -m.
p4 update [file|dir] ...
Get latest files from depot, doing a resolve
if necessary.
p4 ws <wsname> [server:port [user [passwd]]]
Create a new workspace in the current directory.
EOF
else
$P4 "$@"
fi
;;
*)
# Hand off all other commands to p4
exec $P4 $P4ONLY "$@"
;;
esac