[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] svnmerge: use head paths instead of labels

From: John Belmonte <john_at_neggie.net>
Date: 2005-01-24 02:13:21 CET

Attached is a patch to svnmerge which has it use explicit paths, both on
the command line and in the metadata, to identify the "head" (source)
directories. Previously, these were indirected with user-defined
labels, which precluded various kinds of automation, such as integration
without the need for prior initialization. Graceful upgrades are
provided for previous metadata schemes.

The "head" argument of init is now optional. By default, init will take
the head path and revision range from the branch's copyfrom info. This
covers the common case where you just want to track changes to the
parent of a branch created with "svn cp".

I'm not really happy with "head" being an option for avail and merge,
but I assume existing svnmerge users want this. I think both "head" and
"branch-dir" should be mandatory on the command line, and that these
terms should be replaced with "src" and "dest". The motivation is to
more closely mimmic the familiar semantics of a copy operation (similar
to "p4 integrate").

Given this patch, some ideas for the future:

     * do not require init command before using avail/merge. If no
       svnmerge info exists, it defaults to the branch's copyfrom info.
       When there is no revision info for the given "head", all
       available revisions will be merged.

     * for avail and merge commands, use not only the revision info on
       the "branch" dir, but also what's on the "head". For example, if
       the "head" directory already has revisions 500-600 of the "branch"
       dir integrated, do not consider those revisions. This will make
       bidirectional integrations between branches less cumbersome.

     * implement "alist" command as I described in the "svn ancestorlist"
       post to subversion-dev

-John

Index: svnmerge
===================================================================
--- svnmerge (revision 12823)
+++ svnmerge (working copy)
@@ -41,7 +41,7 @@
 # Definitions (would like ':' in property names but can't because of bug 1971)
 NAME="svnmerge"
 SVN_MERGE_SVN="svn"
-DFLABEL="default"
+SVN_MERGE_PROP="${NAME}-integrated"
 SRCREV=`echo '$Rev$' | sed 's/^\$Rev: \([0-9]\{1,\}\).\{0,\}$/\1/g'`
 SRCDATE=`echo '$Date$' | sed 's/^\$Date: .\{0,\}(\(.\{0,\}\)).\{0,\}$/\1/g'`
 
@@ -49,28 +49,32 @@
 usage()
 {
     echo 'Usage:'
- echo " ${NAME} init [-s] [-v] [-n] [-r revs] [-L label] [-f file] head"
+ echo " ${NAME} init [-s] [-v] [-n] [-r revs] [-f file] [head]"
     echo ' Initialize merge tracking from "head" on the current working'
     echo ' directory. "head" is either an URL or a working directory;'
     echo ' in the latter case, the corresponding URL is used. "revs"'
     echo ' specifies the already-merged in revisions; it defaults to'
     echo ' "1-HEAD", where HEAD is the latest revision of "head".'
+ echo ' If "head" is omitted, it, and optionally "revs", defaults to'
+ echo ' the "svn cp" history of the current working directory.'
     echo ''
- echo " ${NAME} avail [-s] [-v] [-l] [-d] [-r revs] [-L label] [branch-dir]"
+ echo " ${NAME} avail [-s] [-v] [-l] [-d] [-r revs] [-H head] [branch-dir]"
     echo ' Show unmerged revisions available for "branch-dir" as a'
     echo ' revision list. If revision list "revs" is given, the revisions'
     echo ' shown will be limited to those also specified in "revs".'
+ echo ' If "branch-dir" is tracking only one head, "head" may be omitted.'
     echo ' Options specific to this command:'
     echo ' -l Show corresponding log history instead of revision list'
     echo ' -d Show corresponding diffs instead of revision list'
     echo ''
- echo " ${NAME} merge [-s] [-v] [-n] [-r revs] [-f file] [-L label] [branch-dir]"
+ echo " ${NAME} merge [-s] [-v] [-n] [-r revs] [-f file] [-H head] [branch-dir]"
     echo ' Merge in revisions specified by "revs" into "branch-dir"'
- echo ' from the "head" location previously specified by init.'
+ echo ' from the given "head" location.'
     echo ' "revs" is the revision list specifying revisions to merge in.'
     echo ' Already merged-in revisions will not be merged in again.'
     echo ' Default for "revs" is "1-HEAD" where HEAD is the latest'
     echo ' revision of the "head" repository (i.e., merge all available).'
+ echo ' If "branch-dir" is tracking only one head, "head" may be omitted.'
     echo ''
     echo ' Options common to multiple commands:'
     echo ' -v Verbose mode: output more information about progress'
@@ -79,14 +83,8 @@
     echo ' -f Write a suitable commit log message into "file"'
     echo ' -r Specify a revision list, consisting of revision numbers'
     echo ' and ranges separated by commas, e.g., "534,537-539,540"'
- echo ' -L Specify a label for merging; allows tracking of merges'
- echo ' from multiple head locations by using unique labels.'
- echo ' Also required when "head" is itself a target for merge'
- echo " tracking, to avoid ${NAME} property name conflicts."
     echo ''
     echo ' "branch-dir" is always a working directory and defaults to ".".'
- echo " Default label is \"${DFLABEL}\" for \"init\", otherwise whichever"
- echo ' label is already in use on "branch-dir" if there is only one.'
     echo " This is svnmerge revision ${SRCREV} dated ${SRCDATE}."
     echo ''
     exit 1
@@ -138,16 +136,6 @@
         error "\"${BRANCH_DIR}\" has local modifications; it must be clean"
 }
 
-# Subroutine to determine the first revision of a file/directory,
-# not including copies. Return the result in ${BRANCH_POINT}
-branch_point()
-{
- RETURN_VALUE=`"${SVN_MERGE_SVN}" log --xml --stop-on-copy "$1" \
- | tr '\n' ' ' \
- | sed \
- 's/^.*\(<logentry[^>]\{1,\}revision="\([0-9]\{1,\}\)"\)\{1,\}.*$/\2/g'`
-}
-
 # Subroutine to clean up an URL or path
 normalize_url()
 {
@@ -169,15 +157,30 @@
     END=`echo "$1" | sed 's/^\([0-9]\{1,\}\)-\([0-9]\{1,\}\)$/\2/g'`
 }
 
-# Subroutine to retrieve an SVN property
-get_prop()
+# Subroutine to retrieve a target's integrated revisions for a given head
+get_integrated_revs()
+{
+ TEMP=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$2" | grep "^${1}:"`
+ [ -z "${TEMP}" ] && \
+ error no integration info available for repository path \"$1\"
+ RETURN_VALUE="${TEMP#${1}:}"
+}
+
+# Subroutine to set a target's integrated revisions for a given head
+set_integrated_revs()
 {
- # Verify property exists
- "${SVN_MERGE_SVN}" proplist "$2" | grep -q "$1" || \
- error property \"$1\" does not exist on \"$2\"
+ TEMP=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$3" | grep -v "^${1}:"`
+ TEMP=`echo "${TEMP} ${1}:${2}" | xargs -n 1 | sort`
+ svn_command propset -q "${SVN_MERGE_PROP}" "${TEMP}" "$3"
+}
 
- # Retrieve property
- RETURN_VALUE=`"${SVN_MERGE_SVN}" propget "$1" "$2"`
+# Subroutine to retrieve the default head of the given target
+get_default_head()
+{
+ RETURN_VALUE=`"${SVN_MERGE_SVN}" propget "${SVN_MERGE_PROP}" "$1" | cut -d: -f 1`
+ [ -z "${RETURN_VALUE}" ] && error no integration info available
+ [ `echo "${RETURN_VALUE}" | wc -l` -gt 1 ] && \
+ error explicit \"head\" argument required
 }
 
 # Subroutine to parse, validate, and normalize a revision list.
@@ -346,6 +349,22 @@
     RETURN_VALUE="${1#${RETURN_VALUE}}"
 }
 
+# Subroutine to get copyfrom info for a given target
+# NOTE: repo root has no copyfrom info. In this case null is returned.
+get_copyfrom()
+{
+ target_to_url "$1"
+ url_to_rlpath "${RETURN_VALUE}"
+ TEMP=`"${SVN_MERGE_SVN}" log -v --xml --stop-on-copy "$1" | tr '\n' ' '`
+ TEMP2=`echo "${TEMP}" | sed -e 's#^.*\(<path .*action="A".*>'"${RETURN_VALUE}"'</path>\).*$#\1#'`
+ if [ "${TEMP}" == "${TEMP2}" ]; then
+ RETURN_VALUE=""
+ else
+ RETURN_VALUE=`echo "${TEMP2}" | sed -e 's/^.* copyfrom-path="\([^"]*\)".*$/\1/'`
+ RETURN_VALUE="${RETURN_VALUE}:"`echo "${TEMP2}" | sed -e 's/^.* copyfrom-rev="\([^"]*\)".*$/\1/'`
+ fi
+}
+
 # The "init" action
 init()
 {
@@ -366,10 +385,7 @@
         revisions "${REVS}" of "${HEAD_URL}".
 
     # Set properties
- svn_command propset -q "${SVN_MERGE_HEAD_PROP}" \
- "${HEAD_PATH}" "${BRANCH_DIR}"
- svn_command propset -q "${SVN_MERGE_REVS_PROP}" \
- "${REVS}" "${BRANCH_DIR}"
+ set_integrated_revs "${HEAD_PATH}" "${REVS}" "${BRANCH_DIR}"
 
     # Write out commit message if desired
     if [ "${SVN_MERGE_COMMIT_FILE}" != "" ]; then
@@ -470,24 +486,23 @@
     # Update list of merged revisions
     normalize_list "${MERGED_REVS},${REVS}"
     beautify_list "${RETURN_VALUE}"
- svn_command propset -q "${SVN_MERGE_REVS_PROP}" \
- "${RETURN_VALUE}" "${BRANCH_DIR}"
+ set_integrated_revs "${HEAD_PATH}" "${RETURN_VALUE}" "${BRANCH_DIR}"
 }
 
 # Get the desired action, compute getopt flags, and apply defaults
 [ $# -ge 1 ] || usage_error no action specified
 case "$1" in
     init)
- FLAGS="svnr:f:L:"
+ FLAGS="svnr:f:"
         BRANCH_DIR="."
         ;;
     avail)
- FLAGS="svldr:L:"
+ FLAGS="svldr:H:"
         BRANCH_DIR="."
         AVAIL_DISPLAY="revisions"
         ;;
     merge)
- FLAGS="svnr:f:L:"
+ FLAGS="svnr:f:H:"
         BRANCH_DIR="."
         ;;
     help)
@@ -542,8 +557,8 @@
             SVN_MERGE_SHOW_CMDS="true"
             shift
             ;;
- -L)
- SVN_MERGE_LABEL="$2"
+ -H)
+ HEAD="$2"
             shift
             shift
             ;;
@@ -561,6 +576,8 @@
             1)
                 HEAD="$1"
                 ;;
+ 0)
+ ;;
             *)
                 usage_error wrong number of parameters
         esac
@@ -597,79 +614,55 @@
 normalize_url "${BRANCH_DIR}"
 BRANCH_DIR="${RETURN_VALUE}"
 
-# See if we need to "upgrade" existing property names
-if "${SVN_MERGE_SVN}" proplist "${BRANCH_DIR}" | grep -qw svnmerge-head; then
+# See if we need to upgrade revision metadata from previous schemes:
+# * revision and head data were stored individually in
+# svnmerge-[LABEL-]{head,revs} properties
+# * head used URL's instead of repo-local paths
+if TEMP=`"${SVN_MERGE_SVN}" proplist "${BRANCH_DIR}" | grep -Ew "svnmerge-(.+-)?head"`; then
     echo "${NAME}: old property names detected; an upgrade is required."
     echo ''
     echo 'Please execute and commit these changes to upgrade:'
     echo ''
- echo " svn propset ${NAME}-${DFLABEL}-head \`svn propget svnmerge-head\` ${BRANCH_DIR}"
- echo " svn propset ${NAME}-${DFLABEL}-revs \`svn propget svnmerge-revs\` ${BRANCH_DIR}"
- echo " svn propdel svnmerge-head ${BRANCH_DIR}"
- echo " svn propdel svnmerge-revs ${BRANCH_DIR}"
+ INTEGRATED=""
+ for OLD_PROP in ${TEMP}; do
+ OLD_PROP=${OLD_PROP%-head}
+ echo " svn propdel ${OLD_PROP}-head ${BRANCH_DIR}"
+ echo " svn propdel ${OLD_PROP}-revs ${BRANCH_DIR}"
+ HEAD=`"${SVN_MERGE_SVN}" propget "${OLD_PROP}-head" "${BRANCH_DIR}"`
+ REVS=`"${SVN_MERGE_SVN}" propget "${OLD_PROP}-revs" "${BRANCH_DIR}"`
+ if echo "${HEAD}" | grep -qE '^[[:alpha:]][-+.[:alnum:]]*://'; then
+ url_to_rlpath "${HEAD}"
+ HEAD="${RETURN_VALUE}"
+ fi
+ INTEGRATED="${INTEGRATED} ${HEAD}:${REVS}"
+ done
+ INTEGRATED=`echo "${INTEGRATED}" | xargs -n 1 | sort`
+ echo " svn propset ${SVN_MERGE_PROP} \"${INTEGRATED}\" ${BRANCH_DIR}"
     echo ''
     exit 1
 fi
 
-# In the --init case, convert ${HEAD} into ${HEAD_URL} and ${HEAD_PATH}
-if [ "${ACTION}" = "init" ]; then
+# Calculate ${HEAD_URL} and ${HEAD_PATH}
+if [ -z "${HEAD}" ]; then
+ if [ "${ACTION}" == "init" ]; then
+ get_copyfrom "${BRANCH_DIR}"
+ [ -z "${RETURN_VALUE}" ] && \
+ error no copyfrom info available. Explicit \"head\" argument required.
+ HEAD_PATH=`echo "${RETURN_VALUE}" | cut -d: -f 1`
+ [ -z "${REVS}" ] && REVS="1-"`echo "${RETURN_VALUE}" | cut -d: -f 2`
+ else
+ get_default_head "${BRANCH_DIR}"
+ HEAD_PATH="${RETURN_VALUE}"
+ fi
+ get_repo_root "${BRANCH_DIR}"
+ HEAD_URL="${RETURN_VALUE}/${HEAD_PATH}"
+else
     target_to_url "${HEAD}"
     HEAD_URL="${RETURN_VALUE}"
     url_to_rlpath "${HEAD_URL}"
     HEAD_PATH="${RETURN_VALUE}"
 fi
 
-# Determine which label to use
-if [ "${ACTION}" = "init" ]; then
- [ "${SVN_MERGE_LABEL}" != "" ] || SVN_MERGE_LABEL="${DFLABEL}"
-else
- # Get the list of labels currently in use
- LABELS=`"${SVN_MERGE_SVN}" proplist "${BRANCH_DIR}" | tail +2 \
- | grep '^[[:space:]]\{0,\}'"${NAME}"'-\([^-]\{1,\}\)-.\{1,\}$' \
- | sed 's/^[[:space:]]\{0,\}'"${NAME}"'-\([^-]\{1,\}\)-.\{1,\}$/\1/g' \
- | sort -u`
- # If more than one is in use, user must specify which one to use
- # Otherwise, use the only one there is
- NUM_LABELS=`echo ${LABELS} | wc -w`
- if [ ${NUM_LABELS} -eq 1 ]; then
- if [ "${SVN_MERGE_LABEL}" = "" ]; then
- SVN_MERGE_LABEL="${LABELS}"
- fi
- fi
- if [ ${NUM_LABELS} -gt 1 ]; then
- if [ "${SVN_MERGE_LABEL}" = "" ]; then
- echo "${NAME}: multiple labels are in use; -L flag is required."
- echo -n "${NAME}: these labels are currently in use:"
- for LABEL in ${LABELS}; do echo -n " ${LABEL}"; done
- echo ''
- exit 1
- fi
- fi
-fi
-
-# Get property names associated with the chosen label
-SVN_MERGE_HEAD_PROP="${NAME}-${SVN_MERGE_LABEL}-head"
-SVN_MERGE_REVS_PROP="${NAME}-${SVN_MERGE_LABEL}-revs"
-
-# If already initialized, retrieve ${HEAD_URL} from the corresponding property
-if [ "${ACTION}" != "init" ]; then
- get_prop "${SVN_MERGE_HEAD_PROP}" "${BRANCH_DIR}"
- HEAD_PATH="${RETURN_VALUE}"
- # handle old metadata scheme which used repository URL's
- if echo "${HEAD_PATH}" | grep -qE '^[[:alpha:]][-+.[:alnum:]]*://'; then
- echo "${NAME}: old head property value detected; an upgrade is required."
- echo
- url_to_rlpath "${HEAD_PATH}"
- echo "Please execute and commit this change to upgrade:"
- echo
- echo " svn propset ${SVN_MERGE_HEAD_PROP} ${RETURN_VALUE} ${BRANCH_DIR}"
- echo
- exit 1
- fi
- get_repo_root "${BRANCH_DIR}"
- HEAD_URL="${RETURN_VALUE}${HEAD_PATH}"
-fi
-
 # Sanity check ${HEAD_URL}
 echo "${HEAD_URL}" | grep -qE '^[[:alpha:]][-+.[:alnum:]]*://' ||
     error "\"${HEAD_URL}\" is not a valid URL or working directory"
@@ -680,7 +673,7 @@
 
 # Get previously merged revisions (except when --init)
 if [ "${ACTION}" != "init" ]; then
- get_prop "${SVN_MERGE_REVS_PROP}" "${BRANCH_DIR}"
+ get_integrated_revs "${HEAD_PATH}" "${BRANCH_DIR}"
     normalize_list "${RETURN_VALUE}"
     MERGED_REVS="${RETURN_VALUE}"
 fi

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Jan 24 02:14:44 2005

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.