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