##// END OF EJS Templates
convert/cvs: delay CVS log parsing after initialization (issue1581/2)...
Patrick Mezard -
r8048:d22432bd default
parent child Browse files
Show More
@@ -1,353 +1,359 b''
1 1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
2 2
3 3 import os, locale, re, socket, errno
4 4 from cStringIO import StringIO
5 5 from mercurial import util
6 6 from mercurial.i18n import _
7 7
8 8 from common import NoRepo, commit, converter_source, checktool
9 9 import cvsps
10 10
11 11 class convert_cvs(converter_source):
12 12 def __init__(self, ui, path, rev=None):
13 13 super(convert_cvs, self).__init__(ui, path, rev=rev)
14 14
15 15 cvs = os.path.join(path, "CVS")
16 16 if not os.path.exists(cvs):
17 17 raise NoRepo("%s does not look like a CVS checkout" % path)
18 18
19 19 checktool('cvs')
20 20 self.cmd = ui.config('convert', 'cvsps', 'builtin')
21 21 cvspsexe = self.cmd.split(None, 1)[0]
22 22 self.builtin = cvspsexe == 'builtin'
23 23
24 24 if not self.builtin:
25 25 checktool(cvspsexe)
26 26
27 self.changeset = {}
27 self.changeset = None
28 28 self.files = {}
29 29 self.tags = {}
30 30 self.lastbranch = {}
31 31 self.parent = {}
32 32 self.socket = None
33 33 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
34 34 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
35 35 self.encoding = locale.getpreferredencoding()
36 36
37 self._parse(ui)
38 37 self._connect()
39 38
40 def _parse(self, ui):
41 if self.changeset:
39 def _parse(self):
40 if self.changeset is not None:
42 41 return
42 self.changeset = {}
43 43
44 44 maxrev = 0
45 45 cmd = self.cmd
46 46 if self.rev:
47 47 # TODO: handle tags
48 48 try:
49 49 # patchset number?
50 50 maxrev = int(self.rev)
51 51 except ValueError:
52 52 try:
53 53 # date
54 54 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
55 55 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
56 56 except util.Abort:
57 57 raise util.Abort(_('revision %s is not a patchset number or date') % self.rev)
58 58
59 59 d = os.getcwd()
60 60 try:
61 61 os.chdir(self.path)
62 62 id = None
63 63 state = 0
64 64 filerevids = {}
65 65
66 66 if self.builtin:
67 67 # builtin cvsps code
68 ui.status(_('using builtin cvsps\n'))
68 self.ui.status(_('using builtin cvsps\n'))
69 69
70 db = cvsps.createlog(ui, cache='update')
71 db = cvsps.createchangeset(ui, db,
72 fuzz=int(ui.config('convert', 'cvsps.fuzz', 60)),
73 mergeto=ui.config('convert', 'cvsps.mergeto', None),
74 mergefrom=ui.config('convert', 'cvsps.mergefrom', None))
70 db = cvsps.createlog(self.ui, cache='update')
71 db = cvsps.createchangeset(self.ui, db,
72 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
73 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
74 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
75 75
76 76 for cs in db:
77 77 if maxrev and cs.id>maxrev:
78 78 break
79 79 id = str(cs.id)
80 80 cs.author = self.recode(cs.author)
81 81 self.lastbranch[cs.branch] = id
82 82 cs.comment = self.recode(cs.comment)
83 83 date = util.datestr(cs.date)
84 84 self.tags.update(dict.fromkeys(cs.tags, id))
85 85
86 86 files = {}
87 87 for f in cs.entries:
88 88 files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]),
89 89 ['', '(DEAD)'][f.dead])
90 90
91 91 # add current commit to set
92 92 c = commit(author=cs.author, date=date,
93 93 parents=[str(p.id) for p in cs.parents],
94 94 desc=cs.comment, branch=cs.branch or '')
95 95 self.changeset[id] = c
96 96 self.files[id] = files
97 97 else:
98 98 # external cvsps
99 99 for l in util.popen(cmd):
100 100 if state == 0: # header
101 101 if l.startswith("PatchSet"):
102 102 id = l[9:-2]
103 103 if maxrev and int(id) > maxrev:
104 104 # ignore everything
105 105 state = 3
106 106 elif l.startswith("Date:"):
107 107 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
108 108 date = util.datestr(date)
109 109 elif l.startswith("Branch:"):
110 110 branch = l[8:-1]
111 111 self.parent[id] = self.lastbranch.get(branch, 'bad')
112 112 self.lastbranch[branch] = id
113 113 elif l.startswith("Ancestor branch:"):
114 114 ancestor = l[17:-1]
115 115 # figure out the parent later
116 116 self.parent[id] = self.lastbranch[ancestor]
117 117 elif l.startswith("Author:"):
118 118 author = self.recode(l[8:-1])
119 119 elif l.startswith("Tag:") or l.startswith("Tags:"):
120 120 t = l[l.index(':')+1:]
121 121 t = [ut.strip() for ut in t.split(',')]
122 122 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
123 123 self.tags.update(dict.fromkeys(t, id))
124 124 elif l.startswith("Log:"):
125 125 # switch to gathering log
126 126 state = 1
127 127 log = ""
128 128 elif state == 1: # log
129 129 if l == "Members: \n":
130 130 # switch to gathering members
131 131 files = {}
132 132 oldrevs = []
133 133 log = self.recode(log[:-1])
134 134 state = 2
135 135 else:
136 136 # gather log
137 137 log += l
138 138 elif state == 2: # members
139 139 if l == "\n": # start of next entry
140 140 state = 0
141 141 p = [self.parent[id]]
142 142 if id == "1":
143 143 p = []
144 144 if branch == "HEAD":
145 145 branch = ""
146 146 if branch:
147 147 latest = 0
148 148 # the last changeset that contains a base
149 149 # file is our parent
150 150 for r in oldrevs:
151 151 latest = max(filerevids.get(r, 0), latest)
152 152 if latest:
153 153 p = [latest]
154 154
155 155 # add current commit to set
156 156 c = commit(author=author, date=date, parents=p,
157 157 desc=log, branch=branch)
158 158 self.changeset[id] = c
159 159 self.files[id] = files
160 160 else:
161 161 colon = l.rfind(':')
162 162 file = l[1:colon]
163 163 rev = l[colon+1:-2]
164 164 oldrev, rev = rev.split("->")
165 165 files[file] = rev
166 166
167 167 # save some information for identifying branch points
168 168 oldrevs.append("%s:%s" % (oldrev, file))
169 169 filerevids["%s:%s" % (rev, file)] = id
170 170 elif state == 3:
171 171 # swallow all input
172 172 continue
173 173
174 174 self.heads = self.lastbranch.values()
175 175 finally:
176 176 os.chdir(d)
177 177
178 178 def _connect(self):
179 179 root = self.cvsroot
180 180 conntype = None
181 181 user, host = None, None
182 182 cmd = ['cvs', 'server']
183 183
184 184 self.ui.status(_("connecting to %s\n") % root)
185 185
186 186 if root.startswith(":pserver:"):
187 187 root = root[9:]
188 188 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
189 189 root)
190 190 if m:
191 191 conntype = "pserver"
192 192 user, passw, serv, port, root = m.groups()
193 193 if not user:
194 194 user = "anonymous"
195 195 if not port:
196 196 port = 2401
197 197 else:
198 198 port = int(port)
199 199 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
200 200 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
201 201
202 202 if not passw:
203 203 passw = "A"
204 204 cvspass = os.path.expanduser("~/.cvspass")
205 205 try:
206 206 pf = open(cvspass)
207 207 for line in pf.read().splitlines():
208 208 part1, part2 = line.split(' ', 1)
209 209 if part1 == '/1':
210 210 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
211 211 part1, part2 = part2.split(' ', 1)
212 212 format = format1
213 213 else:
214 214 # :pserver:user@example.com:/cvsroot/foo Ah<Z
215 215 format = format0
216 216 if part1 == format:
217 217 passw = part2
218 218 break
219 219 pf.close()
220 220 except IOError, inst:
221 221 if inst.errno != errno.ENOENT:
222 222 if not getattr(inst, 'filename', None):
223 223 inst.filename = cvspass
224 224 raise
225 225
226 226 sck = socket.socket()
227 227 sck.connect((serv, port))
228 228 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
229 229 "END AUTH REQUEST", ""]))
230 230 if sck.recv(128) != "I LOVE YOU\n":
231 231 raise util.Abort(_("CVS pserver authentication failed"))
232 232
233 233 self.writep = self.readp = sck.makefile('r+')
234 234
235 235 if not conntype and root.startswith(":local:"):
236 236 conntype = "local"
237 237 root = root[7:]
238 238
239 239 if not conntype:
240 240 # :ext:user@host/home/user/path/to/cvsroot
241 241 if root.startswith(":ext:"):
242 242 root = root[5:]
243 243 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
244 244 # Do not take Windows path "c:\foo\bar" for a connection strings
245 245 if os.path.isdir(root) or not m:
246 246 conntype = "local"
247 247 else:
248 248 conntype = "rsh"
249 249 user, host, root = m.group(1), m.group(2), m.group(3)
250 250
251 251 if conntype != "pserver":
252 252 if conntype == "rsh":
253 253 rsh = os.environ.get("CVS_RSH") or "ssh"
254 254 if user:
255 255 cmd = [rsh, '-l', user, host] + cmd
256 256 else:
257 257 cmd = [rsh, host] + cmd
258 258
259 259 # popen2 does not support argument lists under Windows
260 260 cmd = [util.shellquote(arg) for arg in cmd]
261 261 cmd = util.quotecommand(' '.join(cmd))
262 262 self.writep, self.readp = util.popen2(cmd, 'b')
263 263
264 264 self.realroot = root
265 265
266 266 self.writep.write("Root %s\n" % root)
267 267 self.writep.write("Valid-responses ok error Valid-requests Mode"
268 268 " M Mbinary E Checked-in Created Updated"
269 269 " Merged Removed\n")
270 270 self.writep.write("valid-requests\n")
271 271 self.writep.flush()
272 272 r = self.readp.readline()
273 273 if not r.startswith("Valid-requests"):
274 274 raise util.Abort(_("server sucks"))
275 275 if "UseUnchanged" in r:
276 276 self.writep.write("UseUnchanged\n")
277 277 self.writep.flush()
278 278 r = self.readp.readline()
279 279
280 280 def getheads(self):
281 self._parse()
281 282 return self.heads
282 283
283 284 def _getfile(self, name, rev):
284 285
285 286 def chunkedread(fp, count):
286 287 # file-objects returned by socked.makefile() do not handle
287 288 # large read() requests very well.
288 289 chunksize = 65536
289 290 output = StringIO()
290 291 while count > 0:
291 292 data = fp.read(min(count, chunksize))
292 293 if not data:
293 294 raise util.Abort(_("%d bytes missing from remote file") % count)
294 295 count -= len(data)
295 296 output.write(data)
296 297 return output.getvalue()
297 298
298 299 if rev.endswith("(DEAD)"):
299 300 raise IOError
300 301
301 302 args = ("-N -P -kk -r %s --" % rev).split()
302 303 args.append(self.cvsrepo + '/' + name)
303 304 for x in args:
304 305 self.writep.write("Argument %s\n" % x)
305 306 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
306 307 self.writep.flush()
307 308
308 309 data = ""
309 310 while 1:
310 311 line = self.readp.readline()
311 312 if line.startswith("Created ") or line.startswith("Updated "):
312 313 self.readp.readline() # path
313 314 self.readp.readline() # entries
314 315 mode = self.readp.readline()[:-1]
315 316 count = int(self.readp.readline()[:-1])
316 317 data = chunkedread(self.readp, count)
317 318 elif line.startswith(" "):
318 319 data += line[1:]
319 320 elif line.startswith("M "):
320 321 pass
321 322 elif line.startswith("Mbinary "):
322 323 count = int(self.readp.readline()[:-1])
323 324 data = chunkedread(self.readp, count)
324 325 else:
325 326 if line == "ok\n":
326 327 return (data, "x" in mode and "x" or "")
327 328 elif line.startswith("E "):
328 329 self.ui.warn(_("cvs server: %s\n") % line[2:])
329 330 elif line.startswith("Remove"):
330 331 self.readp.readline()
331 332 else:
332 333 raise util.Abort(_("unknown CVS response: %s") % line)
333 334
334 335 def getfile(self, file, rev):
336 self._parse()
335 337 data, mode = self._getfile(file, rev)
336 338 self.modecache[(file, rev)] = mode
337 339 return data
338 340
339 341 def getmode(self, file, rev):
340 342 return self.modecache[(file, rev)]
341 343
342 344 def getchanges(self, rev):
345 self._parse()
343 346 self.modecache = {}
344 347 return util.sort(self.files[rev].items()), {}
345 348
346 349 def getcommit(self, rev):
350 self._parse()
347 351 return self.changeset[rev]
348 352
349 353 def gettags(self):
354 self._parse()
350 355 return self.tags
351 356
352 357 def getchangedfiles(self, rev, i):
358 self._parse()
353 359 return util.sort(self.files[rev].keys())
@@ -1,53 +1,53 b''
1 1 % create cvs repository
2 2 % Create a new project
3 3
4 4
5 5 N src/a
6 6 N src/b
7 7 No conflicts created by this import
8 8 cvs checkout: Updating src
9 9 U src/a
10 10 U src/b
11 11 % Branch the project
12 12 cvs tag: Tagging .
13 13 T a
14 14 T b
15 15 cvs update: Updating .
16 16 % Modify file a, then b, then a
17 17 cvs commit: Examining .
18 18 checking in src/a,v
19 19 cvs commit: Examining .
20 20 checking in src/b,v
21 21 cvs commit: Examining .
22 22 checking in src/a,v
23 23 % Convert
24 24 assuming destination src-hg
25 25 initializing destination src-hg repository
26 connecting to cvsrepo
27 scanning source...
26 28 using builtin cvsps
27 29 collecting CVS rlog
28 30 7 log entries
29 31 creating changesets
30 32 5 changeset entries
31 connecting to cvsrepo
32 scanning source...
33 33 sorting...
34 34 converting...
35 35 4 Initial revision
36 36 3 init
37 37 2 mod a
38 38 1 mod b
39 39 0 mod a again
40 40 updating tags
41 41 % Check the result
42 42 o 5 () update tags files: .hgtags
43 43 |
44 44 | o 4 (BRANCH) mod a again files: a
45 45 | |
46 46 | o 3 (BRANCH) mod b files: b
47 47 | |
48 48 | o 2 (BRANCH) mod a files: a
49 49 | |
50 50 | o 1 (v0) init files:
51 51 |/
52 52 o 0 () Initial revision files: a b
53 53
@@ -1,236 +1,236 b''
1 1 % create cvs repository
2 2 % create source directory
3 3 % import source directory
4 4 N src/a
5 5 N src/b/c
6 6
7 7 No conflicts created by this import
8 8
9 9 % checkout source directory
10 10 U src/a
11 11 U src/b/c
12 12 % commit a new revision changing b/c
13 13 checking in src/b/c,v
14 14 % convert fresh repo
15 15 initializing destination src-hg repository
16 connecting to cvsrepo
17 scanning source...
16 18 using builtin cvsps
17 19 collecting CVS rlog
18 20 5 log entries
19 21 creating changesets
20 22 3 changeset entries
21 connecting to cvsrepo
22 scanning source...
23 23 sorting...
24 24 converting...
25 25 2 Initial revision
26 26 1 import
27 27 0 ci0
28 28 updating tags
29 29 a
30 30 c
31 31 c
32 32 % convert fresh repo with --filemap
33 33 initializing destination src-filemap repository
34 connecting to cvsrepo
35 scanning source...
34 36 using builtin cvsps
35 37 collecting CVS rlog
36 38 5 log entries
37 39 creating changesets
38 40 3 changeset entries
39 connecting to cvsrepo
40 scanning source...
41 41 sorting...
42 42 converting...
43 43 2 Initial revision
44 44 1 import
45 45 rolling back last transaction
46 46 0 ci0
47 47 updating tags
48 48 c
49 49 c
50 50 2 update tags files: .hgtags
51 51 1 ci0 files: b/c
52 52 0 Initial revision files: b/c
53 53 % commit new file revisions
54 54 checking in src/a,v
55 55 checking in src/b/c,v
56 56 % convert again
57 connecting to cvsrepo
58 scanning source...
57 59 using builtin cvsps
58 60 collecting CVS rlog
59 61 7 log entries
60 62 creating changesets
61 63 4 changeset entries
62 connecting to cvsrepo
63 scanning source...
64 64 sorting...
65 65 converting...
66 66 0 ci1
67 67 a
68 68 a
69 69 c
70 70 c
71 71 c
72 72 % convert again with --filemap
73 connecting to cvsrepo
74 scanning source...
73 75 using builtin cvsps
74 76 collecting CVS rlog
75 77 7 log entries
76 78 creating changesets
77 79 4 changeset entries
78 connecting to cvsrepo
79 scanning source...
80 80 sorting...
81 81 converting...
82 82 0 ci1
83 83 c
84 84 c
85 85 c
86 86 3 ci1 files: b/c
87 87 2 update tags files: .hgtags
88 88 1 ci0 files: b/c
89 89 0 Initial revision files: b/c
90 90 % commit branch
91 91 U b/c
92 92 T a
93 93 T b/c
94 94 checking in src/b/c,v
95 95 % convert again
96 connecting to cvsrepo
97 scanning source...
96 98 using builtin cvsps
97 99 collecting CVS rlog
98 100 8 log entries
99 101 creating changesets
100 102 5 changeset entries
101 connecting to cvsrepo
102 scanning source...
103 103 sorting...
104 104 converting...
105 105 0 ci2
106 106 a
107 107 c
108 108 d
109 109 % convert again with --filemap
110 connecting to cvsrepo
111 scanning source...
110 112 using builtin cvsps
111 113 collecting CVS rlog
112 114 8 log entries
113 115 creating changesets
114 116 5 changeset entries
115 connecting to cvsrepo
116 scanning source...
117 117 sorting...
118 118 converting...
119 119 0 ci2
120 120 c
121 121 d
122 122 4 ci2 files: b/c
123 123 3 ci1 files: b/c
124 124 2 update tags files: .hgtags
125 125 1 ci0 files: b/c
126 126 0 Initial revision files: b/c
127 127 % commit a new revision with funny log message
128 128 checking in src/a,v
129 129 % convert again
130 connecting to cvsrepo
131 scanning source...
130 132 using builtin cvsps
131 133 collecting CVS rlog
132 134 9 log entries
133 135 creating changesets
134 136 6 changeset entries
135 connecting to cvsrepo
136 scanning source...
137 137 sorting...
138 138 converting...
139 139 0 funny
140 140 o 6 (branch) funny
141 141 | ----------------------------
142 142 | log message files: a
143 143 o 5 (branch) ci2 files: b/c
144 144 |
145 145 | o 4 () ci1 files: a b/c
146 146 | |
147 147 | o 3 () update tags files: .hgtags
148 148 | |
149 149 | o 2 () ci0 files: b/c
150 150 |/
151 151 | o 1 (INITIAL) import files:
152 152 |/
153 153 o 0 () Initial revision files: a b/c
154 154
155 155 % testing debugcvsps
156 156 collecting CVS rlog
157 157 9 log entries
158 158 creating changesets
159 159 6 changeset entries
160 160 ---------------------
161 161 PatchSet 1
162 162 Date:
163 163 Author:
164 164 Branch: HEAD
165 165 Tag: (none)
166 166 Log:
167 167 Initial revision
168 168
169 169 Members:
170 170 a:INITIAL->1.1
171 171 b/c:INITIAL->1.1
172 172
173 173 ---------------------
174 174 PatchSet 2
175 175 Date:
176 176 Author:
177 177 Branch: INITIAL
178 178 Tag: start
179 179 Log:
180 180 import
181 181
182 182 Members:
183 183 a:1.1->1.1.1.1
184 184 b/c:1.1->1.1.1.1
185 185
186 186 ---------------------
187 187 PatchSet 3
188 188 Date:
189 189 Author:
190 190 Branch: HEAD
191 191 Tag: (none)
192 192 Log:
193 193 ci0
194 194
195 195 Members:
196 196 b/c:1.1->1.2
197 197
198 198 ---------------------
199 199 PatchSet 4
200 200 Date:
201 201 Author:
202 202 Branch: HEAD
203 203 Tag: (none)
204 204 Log:
205 205 ci1
206 206
207 207 Members:
208 208 a:1.1->1.2
209 209 b/c:1.2->1.3
210 210
211 211 ---------------------
212 212 PatchSet 5
213 213 Date:
214 214 Author:
215 215 Branch: branch
216 216 Tag: (none)
217 217 Log:
218 218 ci2
219 219
220 220 Members:
221 221 b/c:1.1->1.1.2.1
222 222
223 223 ---------------------
224 224 PatchSet 6
225 225 Date:
226 226 Author:
227 227 Branch: branch
228 228 Tag: (none)
229 229 Log:
230 230 funny
231 231 ----------------------------
232 232 log message
233 233
234 234 Members:
235 235 a:1.2->1.2.2.1
236 236
@@ -1,54 +1,54 b''
1 1 % create cvs repository with one project
2 2 cvs checkout: Updating proj
3 3 % create file1 on the trunk
4 4 cvs add: scheduling file `file1' for addition
5 5 cvs add: use `cvs commit' to add this file permanently
6 6 *REPO*/proj/file1,v <-- file1
7 7 initial revision: 1.1
8 8 % create two branches
9 9 cvs tag: Tagging .
10 10 T file1
11 11 cvs tag: Tagging .
12 12 T file1
13 13 % create file2 on branch v1_0
14 14 cvs update: Updating .
15 15 cvs add: scheduling file `file2' for addition on branch `v1_0'
16 16 cvs add: use `cvs commit' to add this file permanently
17 17 *REPO*/proj/Attic/file2,v <-- file2
18 18 new revision: 1.1.2.1; previous revision: 1.1
19 19 % create file3, file4 on branch v1_1
20 20 cvs update: Updating .
21 21 cvs update: `file2' is no longer in the repository
22 22 cvs add: scheduling file `file3' for addition on branch `v1_1'
23 23 cvs add: scheduling file `file4' for addition on branch `v1_1'
24 24 cvs add: use `cvs commit' to add these files permanently
25 25 *REPO*/proj/Attic/file3,v <-- file3
26 26 new revision: 1.1.2.1; previous revision: 1.1
27 27 *REPO*/proj/Attic/file4,v <-- file4
28 28 new revision: 1.1.2.1; previous revision: 1.1
29 29 % merge file2 from v1_0 to v1_1
30 30 cvs update: Updating .
31 31 U file2
32 32 cvs commit: Examining .
33 33 *REPO*/proj/Attic/file2,v <-- file2
34 34 new revision: 1.1.4.2; previous revision: 1.1.4.1
35 35 % convert to hg
36 36 initializing destination proj.hg repository
37 connecting to *REPO*
38 scanning source...
37 39 using builtin cvsps
38 40 collecting CVS rlog
39 41 9 log entries
40 42 creating changesets
41 43 4 changeset entries
42 connecting to *REPO*
43 scanning source...
44 44 sorting...
45 45 converting...
46 46 3 add file1 on trunk
47 47 2 add file2 on branch v1_0
48 48 1 add file3, file4 on branch v1_1
49 49 0 merge file2 from v1_0 to v1_1
50 50 % hg log output
51 51 3 merge file2 from v1_0 to v1_1
52 52 2 add file3, file4 on branch v1_1
53 53 1 add file2 on branch v1_0
54 54 0 add file1 on trunk
General Comments 0
You need to be logged in to leave comments. Login now