5 darcs-fast-export - darcs backend for fast data exporters
7 Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
8 Copyright (c) 2008 Matthias Andree <matthias.andree@gmx.de>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2, or (at your option)
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
41 self
.unread_line
= False
44 self
.export_marks
= []
45 self
.import_marks
= []
47 def read_next_line(self
):
49 self
.unread_line
= False
57 buf
= sys
.stdin
.readline()
63 print "read_next_line: '%s'" % self
.line
65 def read(self
, length
):
70 buf
+= sys
.stdin
.read(length
)
72 print "read: '%s'" % buf
75 def skip_optional_lf(self
):
76 self
.ch
= self
.read(1)
83 def get_date(self
, ts
, tz
):
84 # first fix the case when tz is higher than +1200, as
85 # darcs won't accept it
87 ts
= str(int(ts
) + 60*60*24)
88 tz
= str(int(tz
[:3])-24) + tz
[3:]
89 # int(ts) is seconds since epoch. Since we're trying to
90 # capture both the absolute time of the commit and the
91 # localtime in the timezone of the committer, we need to turn
92 # the (seconds-since-epoch, committer-timezone-offset) pair
93 # that we get from the git-fast-export stream format into a
94 # localized-time-plus-timezone-marker string that darcs will
95 # accept. Therefore, we parse the timezone-offset (which
96 # looks like +0500 or +0000 or -0730 or something) and add it
97 # to seconds-since-epoch before calling gmtime().
98 mo
= re
.search(r
'^([\+\-])(\d\d)(\d\d)$', tz
)
99 offset
= 60*60*int(mo
.group(2)) + 60*int(mo
.group(3))
100 if mo
.group(1) == "-":
102 offset_time
= int(ts
) + offset
103 s
= time
.strftime("%a %b %d %H:%M:%S %Y", time
.gmtime(offset_time
))
105 return " ".join(items
[:-1]) + " " + tz
+ " " + items
[-1]
107 def handle_mark(self
):
108 if self
.line
.startswith("mark :"):
109 self
.mark_num
= int(self
.line
[6:-1])
110 self
.read_next_line()
112 def handle_data(self
):
113 if not self
.line
.startswith("data "):
114 self
.bug("Expected 'data n' command, found: '%s'" % self
.line
[:-1])
115 length
= int(self
.line
[5:-1])
116 self
.buf
= self
.read(length
)
117 self
.skip_optional_lf()
119 def handle_blob(self
):
120 self
.read_next_line()
123 self
.marks
[self
.mark_num
] = self
.buf
125 def handle_ident(self
, s
):
127 self
.ident
= " ".join(items
[:-2])
128 self
.date
= self
.get_date(items
[-2], items
[-1])
130 def handle_msg(self
):
131 items
= self
.buf
.split('\n')
132 self
.short
= items
[0]
133 self
.long = "\n".join(items
[1:])
135 def handle_tag(self
):
136 version
= self
.line
[:-1].split(' ')[1]
137 self
.read_next_line()
138 if self
.line
.startswith("from "):
139 self
.read_next_line()
140 if self
.line
.startswith("tagger "):
141 self
.handle_ident(self
.line
[7:-1])
142 self
.read_next_line()
144 self
.skip_optional_lf()
145 sock
= subprocess
.Popen(["darcs", "tag", "--pipe"], stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
)
146 buf
= [self
.date
, self
.ident
, version
]
147 sock
.stdin
.write("\n".join(buf
))
149 self
.log("Tagging %s:\n%s" % (version
, sock
.stdout
.read()))
152 def handle_commit(self
):
153 if not self
.prevfiles
and self
.options
.import_marks
:
154 # first commit in an incremental continued
156 for (root
, dirs
, files
) in os
.walk("."):
158 path
= os
.path
.normpath(os
.path
.join(root
, i
))
159 if path
.startswith("_darcs") or "-darcs-backup" in path
:
161 self
.files
.append(path
)
162 self
.prevfiles
= self
.files
[:]
165 self
.read_next_line()
167 if self
.line
.startswith("author "):
168 self
.handle_ident(self
.line
[7:-1])
169 self
.read_next_line()
170 if self
.line
.startswith("committer "):
171 self
.handle_ident(self
.line
[10:-1])
172 self
.read_next_line()
174 self
.skip_optional_lf()
176 self
.read_next_line()
177 if self
.line
.startswith("from "):
178 self
.read_next_line()
179 while self
.line
.startswith("merge "):
180 self
.read_next_line()
181 while len(self
.line
) > 0:
182 if self
.line
.startswith("deleteall"):
183 path
= self
.line
[2:-1]
184 for path
in self
.files
:
187 elif self
.line
.startswith("D "):
188 path
= self
.line
[2:-1]
189 if os
.path
.exists(path
):
191 if path
in self
.files
:
192 self
.files
.remove(path
)
193 elif self
.line
.startswith("R "):
194 os
.system("darcs mv %s" % self
.line
[2:])
195 elif self
.line
.startswith("C "):
196 src
, dest
= self
.line
[:-1].split(' ')[1:]
197 shutil
.copy(src
.strip('"'), dest
.strip('"'))
198 os
.system("darcs add %s" % dest
)
199 elif self
.line
.startswith("M "):
200 items
= self
.line
.split(' ')
202 dir = os
.path
.split(path
)[0]
203 if len(dir) and not os
.path
.exists(dir):
205 sock
= open(path
, "w")
206 if items
[2] != "inline":
207 idx
= int(items
[2][1:])
208 sock
.write(self
.marks
[idx
])
211 self
.read_next_line()
215 if path
not in self
.prevfiles
:
217 if path
not in self
.files
:
218 self
.files
.append(path
)
220 self
.unread_line
= True
222 self
.read_next_line()
223 if not len(self
.line
):
227 os
.system("darcs add %s" % i
)
228 sock
= subprocess
.Popen(["darcs", "record", "--ignore-times", "-a", "--pipe"], stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
)
229 buf
= [self
.date
, self
.ident
, self
.short
, self
.long]
230 sock
.stdin
.write("\n".join(buf
))
232 self
.log("Recording :%s:\n%s" % (self
.mark_num
, sock
.stdout
.read()))
235 if self
.options
.export_marks
:
236 # yeah, an xml parser would be better, but
237 # should we mess with encodings just because of
239 sock
= os
.popen("darcs changes --last=1 --xml", "r")
242 hash = buf
.split('\n')[1].split("'")[-2]
243 self
.export_marks
.append(":%s %s" % (self
.mark_num
, hash))
245 def handle_progress(self
, s
):
246 print "progress [%s] %s" % (time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime()), s
.strip())
249 def handle_opts(self
):
251 usage
="%prog [options]"
252 opp
= optparse
.OptionParser(usage
=usage
)
253 opp
.add_option("--import-marks", metavar
="IFILE",
254 help="read state for incremental imports from IFILE")
255 opp
.add_option("--export-marks", metavar
="OFILE",
256 help="write state for incremental imports to OFILE")
257 opp
.add_option("--logfile", metavar
="L",
258 help="log file which contains the output of external programs invoked during the conversion")
259 (self
.options
, args
) = opp
.parse_args()
261 if self
.options
.logfile
:
262 logfile
= self
.options
.logfile
264 logfile
= "_darcs/import.log"
265 self
.logsock
= open(os
.path
.abspath(logfile
), "a")
268 self
.logsock
.write("[%s] %s" % (time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime()), s
))
271 def handle_export_marks(self
):
272 if self
.options
.export_marks
:
273 sock
= open(self
.options
.export_marks
, 'w')
274 sock
.write("\n".join(self
.export_marks
))
278 def handle_import_marks(self
):
279 if self
.options
.import_marks
:
280 sock
= open(self
.options
.import_marks
)
281 for i
in sock
.readlines():
285 self
.import_marks
.append(line
.split(' ')[1])
286 self
.export_marks
.append(line
)
291 self
.handle_import_marks()
294 self
.read_next_line()
295 if not len(self
.line
[:-1]):
297 elif self
.line
.startswith("blob"):
299 elif self
.line
.startswith("commit"):
301 elif self
.line
.startswith("tag"):
303 elif self
.line
.startswith("reset"):
304 self
.read_next_line()
305 if not self
.line
.startswith("from "):
306 self
.unread_line
= True
307 elif self
.line
.startswith("checkpoint"):
309 elif self
.line
.startswith("progress"):
310 self
.handle_progress(self
.line
[9:])
312 self
.bug("'%s': invalid command" % self
.line
[:-1])
314 self
.handle_export_marks()
316 if __name__
== "__main__":