Fix compiler warning due to missing function prototype.
[svn.git] / contrib / client-side / svnmerge / svnmerge.sh
blob1ac6487a8020485df246480791e77f30888592d4
1 #!/bin/sh
3 # Copyright (c) 2004-2005, Awarix, Inc.
4 # All rights reserved.
6 # Subject to the following obligations and disclaimer of warranty,
7 # use and redistribution of this software, in source or object code
8 # forms, with or without modifications are expressly permitted by
9 # Awarix; provided, however, that:
11 # (i) Any and all reproductions of the source or object code
12 # must include the copyright notice above and the following
13 # disclaimer of warranties; and
14 # (ii) No rights are granted, in any manner or form, to use
15 # Awarix trademarks, including the mark "AWARIX"
16 # on advertising, endorsements, or otherwise except as such
17 # appears in the above copyright notice or in the software.
19 # THIS SOFTWARE IS BEING PROVIDED BY AWARIX "AS IS", AND
20 # TO THE MAXIMUM EXTENT PERMITTED BY LAW, AWARIX MAKES NO
21 # REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
22 # THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
24 # OR NON-INFRINGEMENT. AWARIX DOES NOT WARRANT, GUARANTEE,
25 # OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
26 # OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
27 # RELIABILITY OR OTHERWISE. IN NO EVENT SHALL AWARIX BE
28 # LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
29 # OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
30 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
31 # DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
32 # USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
33 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
35 # THE USE OF THIS SOFTWARE, EVEN IF AWARIX IS ADVISED OF
36 # THE POSSIBILITY OF SUCH DAMAGE.
38 # Author: Archie Cobbs archie @ awarix dot com
40 # Acknowledgements:
41 # John Belmonte <john@neggie.net> - metadata and usability improvements
43 # $HeadURL$
44 # $LastChangedDate$
45 # $LastChangedBy$
46 # $LastChangedRevision$
48 # Definitions (would like ':' in property names but can't because of bug 1971)
49 NAME="svnmerge"
50 SVN_MERGE_SVN="svn"
51 SVN_MERGE_PROP="${NAME}-integrated"
52 SRCREV=`echo '$Rev$' | sed 's/^\$Rev: \([0-9]\{1,\}\).\{0,\}$/\1/g'`
53 SRCDATE=`echo '$Date$' | sed 's/^\$Date: .\{0,\}(\(.\{0,\}\)).\{0,\}$/\1/g'`
55 # We expect non-localized output
56 LC_MESSAGES="C"
57 export LC_MESSAGES
59 # Subroutine to output usage message
60 usage()
62 echo 'Usage:'
63 echo " ${NAME} init [-s] [-v] [-n] [-r revs] [-f file] [src]"
64 echo ' Initialize merge tracking from "src" on the current working'
65 echo ' directory. "revs" specifies the already-merged in revisions;'
66 echo ' it defaults to "1-HEAD", where HEAD is the latest revision of'
67 echo ' "src", if "src" is specified; if "src" is omitted, then "src"'
68 echo ' (and optionally "revs") are computed from the "svn cp" history'
69 echo ' of the current working directory.'
70 echo ''
71 echo " ${NAME} avail [-s] [-v] [-l] [-d] [-r revs] [-S src] [dest]"
72 echo ' Show unmerged revisions available for "dest" as a revision'
73 echo ' list. If revision list "revs" is given, the revisions shown'
74 echo ' will be limited to those also specified in "revs". If "dest"'
75 echo ' is tracking only one source, "src" may be omitted.'
76 echo ' Options specific to this command:'
77 echo ' -l Show corresponding log history instead of revision list'
78 echo ' -d Show corresponding diffs instead of revision list'
79 echo ''
80 echo " ${NAME} merge [-s] [-v] [-n] [-r revs] [-f file] [-S src] [dest]"
81 echo ' Merge in revisions specified by "revs" into "dest" from the'
82 echo ' given "src" location. "revs" is the revision list specifying'
83 echo ' revisions to merge in. Already merged-in revisions will not be'
84 echo ' merged in again. Default for "revs" is "1-HEAD" where HEAD is'
85 echo ' the latest revision of the "src" repository (i.e., merge all'
86 echo ' available). If "dest" is tracking only one source, "src" may'
87 echo ' be omitted.'
88 echo ''
89 echo ' Options common to multiple commands:'
90 echo ' -v Verbose mode: output more information about progress'
91 echo ' -s Show subversion commands that make changes'
92 echo " -n Don't actually change anything, just pretend; implies -s"
93 echo ' -f Write a suitable commit log message into "file"'
94 echo ' -r Specify a revision list, consisting of revision numbers'
95 echo ' and ranges separated by commas, e.g., "534,537-539,540"'
96 echo ''
97 echo ' "src" may be a repository path or a working directory.'
98 echo ' "dest" is always a working directory and defaults to ".".'
99 echo " This is svnmerge revision ${SRCREV} dated ${SRCDATE}."
100 echo ''
101 exit 1
104 # Subroutine to output an error and bail
105 error()
107 echo ${NAME}: ${1+"$@"}
108 exit 1
111 # Subroutine to output progress message, unless in quiet mode
112 report()
114 if [ "${SVN_MERGE_VERBOSE}" != "" ]; then
115 echo ${NAME}: ${1+"$@"}
119 # Subroutine to output an error, usage, and bail
120 usage_error()
122 echo ${NAME}: ${1+"$@"}
123 usage
126 # Subroutine to do (or pretend to do) an SVN command
127 svn_command()
129 if [ "${SVN_MERGE_SHOW_CMDS}" != "" ]; then
130 echo "${SVN_MERGE_SVN}" ${1+"$@"}
132 if [ "${SVN_MERGE_PRETEND}" = "" ]; then
133 "${SVN_MERGE_SVN}" ${1+"$@"}
134 if [ $? -ne 0 ]; then
135 error command failed: ${1+"$@"}
140 # Check the current status of ${BRANCH_DIR} for up-to-dateness and local mods
141 check_branch_dir()
143 report "checking status of \"${BRANCH_DIR}\""
144 "${SVN_MERGE_SVN}" status -u "${BRANCH_DIR}" | grep -q '^.......\*' && \
145 error "\"${BRANCH_DIR}\" is not up to date; please \"svn update\" first"
146 [ `"${SVN_MERGE_SVN}" stat -q "${BRANCH_DIR}" \
147 | sed '/^$/,$d' | wc -l` = "0" ] || \
148 error "\"${BRANCH_DIR}\" has local modifications; it must be clean"
151 # Subroutine to clean up an URL or path
152 normalize_url()
154 TEMP="$1"
155 while true; do
156 TEMP2=`echo "${TEMP}" | sed -e 's/$/\//g' \
157 -e 's/\/[^/]\{1,\}\/\.\.\//\//g' -e 's/\/\.\//\//g' \
158 -e 's/\([^:/]\)\/\//\1\//g' -e 's/\/$//g'`
159 [ "${TEMP2}" != "${TEMP}" ] || break
160 TEMP="${TEMP2}"
161 done
162 RETURN_VALUE="${TEMP}"
165 # Subroutine to parse out the start and end from a range like "123-456"
166 get_start_end()
168 START=`echo "$1" | sed 's/^\([0-9]\{1,\}\)-\([0-9]\{1,\}\)$/\1/g'`
169 END=`echo "$1" | sed 's/^\([0-9]\{1,\}\)-\([0-9]\{1,\}\)$/\2/g'`
172 # Subroutine to get all integrated revisions for a given head
173 get_all_integrated_revs()
175 RETURN_VALUE=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$1"`
178 # Subroutine to retrieve a target's integrated revisions for a given head
179 get_integrated_revs()
181 TEMP=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$2" | grep "^${1}:"`
182 [ -z "${TEMP}" ] && \
183 error no integration info available for repository path \"$1\"
184 RETURN_VALUE="${TEMP#${1}:}"
187 # Subroutine to set a target's integrated revisions for a given head
188 set_integrated_revs()
190 TEMP=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$3" | grep -v "^${1}:"`
191 TEMP=`echo "${TEMP} ${1}:${2}" | xargs -n 1 | sort`
192 svn_command propset -q "${SVN_MERGE_PROP}" "${TEMP}" "$3"
195 # Subroutine to retrieve the default head of the given target
196 get_default_head()
198 # To make bi-directional merges easier, find the target's
199 # repository local path so it can be removed from the list of
200 # possible integration sources.
201 target_to_url "$1"
202 url_to_rlpath "${RETURN_VALUE}"
204 RETURN_VALUE=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$1" | cut -d: -f 1 | grep -v "^${RETURN_VALUE}$"`
205 [ -z "${RETURN_VALUE}" ] && error no integration info available
206 [ `echo "${RETURN_VALUE}" | wc -l` -gt 1 ] && \
207 error explicit \"src\" argument required
210 # Subroutine to parse, validate, and normalize a revision list.
211 # This input has commas separating ranges and any additional whitespace.
212 # The result has the form "123-123,125-127,128-130,132-132", i.e.,
213 # sorted with all adjacent, empty, and redundant ranges merged.
214 normalize_list()
216 # Special case empty list
217 TEMP=`echo "$1" | tr -d '[:space:]'`
218 if [ "${TEMP}" = "" ]; then
219 RETURN_VALUE=""
220 return 0
223 # See if list is well formed
224 NUMPAT='[0-9]\{1,\}'
225 RNGPAT="${NUMPAT}\(-${NUMPAT}\)\{0,1\}"
226 LISTPAT="\(,\{0,1\}${RNGPAT},\{0,1\}\)\{0,\}"
227 expr "${TEMP}" : "${LISTPAT}\$" >/dev/null || \
228 usage_error invalid revision list \"$1\"
230 # Now sort the list and compress out redundancies
231 RESULT=''
232 LAST_START=''
233 LAST_END=''
234 for RNG in `echo "${TEMP}" | tr , '\n' | sort -n -t - -k 1,2 \
235 | sed 's/^\([0-9]\{1,\}\)$/\1-\1/g'`; do
237 # Get range start and end
238 get_start_end "${RNG}"
240 # First revision is #1
241 if [ "${START}" -le 0 ]; then
242 START="1"
245 # Completely ignore any empty ranges
246 if [ "${START}" -gt "${END}" ]; then
247 continue
250 # First iteration?
251 if [ "${LAST_START}" = "" ]; then
252 LAST_START=${START}
253 LAST_END=${END}
254 continue
257 # Does this range overlap with the previous?
258 if [ "${START}" -le `expr "${LAST_END}" + 1` ]; then
259 if [ "${END}" -gt "${LAST_END}" ]; then
260 LAST_END=${END}
262 continue
265 # Break off discontigous range
266 [ "${RESULT}" = "" ] || RESULT="${RESULT},"
267 RESULT="${RESULT}${LAST_START}-${LAST_END}"
268 LAST_START=${START}
269 LAST_END=${END}
270 done
272 # Tack on final range
273 if [ "${LAST_START}" != "" ]; then
274 [ "${RESULT}" = "" ] || RESULT="${RESULT},"
275 RESULT="${RESULT}${LAST_START}-${LAST_END}"
278 # Done
279 RETURN_VALUE="${RESULT}"
282 # Subroutine to compute the set $1 minus $2, where $1 and $2 are
283 # *normalized* revision lists. This is also pretty gross.
284 list_subtract()
286 TEMP=''
287 for ARNG in `echo $1 | tr ',' ' '`; do
289 # Parse range
290 get_start_end "${ARNG}"
291 ASTART="${START}"
292 AEND="${END}"
294 # Iterate over subtracted ranges
295 for BRNG in `echo $2 | tr ',' ' '`; do
297 # Parse range
298 get_start_end "${BRNG}"
299 BSTART="${START}"
300 BEND="${END}"
302 # Is this BRNG entirely before or past ARNG?
303 if [ ${ASTART} -gt ${BEND} ]; then
304 continue
305 elif [ ${BSTART} -gt ${AEND} ]; then
306 break
309 # Keep the initial part of ARNG missed by BRNG (if anything)
310 [ "${TEMP}" = "" ] || TEMP="${TEMP},"
311 TEMP="${TEMP}${ASTART}-`expr ${BSTART} - 1`"
313 # Keep going with whatever remains of ARNG (if anything)
314 if [ ${AEND} -gt ${BEND} ]; then
315 ASTART=`expr ${BEND} + 1`
316 else
317 AEND=`expr ${ASTART} - 1`
318 break
320 done
322 # Keep what's left of ARNG (if anything)
323 [ "${TEMP}" = "" ] || TEMP="${TEMP},"
324 TEMP="${TEMP}${ASTART}-${AEND}"
325 done
327 # Normalize the result
328 normalize_list "${TEMP}"
331 # Subroutine to return a normalized list to a more pleasant form
332 beautify_list()
334 TEMP=''
335 for RNG in `echo "$1" | tr ',' ' '`; do
336 get_start_end "${RNG}"
337 [ "${TEMP}" = "" ] || TEMP="${TEMP},"
338 TEMP="${TEMP}${START}"
339 if [ "${END}" != "${START}" ]; then
340 TEMP="${TEMP}-${END}"
342 done
343 RETURN_VALUE="${TEMP}"
346 # Subroutine to convert working copy path or repo URL $1 to a repo URL
347 target_to_url()
349 if [ -d "$1" -a -d "$1/.svn" ]; then
350 RETURN_VALUE=`"${SVN_MERGE_SVN}" info "$1" \
351 | grep ^URL: | sed -e 's/^URL: \(.*\)$/\1/g'`
352 else
353 RETURN_VALUE="$1"
357 # Subroutine to compute the root repo URL given wc path or repo URL $1.
358 # Constrained to svn command line tools, we are stuck with this ugly trial-
359 # and-error implementation. It could be made faster with a binary search.
360 get_repo_root()
362 target_to_url "$1"
363 while TEMP=`dirname ${RETURN_VALUE}` &&
364 "${SVN_MERGE_SVN}" proplist "${TEMP}" >/dev/null 2>&1; do
365 RETURN_VALUE="${TEMP}"
366 done
369 # Subroutine to convert repo URL $1 to a repo-local path
370 url_to_rlpath()
372 get_repo_root $1
373 RETURN_VALUE="${1#${RETURN_VALUE}}"
376 # Subroutine to get copyfrom info for a given target
377 # NOTE: repo root has no copyfrom info. In this case null is returned.
378 get_copyfrom()
380 target_to_url "$1"
381 url_to_rlpath "${RETURN_VALUE}"
382 TEMP=`"${SVN_MERGE_SVN}" log -v --xml --stop-on-copy "$1" | tr '\n' ' '`
383 TEMP2=`echo "${TEMP}" | sed -e 's#^.*\(<path .*action="A".*>'"${RETURN_VALUE}"'</path>\).*$#\1#'`
384 if [ "${TEMP}" = "${TEMP2}" ]; then
385 RETURN_VALUE=""
386 else
387 RETURN_VALUE=`echo "${TEMP2}" | sed -e 's/^.* copyfrom-path="\([^"]*\)".*$/\1/'`
388 RETURN_VALUE="${RETURN_VALUE}:"`echo "${TEMP2}" | sed -e 's/^.* copyfrom-rev="\([^"]*\)".*$/\1/'`
392 # The "init" action
393 init()
395 # Check branch directory
396 check_branch_dir
398 # Get initial revision list if not explicitly specified
399 if [ "${REVS}" = "" ]; then
400 REVS="1-${HEAD_REVISION}"
403 # Normalize and beautify ${REVS}
404 normalize_list "${REVS}"
405 beautify_list "${RETURN_VALUE}"
406 REVS="${RETURN_VALUE}"
408 report marking "${BRANCH_DIR}" as already containing \
409 revisions "${REVS}" of "${HEAD_URL}".
411 # Set properties
412 set_integrated_revs "${HEAD_PATH}" "${REVS}" "${BRANCH_DIR}"
414 # Write out commit message if desired
415 if [ "${SVN_MERGE_COMMIT_FILE}" != "" ]; then
416 echo Initialized merge tracking via "${NAME}" with revisions \
417 "${REVS}" from > "${SVN_MERGE_COMMIT_FILE}"
418 echo "${HEAD_URL}" >> "${SVN_MERGE_COMMIT_FILE}"
419 report wrote commit message to "${SVN_MERGE_COMMIT_FILE}"
423 # "avail" action
424 avail()
426 # Default --avail display type is "revisions"
427 [ "${AVAIL_DISPLAY}" != "" ] || AVAIL_DISPLAY="revisions"
429 # Calculate outstanding revisions
430 list_subtract "1-${HEAD_REVISION}" "${MERGED_REVS}"
431 AVAIL_REVS="${RETURN_VALUE}"
433 # Limit to revisions specified by -r (if any)
434 if [ "${REVS}" != "" ]; then
435 normalize_list "${REVS}"
436 list_subtract "1-${HEAD_REVISION}" "${RETURN_VALUE}"
437 list_subtract "${AVAIL_REVS}" "${RETURN_VALUE}"
438 AVAIL_REVS="${RETURN_VALUE}"
441 # Show them, either numerically, in log format, or as diffs
442 case "${AVAIL_DISPLAY}" in
443 revisions)
444 beautify_list "${AVAIL_REVS}"
445 echo "${RETURN_VALUE}"
447 logs)
448 for RNG in `echo "${AVAIL_REVS}" | tr ',' ' ' | tr '-' ':'`; do
449 svn_command log --incremental -v -r "${RNG}" "${HEAD_URL}"
450 done
452 diffs)
453 for RNG in `echo "${AVAIL_REVS}" | tr ',' ' '`; do
454 get_start_end "${RNG}"
455 echo ''
456 echo "${NAME}: changes in revisions ${RNG} follow"
457 echo ''
458 # Note: the starting revision number to 'svn diff' is
459 # NOT inclusive so we have to subtract one from ${START}.
460 svn_command diff -r `expr ${START} - 1`:${END} "${HEAD_URL}"
461 done
464 error internal error
465 esac
468 # "merge" action
469 merge()
471 # Check branch directory
472 check_branch_dir
474 # Default to merging all outstanding revisions
475 if [ "${REVS}" = "" ]; then
476 REVS="1-${HEAD_REVISION}"
479 # Parse desired merge revisions
480 normalize_list "${REVS}"
481 REVS="${RETURN_VALUE}"
483 # Calculate subset of REVS which is not in MERGED_REVS
484 list_subtract "${REVS}" "${MERGED_REVS}"
485 REVS="${RETURN_VALUE}"
486 beautify_list "${REVS}"
487 BREVS="${RETURN_VALUE}"
489 # Save "svnmerge-integrated" property value from before the merge
490 get_all_integrated_revs
491 OLDREVS="${RETURN_VALUE}"
493 # Show what we're doing
494 beautify_list "${MERGED_REVS}"
495 report "\"${BRANCH_DIR}\" already contains revisions ${RETURN_VALUE}"
496 report merging in 'revision(s)' "${BREVS}" from "${HEAD_URL}"
498 # Do the merge(s). Note: the starting revision number to 'svn merge'
499 # is NOT inclusive so we have to subtract one from ${START}.
500 for RNG in `echo "${REVS}" | tr ',' ' '`; do
501 get_start_end "${RNG}"
502 svn_command merge -r `expr ${START} - 1`:${END} \
503 "${HEAD_URL}" "${BRANCH_DIR}"
504 done
506 # Revert any merged-in changes to the "svnmerge-integrated" property.
507 # We only want updates to this property to happen via explicit action.
508 svn_command propset -q "${SVN_MERGE_PROP}" "${OLDREVS}" "${BRANCH_DIR}"
510 # Write out commit message if desired
511 if [ "${SVN_MERGE_COMMIT_FILE}" != "" ]; then
512 echo "Merged revisions ${BREVS} via ${NAME} from" \
513 > "${SVN_MERGE_COMMIT_FILE}"
514 echo "${HEAD_PATH}" >> "${SVN_MERGE_COMMIT_FILE}"
515 report wrote commit message to "${SVN_MERGE_COMMIT_FILE}"
518 # Update list of merged revisions
519 normalize_list "${MERGED_REVS},${REVS}"
520 beautify_list "${RETURN_VALUE}"
521 set_integrated_revs "${HEAD_PATH}" "${RETURN_VALUE}" "${BRANCH_DIR}"
524 # Get the desired action, compute getopt flags, and apply defaults
525 [ $# -ge 1 ] || usage_error no action specified
526 case "$1" in
527 init)
528 FLAGS="svnr:f:"
529 BRANCH_DIR="."
531 avail)
532 FLAGS="svldr:S:"
533 BRANCH_DIR="."
534 AVAIL_DISPLAY="revisions"
536 merge)
537 FLAGS="svnr:f:S:"
538 BRANCH_DIR="."
540 help)
541 usage
544 usage_error "no action specified"
547 usage_error "unknown action \"$1\""
549 esac
550 ACTION="$1"
551 shift
553 # Unset variables we don't want to inherit from the environment
554 unset REVS
556 # Parse remaining command line
557 ARGS=`getopt "${FLAGS}" $*`
558 [ $? = 0 ] || usage
559 set -- ${ARGS}
561 for i in "$@"; do
562 case "$i" in
564 SVN_MERGE_COMMIT_FILE="$2"
565 shift; shift
568 REVS="$2"
569 shift; shift
572 AVAIL_DISPLAY="diffs"
573 shift
576 AVAIL_DISPLAY="logs"
577 shift
580 SVN_MERGE_VERBOSE="true"
581 shift
584 SVN_MERGE_PRETEND="true"
585 SVN_MERGE_SHOW_CMDS="true"
586 shift
589 SVN_MERGE_SHOW_CMDS="true"
590 shift
593 HEAD="$2"
594 shift
595 shift
598 shift
599 break
601 esac
602 done
604 # Now parse the non-flag command line parameters
605 case "${ACTION}" in
606 init)
607 case $# in
609 HEAD="$1"
614 usage_error wrong number of parameters
615 esac
617 avail)
618 case $# in
620 BRANCH_DIR="$1"
625 usage_error wrong number of parameters
626 esac
628 merge)
629 case $# in
631 BRANCH_DIR="$1"
636 usage_error wrong number of parameters
637 esac
639 esac
641 # Validate branch-dir
642 [ -d "${BRANCH_DIR}" -a -d "${BRANCH_DIR}/.svn" ] || \
643 error \"${BRANCH_DIR}\" is not a subversion working directory
645 # Normalize ${BRANCH_DIR}
646 normalize_url "${BRANCH_DIR}"
647 BRANCH_DIR="${RETURN_VALUE}"
649 # See if we need to upgrade revision metadata from previous schemes:
650 # * revision and head data were stored individually in
651 # svnmerge-[LABEL-]{head,revs} properties
652 # * head used URL's instead of repo-local paths
653 if TEMP=`"${SVN_MERGE_SVN}" proplist "${BRANCH_DIR}" | grep -Ew "svnmerge-(.+-)?head"`; then
654 echo "${NAME}: old property names detected; an upgrade is required."
655 echo ''
656 echo 'Please execute and commit these changes to upgrade:'
657 echo ''
658 INTEGRATED=""
659 for OLD_PROP in ${TEMP}; do
660 OLD_PROP=${OLD_PROP%-head}
661 echo " svn propdel ${OLD_PROP}-head ${BRANCH_DIR}"
662 echo " svn propdel ${OLD_PROP}-revs ${BRANCH_DIR}"
663 HEAD=`"${SVN_MERGE_SVN}" propget "${OLD_PROP}-head" "${BRANCH_DIR}"`
664 REVS=`"${SVN_MERGE_SVN}" propget "${OLD_PROP}-revs" "${BRANCH_DIR}"`
665 if echo "${HEAD}" | grep -qE '^[[:alpha:]][-+.[:alnum:]]*://'; then
666 url_to_rlpath "${HEAD}"
667 HEAD="${RETURN_VALUE}"
669 INTEGRATED="${INTEGRATED} ${HEAD}:${REVS}"
670 done
671 INTEGRATED=`echo "${INTEGRATED}" | xargs -n 1 | sort`
672 echo " svn propset ${SVN_MERGE_PROP} \"${INTEGRATED}\" ${BRANCH_DIR}"
673 echo ''
674 exit 1
677 # Previous svnmerge versions allowed trailing /'s in the repository
678 # local path. Newer versions of svnmerge will trim trailing /'s
679 # appearing in the command line, so if there are any properties with
680 # trailing /'s, they will not be properly matched later on, so require
681 # the user to change them now.
682 if TEMP=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "${BRANCH_DIR}" | grep -E '/+:[-,0-9]+$'`; then
683 echo "${NAME}: old property values detected; an upgrade is required."
684 echo ''
685 echo 'Please execute and commit these changes to upgrade:'
686 echo ''
687 echo "svn propget ${SVN_MERGE_PROP} ${BRANCH_DIR} > svnmerge1.txt"
688 echo "sed -e 's/\/*\(:[-,0-9]*\)/\1/g' < svnmerge1.txt > svnmerge2.txt"
689 echo "svn propset ${SVN_MERGE_PROP} ${BRANCH_DIR} -F svnmerge2.txt"
690 echo "rm svnmerge1.txt svnmerge2.txt"
691 exit 1
694 # Calculate ${HEAD_URL} and ${HEAD_PATH}
695 if [ -z "${HEAD}" ]; then
696 if [ "${ACTION}" = "init" ]; then
697 get_copyfrom "${BRANCH_DIR}"
698 [ -z "${RETURN_VALUE}" ] && \
699 error no copyfrom info available. Explicit \"src\" argument required.
700 HEAD_PATH=`echo "${RETURN_VALUE}" | cut -d: -f 1`
701 [ -z "${REVS}" ] && REVS="1-"`echo "${RETURN_VALUE}" | cut -d: -f 2`
702 else
703 get_default_head "${BRANCH_DIR}"
704 HEAD_PATH="${RETURN_VALUE}"
706 get_repo_root "${BRANCH_DIR}"
707 HEAD_URL="${RETURN_VALUE}/${HEAD_PATH}"
708 else
709 # The source was given as a command line argument and is stored in
710 # HEAD. Ensure that the specified source does not end in a /,
711 # otherwise it's easy to have the same source path listed more
712 # than once in the integrated version properties, with and without
713 # trailing /'s.
714 HEAD=`echo ${HEAD} | sed -e 's/\/*$//'`
716 target_to_url "${HEAD}"
717 HEAD_URL="${RETURN_VALUE}"
718 url_to_rlpath "${HEAD_URL}"
719 HEAD_PATH="${RETURN_VALUE}"
722 # Sanity check ${HEAD_URL}
723 echo "${HEAD_URL}" | grep -qE '^[[:alpha:]][-+.[:alnum:]]*://' ||
724 error "\"${HEAD_URL}\" is not a valid URL or working directory"
726 # Normalize head URL
727 normalize_url "${HEAD_URL}"
728 HEAD_URL="${RETURN_VALUE}"
730 # Get previously merged revisions (except when --init)
731 if [ "${ACTION}" != "init" ]; then
732 get_integrated_revs "${HEAD_PATH}" "${BRANCH_DIR}"
733 normalize_list "${RETURN_VALUE}"
734 MERGED_REVS="${RETURN_VALUE}"
737 # Get latest revision of head
738 report checking latest revision of "${HEAD_URL}"
739 HEAD_REVISION=`"${SVN_MERGE_SVN}" proplist --revprop -r HEAD "${HEAD_URL}" \
740 | sed -e 's/.* \([0-9]\{1,\}\).*$/\1/g' -e 1q`
741 if ! expr "${HEAD_REVISION}" : '[0-9]\{1,\}$' >/dev/null; then
742 error "can't get head revision of \"${HEAD_URL}\" (got \"${REVISION}\")"
744 report latest revision of "${HEAD_URL}" is "${HEAD_REVISION}"
746 # Perform action
747 ${ACTION}