merging with hg(1)

Giorgos Keramidas keramida at ceid.upatras.gr
Tue Jan 22 19:13:24 EET 2008


Καλησπέρα,

όσοι χρησιμοποιείται το Mercurial για merges με το hg.hellug.gr μπορεί
να βρείτε χρήσιμο το παρακάτω script.  Το χρησιμοποιώ εδώ και καιρό εγώ
βάζοντας στο ``~/.hgrc'' μου κάτι σαν:

    [ui]
    merge = /home/keramida/bin/hg-merge

Μια σημαντική αλλαγή που έκανα πρόσφατα είναι ότι αν σε ένα commit
υπάρχουν merges/revisions που είναι 100% πανομοιότυπα, π.χ.:


    [ancestor] --> [keramida]
       \
        `---> [manolis]

Τότε το merge κάνει αυτόματα 'resolve', θεωρώντας ότι το να είναι ίδια
τα αρχεία στο [keramida] και [manolis] είναι αρκετά καλή ένδειξη ότι δεν
χρειάζεται να γίνει merge τίποτα, π.χ.:

- Δημιουργία ενός `master' με ένα `foo' file:

    % cd /tmp
    % mkdir hgdemo
    % cd hgdemo
    % hg init master
    % cd master
    % echo foo > foo
    % hg add
    adding foo
    % hg ci -m 'add foo'
    % cd ..
    % ls -la
    total 6
    drwxrwxr-x   3 keramida  wheel  - 512 Jan 22 19:01 .
    drwxrwxrwt  12 root      wheel  - 512 Jan 22 19:01 ..
    drwxrwxr-x   3 keramida  wheel  - 512 Jan 22 19:01 master

- Δημιουργία 2 clones με ακριβώς την ίδια αλλαγή, αλλά όχι το ίδιο
  changeset id:

    % hg clone master keramida
    1 files updated, 0 files merged, 0 files removed, 0 files unresolved
    % hg clone master manolis
    1 files updated, 0 files merged, 0 files removed, 0 files unresolved
    % cd manolis
    % echo foobar > foo
    % hg ci -m 'modify foo'
    % cd /tmp/hgdemo/keramida/
    % echo foobar > foo
    % hg ci -m 'modify foo (duplicate)'
    % hg heads --template '{rev}:{node|short} {tags}| {author|email} | {desc|strip|firstline}\n'
    1:cd54f17c35c3 tip| keramida at ceid.upatras.gr | modify foo (duplicate)
    % hg pull ../manolis
    pulling from ../manolis
    searching for changes
    adding changesets
    adding manifests
    adding file changes
    added 1 changesets with 0 changes to 1 files (+1 heads)
    (run 'hg heads' to see heads, 'hg merge' to merge)
    % hg heads --template '{rev}:{node|short} {tags}| {author|email} | {desc|strip|firstline}\n'
    2:038803e77c9f tip| keramida at ceid.upatras.gr | modify foo
    1:cd54f17c35c3 | keramida at ceid.upatras.gr | modify foo (duplicate)

Επειδή οι *content* αλλαγές είναι ακριβώς οι ίδιες, στα δύο heads (1)
και (2), το επόμενο βήμα δεν ξεκινάει κάποιο editor για manual merge:

    % hg up --clean 1
    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
    % hg merge
    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
    (branch merge, don't forget to commit)
    % hg st
    % hg parent --template '{rev}:{node|short} {tags}| {author|email} | {desc|strip|firstline}\n'
    1:cd54f17c35c3 | keramida at ceid.upatras.gr | modify foo (duplicate)
    2:038803e77c9f tip| keramida at ceid.upatras.gr | modify foo
    % hg ci -m 'merge from manolis (no conflicts)'
    %

Αλλά το merge έχει γίνει κανονικά:

    % hg glog
    @    changeset:   3:45fe20157a6f
    |\   tag:         tip
    | |  parent:      1:cd54f17c35c3
    | |  parent:      2:038803e77c9f
    | |  user:        Giorgos Keramidas <keramida at ceid.upatras.gr>
    | |  date:        Tue Jan 22 19:03:19 2008 +0200
    | |  summary:     merge from manolis (no conflicts)
    | |
    | o  changeset:   2:038803e77c9f
    | |  parent:      0:30961d810b03
    | |  user:        Giorgos Keramidas <keramida at ceid.upatras.gr>
    | |  date:        Tue Jan 22 19:02:04 2008 +0200
    | |  summary:     modify foo
    | |
    o |  changeset:   1:cd54f17c35c3
    |/   user:        Giorgos Keramidas <keramida at ceid.upatras.gr>
    |    date:        Tue Jan 22 19:02:21 2008 +0200
    |    summary:     modify foo (duplicate)
    |
    o  changeset:   0:30961d810b03
       user:        Giorgos Keramidas <keramida at ceid.upatras.gr>
       date:        Tue Jan 22 19:01:36 2008 +0200
       summary:     add foo

    %

Το `hg-merge' script που έχω τοπικά εγώ τώρα είναι:

----- start of script -----------------------------------------------------
#!/bin/sh
#
# hg-merge -- merge wrapper for the Mercurial version-control system
#
# Copyright (c) 2006 Giorgos Keramidas <keramida at FreeBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

if test $# -ne 3 ; then
	echo >&2 "usage: `basename $0` MYFILE OLDFILE YOURFILE"
	exit 1
fi

# Keep a local copy of the filenames involved in the merge.
LOCAL="$1"
BASE="$2"
OTHER="$3"

cleanup() {
	# We failed.  If ${RESTORE} is set, then we are supposed to have
	# the pathname of a ${BACKUP} copy, and we should restore ${BACKUP}
	# to ${LOCAL} before dying.

	CLEANUP=true		# Make sure we don't recurse forever.

	test -z "${RESTORE}"		&& return
	test X"${RESTORE}" = X'yes'	|| return

	if test -z "${BACKUP}" || test -z "${LOCAL}" ; then
		err 1 "internal merge script error."
	fi

	cat "${BACKUP}" > "${LOCAL}" && rm "${BACKUP}"
	if test $? -ne 0 ; then
		err 1 "Cannot restore ${LOCAL} -- workspace is *unclean*"
	fi

	return 0
}

success() {
	if test -z "${BACKUP}" || test -z "${LOCAL}" ; then
		err 1 "internal merge script error."
	fi

	# The merge was successful.  Remove the backup copy of ${LOCAL}
	if test -n "${BACKUP}" ; then
		/bin/rm -f "${BACKUP}"
	fi
}

err() {
	errcode=$1
	shift
	echo >&2 "`basename $0`: error: $*"
	if test -z "${CLEANUP}" ; then
		cleanup
	fi
	exit $errcode
}

# Since this script depends on manual edits being performed to the files being
# merged, make sure that ${EDITOR} is truly set to something, even if this is
# just plain good ol' vi(1).
EDITOR="${EDITOR:-vi}"
export EDITOR

# First make sure $TMPDIR points to a meaningful directory.  We will be using
# this shell variable further down, so it's a good idea to make sure it isn't
# empty later on.
TMPDIR="${TMPDIR:-/var/tmp}"
export TMPDIR

# We depend on diff3(1) being available to do the first pass of the merge,
# adding conflict markers around the areas that should be edited.
which diff3 >/dev/null 2>&1
if test $? -ne 0 ; then
	err 1 "No diff3(1) utility found in the current PATH."
fi

# We will be using a temporary file with the diff3(1) output as the merge
# buffer, until either the merge removes all conflict markers from the working
# copy of the file or we fail somehow to complete the merge.
BACKUP=`mktemp "${TMPDIR}/hgmerge-XXXXXX"`
if test $? -ne 0 ; then
	err 1 "Cannot create backup file at ${TMPDIR}/hgmerge-XXXXXX"
fi

LABEL=`basename "${LOCAL}"`

# First try to add conflict markers around the areas that need special
# attention in the ${LOCAL} file.
cp "${LOCAL}" "${BACKUP}" && RESTORE='yes'
rc=$?
if test $rc -ne 0 ; then
	err 1 "Cannot create backup file at ${BACKUP}"
fi

# If the remote and the local file have no differences, then there's
# nothing to merge.  Accept both :)
if cmp "${LOCAL}" "${OTHER}" ; then
	success
	exit 0
fi

diff3 -m \
    -L "${LABEL}" -L "${LABEL}.base" -L "${LABEL}.other" \
    "${BACKUP}" "${BASE}" "${OTHER}" > "${LOCAL}"
rc=$?
if test $rc -eq 0 ; then
	# No conflicts found.  Merge done.
	success
	exit 0
elif test $rc -gt 1 ; then
	err 1 "serious diff3 error, while trying to merge ${LOCAL}"
fi

# In all other cases, diff3(1) has found conflicts, added the proper
# conflict markers to the ${LOCAL} file and we should now edit this file.
#
# Editing the ${LOCAL} file "pollutes" the workspace area, but the filename
# shown in the editor buffer _really_ reflects the workspace path of the
# ${LOCAL} file, which is very helpful with editors (i.e. it lets the
# editor autopick the right 'mode' for editing the file, and so on).
#
# When the editor exits successfully, there should be no conflict markers
# in the ${LOCAL} file, otherwise we consider this merge failed.
${EDITOR} "${LOCAL}"
if test $? -ne 0 ; then
	err 1 "merge error for ${LOCAL}"
fi
if grep '^<<<<<<<' "${LOCAL}" >/dev/null 2>&1 ; then
	err 1 "'^<<<<<<<' conflict markers" \
	    "still found in the working-copy." \
	    "Merge aborted for ${LOCAL}"
fi
if grep '^>>>>>>>' "${LOCAL}" >/dev/null 2>&1 ; then
	err 1 "'^>>>>>>>' conflict markers" \
	    "still found in the working-copy." \
	    "Merge aborted for ${LOCAL}"
fi

success
exit 0
----- end of script -------------------------------------------------------




More information about the Freebsd-doc-el mailing list