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 |
|