--- a/contrib/darcs2hg.py Thu Sep 27 23:59:18 2007 -0500
+++ b/contrib/darcs2hg.py Tue Oct 02 18:04:18 2007 -0500
@@ -3,18 +3,22 @@
# vim: tw=80 ts=4 sw=4 noet
# -----------------------------------------------------------------------------
# Project : Basic Darcs to Mercurial conversion script
+#
+# *** DEPRECATED. Use the convert extension instead. This script will
+# *** be removed soon.
+#
# -----------------------------------------------------------------------------
# Authors : Sebastien Pierre <sebastien@xprima.com>
# TK Soh <teekaysoh@gmail.com>
# -----------------------------------------------------------------------------
# Creation : 24-May-2006
-# Last mod : 05-Jun-2006
# -----------------------------------------------------------------------------
import os, sys
import tempfile
import xml.dom.minidom as xml_dom
from time import strptime, mktime
+import re
DARCS_REPO = None
HG_REPO = None
@@ -93,11 +97,50 @@
def darcs_pull(hg_repo, darcs_repo, chash):
old_tip = darcs_tip(darcs_repo)
res = cmd("darcs pull \"%s\" --all --match=\"hash %s\"" % (darcs_repo, chash), hg_repo)
+ if re.search('^We have conflicts in the following files:$', res, re.MULTILINE):
+ print "Trying to revert files to work around conflict..."
+ rev_res = cmd ("darcs revert --all", hg_repo)
+ print rev_res
print res
new_tip = darcs_tip(darcs_repo)
if not new_tip != old_tip + 1:
error("Darcs pull did not work as expected: " + res)
+def darcs_changes_summary(darcs_repo, chash):
+ """Gets the changes from the darcs summary. This returns the chronological
+ list of changes as (change_type, args). Eg. ('add_file', 'foo.txt') or
+ ('move', ['foo.txt','bar.txt'])."""
+ change = cmd("darcs changes --summary --xml-output --match=\"hash %s\"" % (chash), darcs_repo)
+ doc = xml_dom.parseString(change)
+ for patch_node in doc.childNodes[0].childNodes:
+ summary_nodes = filter(lambda n: n.nodeName == "summary" and n.nodeType == n.ELEMENT_NODE, patch_node.childNodes)
+ for summary_node in summary_nodes:
+ change_nodes = filter(lambda n: n.nodeType == n.ELEMENT_NODE, summary_node.childNodes)
+ if len(change_nodes) == 0:
+ name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
+ if not name:
+ error("Darcs patch has an empty summary node and no name: " + patch_node.toxml())
+ name = name[0].childNodes[0].data.strip()
+ (tag, sub_count) = re.subn('^TAG ', '', name, 1)
+ if sub_count != 1:
+ error("Darcs patch has an empty summary node but doesn't look like a tag: " + patch_node.toxml());
+ for change_node in change_nodes:
+ change = change_node.nodeName
+ if change == 'modify_file':
+ yield change, change_node.childNodes[0].data.strip()
+ elif change == 'add_file':
+ yield change, change_node.childNodes[0].data.strip()
+ elif change == 'remove_file':
+ yield change, change_node.childNodes[0].data.strip()
+ elif change == 'add_directory':
+ yield change, change_node.childNodes[0].data.strip()
+ elif change == 'remove_directory':
+ yield change, change_node.childNodes[0].data.strip()
+ elif change == 'move':
+ yield change, (change_node.getAttribute('from'), change_node.getAttribute('to'))
+ else:
+ error('Problem parsing summary xml: Unexpected element: ' + change_node.toxml())
+
# ------------------------------------------------------------------------------
#
# Mercurial interface
@@ -127,6 +170,36 @@
tip = tip.split("\n")[0].split(":")[1].strip()
return int(tip)
+def hg_rename( hg_repo, from_file, to_file ):
+ cmd("hg rename --after \"%s\" \"%s\"" % (from_file, to_file), hg_repo);
+
+def hg_tag ( hg_repo, text, author, date ):
+ old_tip = hg_tip(hg_repo)
+ res = cmd("hg tag -u \"%s\" -d \"%s 0\" \"%s\"" % (author, date, text), hg_repo)
+ new_tip = hg_tip(hg_repo)
+ if not new_tip == old_tip + 1:
+ error("Mercurial tag did not work as expected: " + res)
+
+def hg_handle_change( hg_repo, author, date, change, arg ):
+ """Processes a change event as output by darcs_changes_summary. These
+ consist of file move/rename/add/delete commands."""
+ if change == 'modify_file':
+ pass
+ elif change == 'add_file':
+ pass
+ elif change =='remove_file':
+ pass
+ elif change == 'add_directory':
+ pass
+ elif change == 'remove_directory':
+ pass
+ elif change == 'move':
+ hg_rename(hg_repo, arg[0], arg[1])
+ elif change == 'tag':
+ hg_tag(hg_repo, arg, author, date)
+ else:
+ error('Unknown change type ' + change + ': ' + arg)
+
# ------------------------------------------------------------------------------
#
# Main
@@ -147,6 +220,7 @@
else:
print USAGE
sys.exit(-1)
+ print 'This command is deprecated. Use the convert extension instead.'
# Initializes the target repo
if not os.path.isdir(darcs_repo + "/_darcs"):
print "No darcs directory found at: " + darcs_repo
@@ -167,11 +241,13 @@
print "(skipping)"
else:
text = summary + "\n" + description
- darcs_pull(hg_repo, darcs_repo, chash)
# The commit hash has a date like 20021020201112
# --------------------------------YYYYMMDDHHMMSS
date = chash.split("-")[0]
epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
+ darcs_pull(hg_repo, darcs_repo, chash)
+ for change, arg in darcs_changes_summary(darcs_repo, chash):
+ hg_handle_change(hg_repo, author, epoch, change, arg)
hg_commit(hg_repo, text, author, epoch)
change_number += 1
print "Darcs repository (_darcs) was not deleted. You can keep or remove it."
--- a/hgext/convert/__init__.py Thu Sep 27 23:59:18 2007 -0500
+++ b/hgext/convert/__init__.py Tue Oct 02 18:04:18 2007 -0500
@@ -7,6 +7,7 @@
from common import NoRepo, converter_source, converter_sink
from cvs import convert_cvs
+from darcs import darcs_source
from git import convert_git
from hg import mercurial_source, mercurial_sink
from subversion import convert_svn, debugsvnlog
@@ -18,7 +19,7 @@
commands.norepo += " convert debugsvnlog"
converters = [convert_cvs, convert_git, convert_svn, mercurial_source,
- mercurial_sink]
+ mercurial_sink, darcs_source]
def convertsource(ui, path, **opts):
for c in converters:
@@ -235,6 +236,7 @@
def convert(self):
try:
+ self.source.before()
self.dest.before()
self.source.setrevmap(self.map)
self.ui.status("scanning source...\n")
@@ -273,7 +275,10 @@
self.cleanup()
def cleanup(self):
- self.dest.after()
+ try:
+ self.dest.after()
+ finally:
+ self.source.after()
if self.revmapfilefd:
self.revmapfilefd.close()
@@ -367,9 +372,10 @@
"""Convert a foreign SCM repository to a Mercurial one.
Accepted source formats:
- - GIT
- CVS
- - SVN
+ - Darcs
+ - git
+ - Subversion
Accepted destination formats:
- Mercurial
--- a/hgext/convert/common.py Thu Sep 27 23:59:18 2007 -0500
+++ b/hgext/convert/common.py Tue Oct 02 18:04:18 2007 -0500
@@ -38,6 +38,12 @@
self.encoding = 'utf-8'
+ def before(self):
+ pass
+
+ def after(self):
+ pass
+
def setrevmap(self, revmap):
"""set the map of already-converted revisions"""
pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/darcs.py Tue Oct 02 18:04:18 2007 -0500
@@ -0,0 +1,137 @@
+# darcs support for the convert extension
+
+from common import NoRepo, commit, converter_source
+from mercurial.i18n import _
+from mercurial import util
+import os, shutil, tempfile
+
+# The naming drift of ElementTree is fun!
+
+try: from xml.etree.cElementTree import ElementTree
+except ImportError:
+ try: from xml.etree.ElementTree import ElementTree
+ except ImportError:
+ try: from elementtree.cElementTree import ElementTree
+ except ImportError:
+ try: from elementtree.ElementTree import ElementTree
+ except ImportError: ElementTree = None
+
+
+class darcs_source(converter_source):
+ def __init__(self, ui, path, rev=None):
+ super(darcs_source, self).__init__(ui, path, rev=rev)
+
+ if not os.path.exists(os.path.join(path, '_darcs', 'inventory')):
+ raise NoRepo("couldn't open darcs repo %s" % path)
+
+ if ElementTree is None:
+ raise util.Abort(_("Python ElementTree module is not available"))
+
+ self.path = os.path.realpath(path)
+
+ self.lastrev = None
+ self.changes = {}
+ self.parents = {}
+ self.tags = {}
+
+ def before(self):
+ self.tmppath = tempfile.mkdtemp(
+ prefix='convert-' + os.path.basename(self.path) + '-')
+ output, status = self.run('init', repodir=self.tmppath)
+ self.checkexit(status)
+
+ tree = self.xml('changes', '--xml-output', '--summary')
+ tagname = None
+ child = None
+ for elt in tree.findall('patch'):
+ node = elt.get('hash')
+ name = elt.findtext('name', '')
+ if name.startswith('TAG '):
+ tagname = name[4:].strip()
+ elif tagname is not None:
+ self.tags[tagname] = node
+ tagname = None
+ self.changes[node] = elt
+ self.parents[child] = [node]
+ child = node
+ self.parents[child] = []
+
+ def after(self):
+ self.ui.debug('cleaning up %s\n' % self.tmppath)
+ #shutil.rmtree(self.tmppath, ignore_errors=True)
+
+ def _run(self, cmd, *args, **kwargs):
+ cmdline = 'darcs %s --repodir=%r %s </dev/null' % (
+ cmd, kwargs.get('repodir', self.path), ' '.join(args))
+ self.ui.debug(cmdline, '\n')
+ return os.popen(cmdline, 'r')
+
+ def run(self, cmd, *args, **kwargs):
+ fp = self._run(cmd, *args, **kwargs)
+ output = fp.read()
+ return output, fp.close()
+
+ def checkexit(self, status, output=''):
+ if status:
+ if output:
+ ui.warn(_('darcs error:\n'))
+ ui.warn(output)
+ msg = util.explain_exit(status)[0]
+ raise util.Abort(_('darcs %s') % msg)
+
+ def xml(self, cmd, *opts):
+ etree = ElementTree()
+ fp = self._run(cmd, *opts)
+ etree.parse(fp)
+ self.checkexit(fp.close())
+ return etree.getroot()
+
+ def getheads(self):
+ return self.parents[None]
+
+ def getcommit(self, rev):
+ elt = self.changes[rev]
+ date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
+ desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
+ return commit(author=elt.get('author'), date=util.datestr(date),
+ desc=desc.strip(), parents=self.parents[rev])
+
+ def pull(self, rev):
+ output, status = self.run('pull %r --all --match="hash %s"' %
+ (self.path, rev),
+ '--no-test', '--no-posthook',
+ '--external-merge=/bin/false',
+ repodir=self.tmppath)
+ if status:
+ if output.find('We have conflicts in') == -1:
+ self.checkexit(status, output)
+ output, status = self.run('revert --all', repodir=self.tmppath)
+ self.checkexit(status, output)
+
+ def getchanges(self, rev):
+ self.pull(rev)
+ copies = {}
+ changes = []
+ for elt in self.changes[rev].find('summary').getchildren():
+ if elt.tag in ('add_directory', 'remove_directory'):
+ continue
+ if elt.tag == 'move':
+ changes.append((elt.get('from'), rev))
+ copies[elt.get('from')] = elt.get('to')
+ else:
+ changes.append((elt.text.strip(), rev))
+ changes.sort()
+ self.lastrev = rev
+ return changes, copies
+
+ def getfile(self, name, rev):
+ if rev != self.lastrev:
+ raise util.Abort(_('internal calling inconsistency'))
+ return open(os.path.join(self.tmppath, name), 'rb').read()
+
+ def getmode(self, name, rev):
+ mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
+ return (mode & 0111) and 'x' or ''
+
+ def gettags(self):
+ return self.tags
--- a/hgext/convert/hg.py Thu Sep 27 23:59:18 2007 -0500
+++ b/hgext/convert/hg.py Tue Oct 02 18:04:18 2007 -0500
@@ -59,9 +59,9 @@
def delfile(self, f):
try:
- os.unlink(self.repo.wjoin(f))
+ util.unlink(self.repo.wjoin(f))
#self.repo.remove([f])
- except:
+ except OSError:
pass
def setbranch(self, branch, pbranch, parents):
@@ -156,7 +156,10 @@
class mercurial_source(converter_source):
def __init__(self, ui, path, rev=None):
converter_source.__init__(self, ui, path, rev)
- self.repo = hg.repository(self.ui, path)
+ try:
+ self.repo = hg.repository(self.ui, path)
+ except:
+ raise NoRepo("could not open hg repo %s as source" % path)
self.lastrev = None
self.lastctx = None
--- a/mercurial/util.py Thu Sep 27 23:59:18 2007 -0500
+++ b/mercurial/util.py Tue Oct 02 18:04:18 2007 -0500
@@ -1095,7 +1095,7 @@
def set_exec(f, mode):
s = os.lstat(f).st_mode
- if (s & 0100 != 0) == mode:
+ if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
return
if mode:
# Turn on +x for every +r bit when making a file executable
@@ -1468,7 +1468,7 @@
s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
return s
-def strdate(string, format, defaults):
+def strdate(string, format, defaults=[]):
"""parse a localized time string and return a (unixtime, offset) tuple.
if the string cannot be parsed, ValueError is raised."""
def timezone(string):
--- a/tests/test-convert-git Thu Sep 27 23:59:18 2007 -0500
+++ b/tests/test-convert-git Tue Oct 02 18:04:18 2007 -0500
@@ -25,16 +25,26 @@
cd git-repo
git init-db >/dev/null 2>/dev/null
echo a > a
-git add a
-commit -m t1
+mkdir d
+echo b > d/b
+git add a d
+commit -a -m t1
+
+# Remove the directory, then try to replace it with a file
+# (issue 754)
+git rm -r d
+commit -m t2
+echo d > d
+git add d
+commit -m t3
echo b >> a
-commit -a -m t2.1
+commit -a -m t4.1
git checkout -b other HEAD^ >/dev/null 2>/dev/null
echo c > a
echo a >> a
-commit -a -m t2.2
+commit -a -m t4.2
git checkout master >/dev/null 2>/dev/null
git pull --no-commit . other > /dev/null 2>/dev/null
--- a/tests/test-convert-git.out Thu Sep 27 23:59:18 2007 -0500
+++ b/tests/test-convert-git.out Tue Oct 02 18:04:18 2007 -0500
@@ -1,18 +1,21 @@
+rm 'd/b'
assuming destination git-repo-hg
initializing destination git-repo-hg repository
scanning source...
sorting...
converting...
-3 t1
-2 t2.1
-1 t2.2
+5 t1
+4 t2
+3 t3
+2 t4.1
+1 t4.2
0 Merge branch other
-changeset: 3:69b3a302b4a1
+changeset: 5:c6d72c98aa00
tag: tip
-parent: 1:0de2a40e261b
-parent: 2:8815d3b33506
+parent: 3:a18bdfccf429
+parent: 4:48cb5b72ce56
user: test <test@example.org>
-date: Mon Jan 01 00:00:13 2007 +0000
+date: Mon Jan 01 00:00:15 2007 +0000
files: a
description:
Merge branch other
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-debugindexdot Tue Oct 02 18:04:18 2007 -0500
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Just exercize debugindexdot
+# Create a short file history including a merge.
+hg init t
+cd t
+echo a > a
+hg ci -qAm t1 -d '0 0'
+echo a >> a
+hg ci -m t2 -d '1 0'
+hg up -qC 0
+echo b >> a
+hg ci -m t3 -d '2 0'
+HGMERGE=true hg merge -q
+hg ci -m merge -d '3 0'
+
+hg debugindexdot .hg/store/data/a.i
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-debugindexdot.out Tue Oct 02 18:04:18 2007 -0500
@@ -0,0 +1,7 @@
+digraph G {
+ -1 -> 0
+ 0 -> 1
+ 0 -> 2
+ 2 -> 3
+ 1 -> 3
+}