changeset 5367:adce4d30a6ea

Merge with crew
author Matt Mackall <mpm@selenic.com>
date Tue, 02 Oct 2007 18:04:18 -0500
parents d0c48891dd4a (current diff) b98c377b3c16 (diff)
children ff32b2725651
files
diffstat 10 files changed, 287 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/darcs2hg.py
+++ b/contrib/darcs2hg.py
@@ -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_tip(darcs_repo):
 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 @@ def hg_tip( hg_repo ):
 	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 @@ if __name__ == "__main__":
 	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 @@ if __name__ == "__main__":
 			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
+++ b/hgext/convert/__init__.py
@@ -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 @@ from mercurial.i18n import _
 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 @@ class converter(object):
 
     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 @@ class converter(object):
             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 @@ def convert(ui, src, dest=None, revmapfi
     """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
+++ b/hgext/convert/common.py
@@ -38,6 +38,12 @@ class converter_source(object):
 
         self.encoding = 'utf-8'
 
+    def before(self):
+        pass
+
+    def after(self):
+        pass
+
     def setrevmap(self, revmap):
         """set the map of already-converted revisions"""
         pass
new file mode 100644
--- /dev/null
+++ b/hgext/convert/darcs.py
@@ -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
+++ b/hgext/convert/hg.py
@@ -59,9 +59,9 @@ class mercurial_sink(converter_sink):
 
     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_sink(converter_sink):
 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
+++ b/mercurial/util.py
@@ -1095,7 +1095,7 @@ else:
 
     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 @@ def datestr(date=None, format='%a %b %d 
         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
+++ b/tests/test-convert-git
@@ -25,16 +25,26 @@ mkdir git-repo
 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
+++ b/tests/test-convert-git.out
@@ -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
new file mode 100755
--- /dev/null
+++ b/tests/test-debugindexdot
@@ -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
new file mode 100644
--- /dev/null
+++ b/tests/test-debugindexdot.out
@@ -0,0 +1,7 @@
+digraph G {
+	-1 -> 0
+	0 -> 1
+	0 -> 2
+	2 -> 3
+	1 -> 3
+}