#!/bin/sh
MD5SUM="534841181cec9b82373185b088619dc0 -"
VERSION="Sat Jul 14 15:16:10 CDT 2001 <rick@ipcroe.rkkda.com>"
#
# 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://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).
# TODO add:
# There is a bug with stock Perforce "p4 add". If you p4 add
# a file that doesn't yet exist, then create the file and
# try to do a submit/commit, the submit will fail.
# At this point, the only thing you can do is to issue
# a p4 revert and then a new p4 add command. This wrapper
# should be changed to protect against that.
#
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=
OPTIND=0
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=
OPTIND=0
while getopts "c:d:H:p:P:su:v:Vx:?h" 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)
echo "p4 wrapper: version $VERSION"
exec $P4 -V
;;
h|\?) $P4 -h
set -- help
OPTIND=1
break;
# echo " Run \"p4 help\" for further information"
# echo
# exit 0
;;
esac
done
shift `expr $OPTIND - 1`
#
# Main program
#
case "$1" in
annotate)
shift
#
# Get CVS style args
#
REV=
RECURSE=1
OPTIND=0
while getopts "lRfr:D:h?" opt
do
case $opt in
# TODO: handle the rest of the CVS options
l) # Local directory only, no recursion.
RECURSE=0
;;
R) # Process directories recursively.
RECURSE=1
;;
f) # Use head revision if tag/date not found.
error "option not implemented yet"
;;
r) # rev; Annotate using specified revision/tag
case "$OPTARG" in
[0-9]*) REV="#$OPTARG";;
"#"*) REV="$OPTARG";;
@*) REV="$OPTARG";;
[A-Za-z]*) REV="@$OPTARG";;
*) error "Unknown revision syntax";;
esac
;;
D) # date; Set date to annoate on from
# 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.
REV=`date -d "$OPTARG" "+@%Y/%m/%d:%H:%M:%S"`
if [ $? != 0 ]; then
exit 1
fi
error "p4pr.perl too stupid to handle -D right now"
;;
h|\?)
echo "Usage:"
echo -n " p4 annotate"
echo " [-lRf] [-r rev|-D date] [file|dir] ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
if [ $RECURSE = 1 ]; then
glob=...
else
glob="*"
fi
if [ $# = 0 ]; then
$P4 files ./$glob | sed 's/#.*//' |
while read pfile blather
do
p4pr.perl $pfile$REV
done
else
for file in "$@"
do
if [ -d "$file" ]; then
$P4 files $file/"$glob" | sed 's/#.*//' |
while read pfile blather
do
p4pr.perl $pfile$REV
done
else
p4pr.perl $file$REV
fi
done
fi
;;
commit)
shift
#
# Get CVS style args
#
LOGMSG=
LOGFILE=
RECURSE=1
OPTIND=0
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;;
r) # rev; Commit to this branch or trunk revision.
error "option -r not implemented yet"
;;
h|\?)
echo -n "Usage: p4 commit [-nRlf] [-m msg|-F logfile]"
echo " [-r rev] [file|dir]..."
echo -n " -R Process directories"
echo " recursively. (Default)"
echo " -l Local directory only (not recursive)."
echo -n " -f Force the file to be committed;"
echo " disables recursion."
echo " -F file Read the log message from file."
echo " -m msg Log message."
echo " -r rev Commit to this branch/trunk revision."
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)
shift
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage:"
echo " p4 import"
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
# TODO: handle repository vendortag releasetag args
find . -type f -print | $P4 -x - add
$P4 submit ./...
;;
log)
shift
# TODO: handle CVS options
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage:"
echo " p4 log [file|dir] ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
# 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
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage: p4 rename from to"
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
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
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage: p4 stat [file|dir] ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
# 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
#
REV=
FFLAG=
OPTIND=0
while getopts "APCdflRpk:r:D:j:I:W:h?" opt
do
case $opt in
# TODO: handle the rest of the CVS options
A) # Reset sticky
error "option not implemented yet"
;;
P) # Prune empty directories
error "option not implemented yet"
;;
C) # Overwrite locally modified files with clean copies
FFLAG=-f
;;
d) # Build directories like checkout does
error "option not implemented yet"
;;
f) # Force a head revision match if tag/date not found.
error "option not implemented yet"
;;
l) # Local directory only, no recursion.
error "option not implemented yet"
;;
R) # Process directories recursively.
error "option not implemented yet"
;;
p) # Send updates to standard output (avoids stickiness).
error "option not implemented yet"
;;
k) # kopt; Use RCS kopt -k option on checkout.
error "option not implemented yet"
;;
r) # rev; Update using specified revision/tag (is sticky).
case "$OPTARG" in
[0-9]*) REV="#$OPTARG";;
"#"*) REV="$OPTARG";;
@*) REV="$OPTARG";;
[A-Za-z]*) REV="@$OPTARG";;
*) error "Unknown revision syntax";;
esac
;;
D) # date; Set date to update from (in CVS, is sticky).
# 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.
REV=`date -d "$OPTARG" "+@%Y/%m/%d:%H:%M:%S"`
if [ $? != 0 ]; then
exit 1
fi
;;
j) # rev; Merge changes between current revision and rev.
error "option not implemented yet"
;;
I) # ign; More files to ignore (! to reset).
error "option not implemented yet"
;;
W) # spec; Wrappers specification line.
error "option not implemented yet"
;;
h|\?)
echo "Usage:"
echo " p4 update [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 ./...$REV | $P4 -x- sync -f
# Pick up files that have changed...
$P4 sync $FFLAG ./...$REV
$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/...$REV"
else
pfiles="$pfiles $file"
pdfiles="$pfiles $file$REV"
fi
done
# Pick up files that are missing...
$P4 diff -sd $pfiles | $P4 -x- sync -f
# Pick up files that have changed...
$P4 sync $FFLAG $pdfiles
$P4 resolve $pfiles
fi
;;
checkout)
shift
# TODO: handle CVS options
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage: p4 checkout dir ..."
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
for module in "$@"
do
$P4 sync $module/...
done
;;
ws)
#
# Perform the "dirty secret" business of setting up a workspace.
#
# This basically means creating a .p4config file in the current
# directory to hold the workspace name and server settings, and
# then running the p4 client program.
#
# The business of setting all those environment variables in
# your .profile is a crock. If you do it that way, you can only
# have one workspace per login. The dotfile is the only way to go.
#
shift
OPTIND=0
while getopts "h?" opt
do
case $opt in
h|\?)
echo "Usage: p4 ws <wsname> [serv:port [user [passwd]]]"
exit 0
;;
esac
done
shift `expr $OPTIND - 1`
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
else
echo "P4USER=$LOGNAME" >> $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 views.*/a\
p4 help wrapper list extended commands provided by p4 wrapper'
elif [ $2 = "wrapper" ]; 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 checkout dir ...
Checkout everything under the directory "dir".
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