contrib/darcs2hg.py
changeset 6942 5423224c7913
parent 6941 b2bc2d984bac
child 6943 2ca70663ded3
equal deleted inserted replaced
6941:b2bc2d984bac 6942:5423224c7913
     1 #!/usr/bin/env python
       
     2 # Encoding: iso-8859-1
       
     3 # vim: tw=80 ts=4 sw=4 noet
       
     4 # -----------------------------------------------------------------------------
       
     5 # Project   : Basic Darcs to Mercurial conversion script
       
     6 #
       
     7 # *** DEPRECATED. Use the convert extension instead. This script will
       
     8 # *** be removed soon.
       
     9 #
       
    10 # -----------------------------------------------------------------------------
       
    11 # Authors   : Sebastien Pierre                           <sebastien@xprima.com>
       
    12 #             TK Soh                                      <teekaysoh@gmail.com>
       
    13 # -----------------------------------------------------------------------------
       
    14 # Creation  : 24-May-2006
       
    15 # -----------------------------------------------------------------------------
       
    16 
       
    17 import os, sys
       
    18 import tempfile
       
    19 import xml.dom.minidom as xml_dom
       
    20 from time import strptime, mktime
       
    21 import re
       
    22 
       
    23 DARCS_REPO = None
       
    24 HG_REPO    = None
       
    25 
       
    26 USAGE = """\
       
    27 %s DARCSREPO HGREPO [SKIP]
       
    28 
       
    29     Converts the given Darcs repository to a new Mercurial repository. The given
       
    30     HGREPO must not exist, as it will be created and filled up (this will avoid
       
    31     overwriting valuable data.
       
    32 
       
    33     In case an error occurs within the process, you can resume the process by
       
    34     giving the last successfuly applied change number.
       
    35 """ % (os.path.basename(sys.argv[0]))
       
    36 
       
    37 # ------------------------------------------------------------------------------
       
    38 #
       
    39 # Utilities
       
    40 #
       
    41 # ------------------------------------------------------------------------------
       
    42 
       
    43 def cmd(text, path=None, silent=False):
       
    44 	"""Executes a command, in the given directory (if any), and returns the
       
    45 	command result as a string."""
       
    46 	cwd = None
       
    47 	if path:
       
    48 		path = os.path.abspath(path)
       
    49 		cwd  = os.getcwd()
       
    50 		os.chdir(path)
       
    51 	if not silent: print "> ", text
       
    52 	res = os.popen(text).read()
       
    53 	if path:
       
    54 		os.chdir(cwd)
       
    55 	return res
       
    56 
       
    57 def writefile(path, data):
       
    58 	"""Writes the given data into the given file."""
       
    59 	f = file(path, "w") ; f.write(data)  ; f.close()
       
    60 
       
    61 def error( *args ):
       
    62 	sys.stderr.write("ERROR: ")
       
    63 	for a in args: sys.stderr.write(str(a))
       
    64 	sys.stderr.write("\n")
       
    65 	sys.stderr.write("You can make manual fixes if necessary and then resume by"
       
    66 	" giving the last changeset number")
       
    67 	sys.exit(-1)
       
    68 
       
    69 # ------------------------------------------------------------------------------
       
    70 #
       
    71 # Darcs interface
       
    72 #
       
    73 # ------------------------------------------------------------------------------
       
    74 
       
    75 def darcs_changes(darcsRepo):
       
    76 	"""Gets the changes list from the given darcs repository. This returns the
       
    77 	chronological list of changes as (change name, change summary)."""
       
    78 	changes    = cmd("darcs changes --reverse --xml-output", darcsRepo)
       
    79 	doc        = xml_dom.parseString(changes)
       
    80 	for patch_node in doc.childNodes[0].childNodes:
       
    81 		name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
       
    82 		comm = filter(lambda n: n.nodeName == "comment", patch_node.childNodes)
       
    83 		if not name:continue
       
    84 		else: name = name[0].childNodes[0].data
       
    85 		if not comm: comm = ""
       
    86 		else: comm = comm[0].childNodes[0].data
       
    87 		author = patch_node.getAttribute("author")
       
    88 		date   = patch_node.getAttribute("date")
       
    89 		chash  = os.path.splitext(patch_node.getAttribute("hash"))[0]
       
    90 		yield author, date, name, chash, comm
       
    91 
       
    92 def darcs_tip(darcs_repo):
       
    93 	changes = cmd("darcs changes",darcs_repo,silent=True)
       
    94 	changes = filter(lambda l: l.strip().startswith("* "), changes.split("\n"))
       
    95 	return len(changes)
       
    96 
       
    97 def darcs_pull(hg_repo, darcs_repo, chash):
       
    98 	old_tip = darcs_tip(darcs_repo)
       
    99 	res     = cmd("darcs pull \"%s\" --all --match=\"hash %s\"" % (darcs_repo, chash), hg_repo)
       
   100 	if re.search('^We have conflicts in the following files:$', res, re.MULTILINE):
       
   101 		print "Trying to revert files to work around conflict..."
       
   102 		rev_res = cmd ("darcs revert --all", hg_repo)
       
   103 		print rev_res
       
   104 	print res
       
   105 	new_tip = darcs_tip(darcs_repo)
       
   106 	if not new_tip != old_tip + 1:
       
   107 		error("Darcs pull did not work as expected: " + res)
       
   108 
       
   109 def darcs_changes_summary(darcs_repo, chash):
       
   110 	"""Gets the changes from the darcs summary. This returns the chronological
       
   111 	list of changes as (change_type, args). Eg. ('add_file', 'foo.txt') or
       
   112 	('move', ['foo.txt','bar.txt'])."""
       
   113 	change = cmd("darcs changes --summary --xml-output --match=\"hash %s\"" % (chash), darcs_repo)
       
   114 	doc = xml_dom.parseString(change)
       
   115 	for patch_node in doc.childNodes[0].childNodes:
       
   116 		summary_nodes = filter(lambda n: n.nodeName == "summary" and n.nodeType == n.ELEMENT_NODE, patch_node.childNodes)
       
   117 		for summary_node in summary_nodes:
       
   118 			change_nodes = filter(lambda n: n.nodeType == n.ELEMENT_NODE, summary_node.childNodes)
       
   119 			if len(change_nodes) == 0:
       
   120 				name = filter(lambda n: n.nodeName == "name", patch_node.childNodes)
       
   121 				if not name:
       
   122 					error("Darcs patch has an empty summary node and no name: " + patch_node.toxml())
       
   123 				name = name[0].childNodes[0].data.strip()
       
   124 				(tag, sub_count) = re.subn('^TAG ', '', name, 1)
       
   125 				if sub_count != 1:
       
   126 					error("Darcs patch has an empty summary node but doesn't look like a tag: " + patch_node.toxml());
       
   127 			for change_node in change_nodes:
       
   128 				change = change_node.nodeName
       
   129 				if change == 'modify_file':
       
   130 					yield change, change_node.childNodes[0].data.strip()
       
   131 				elif change == 'add_file':
       
   132 					yield change, change_node.childNodes[0].data.strip()
       
   133 				elif change == 'remove_file':
       
   134 					yield change, change_node.childNodes[0].data.strip()
       
   135 				elif change == 'add_directory':
       
   136 					yield change, change_node.childNodes[0].data.strip()
       
   137 				elif change == 'remove_directory':
       
   138 					yield change, change_node.childNodes[0].data.strip()
       
   139 				elif change == 'move':
       
   140 					yield change, (change_node.getAttribute('from'), change_node.getAttribute('to'))
       
   141 				else:
       
   142 					error('Problem parsing summary xml: Unexpected element: ' + change_node.toxml())
       
   143 
       
   144 # ------------------------------------------------------------------------------
       
   145 #
       
   146 # Mercurial interface
       
   147 #
       
   148 # ------------------------------------------------------------------------------
       
   149 
       
   150 def hg_commit( hg_repo, text, author, date ):
       
   151 	fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_")
       
   152 	writefile(tmpfile, text)
       
   153 	old_tip = hg_tip(hg_repo)
       
   154 	cmd("hg add -X _darcs", hg_repo)
       
   155 	cmd("hg remove -X _darcs --after", hg_repo)
       
   156 	res = cmd("hg commit -l %s -u \"%s\" -d \"%s 0\""  % (tmpfile, author, date), hg_repo)
       
   157 	os.close(fd)
       
   158 	os.unlink(tmpfile)
       
   159 	new_tip = hg_tip(hg_repo)
       
   160 	if not new_tip == old_tip + 1:
       
   161 		# Sometimes we may have empty commits, we simply skip them
       
   162 		if res.strip().lower().find("nothing changed") != -1:
       
   163 			pass
       
   164 		else:
       
   165 			error("Mercurial commit did not work as expected: " + res)
       
   166 
       
   167 def hg_tip( hg_repo ):
       
   168 	"""Returns the latest local revision number in the given repository."""
       
   169 	tip = cmd("hg tip", hg_repo, silent=True)
       
   170 	tip = tip.split("\n")[0].split(":")[1].strip()
       
   171 	return int(tip)
       
   172 
       
   173 def hg_rename( hg_repo, from_file, to_file ):
       
   174 	cmd("hg rename --after \"%s\" \"%s\"" % (from_file, to_file), hg_repo);
       
   175 	
       
   176 def hg_tag ( hg_repo, text, author, date ):
       
   177 	old_tip = hg_tip(hg_repo)
       
   178 	res = cmd("hg tag -u \"%s\" -d \"%s 0\" \"%s\""	 % (author, date, text), hg_repo)
       
   179 	new_tip = hg_tip(hg_repo)
       
   180 	if not new_tip == old_tip + 1:
       
   181 		error("Mercurial tag did not work as expected: " + res)
       
   182 
       
   183 def hg_handle_change( hg_repo, author, date, change, arg ):
       
   184 	"""Processes a change event as output by darcs_changes_summary. These
       
   185 	consist of file move/rename/add/delete commands."""
       
   186 	if change == 'modify_file':
       
   187 		pass
       
   188 	elif change == 'add_file':
       
   189 		pass
       
   190 	elif change =='remove_file':
       
   191 		pass
       
   192 	elif change == 'add_directory':
       
   193 		pass
       
   194 	elif change == 'remove_directory':
       
   195 		pass
       
   196 	elif change == 'move':
       
   197 		hg_rename(hg_repo, arg[0], arg[1])
       
   198 	elif change == 'tag':
       
   199 		hg_tag(hg_repo, arg, author, date)
       
   200 	else:
       
   201 		error('Unknown change type ' + change + ': ' + arg)
       
   202 
       
   203 # ------------------------------------------------------------------------------
       
   204 #
       
   205 # Main
       
   206 #
       
   207 # ------------------------------------------------------------------------------
       
   208 
       
   209 if __name__ == "__main__":
       
   210 	args = sys.argv[1:]
       
   211 	# We parse the arguments
       
   212 	if len(args)   == 2:
       
   213 		darcs_repo = os.path.abspath(args[0])
       
   214 		hg_repo    = os.path.abspath(args[1])
       
   215 		skip       = None
       
   216 	elif len(args) == 3:
       
   217 		darcs_repo = os.path.abspath(args[0])
       
   218 		hg_repo    = os.path.abspath(args[1])
       
   219 		skip       = int(args[2])
       
   220 	else:
       
   221 		print USAGE
       
   222 		sys.exit(-1)
       
   223 	print 'This command is deprecated.  Use the convert extension instead.'
       
   224 	# Initializes the target repo
       
   225 	if not os.path.isdir(darcs_repo + "/_darcs"):
       
   226 		print "No darcs directory found at: " + darcs_repo
       
   227 		sys.exit(-1)
       
   228 	if not os.path.isdir(hg_repo):
       
   229 		os.mkdir(hg_repo)
       
   230 	elif skip == None:
       
   231 		print "Given HG repository must not exist when no SKIP is specified."
       
   232 		sys.exit(-1)
       
   233 	if skip == None:
       
   234 		cmd("hg init \"%s\"" % (hg_repo))
       
   235 		cmd("darcs initialize", hg_repo)
       
   236 	# Get the changes from the Darcs repository
       
   237 	change_number = 0
       
   238 	for author, date, summary, chash, description in darcs_changes(darcs_repo):
       
   239 		print "== changeset", change_number,
       
   240 		if skip != None and change_number <= skip:
       
   241 			print "(skipping)"
       
   242 		else:
       
   243 			text = summary + "\n" + description
       
   244 			# The commit hash has a date like 20021020201112
       
   245 			# --------------------------------YYYYMMDDHHMMSS
       
   246 			date = chash.split("-")[0]
       
   247 			epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
       
   248 			darcs_pull(hg_repo, darcs_repo, chash)
       
   249 			for change, arg in darcs_changes_summary(darcs_repo, chash):
       
   250 				hg_handle_change(hg_repo, author, epoch, change, arg)
       
   251 			hg_commit(hg_repo, text, author, epoch)
       
   252 		change_number += 1
       
   253 	print "Darcs repository (_darcs) was not deleted. You can keep or remove it."
       
   254 
       
   255 # EOF