File: c2f_repository.tcl

package info (click to toggle)
fossil 1%3A1.22.1%2Bdfsg-0.1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 10,588 kB
  • sloc: ansic: 151,799; tcl: 10,291; sh: 4,413; makefile: 1,822; sql: 376
file content (502 lines) | stat: -rw-r--r-- 15,710 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2007-2008 Andreas Kupries.
#
# This software is licensed as described in the file LICENSE, which
# you should have received as part of this distribution.
#
# This software consists of voluntary contributions made by many
# individuals.  For exact contribution history, see the revision
# history and logs, available at http://fossil-scm.hwaci.com/fossil
# # ## ### ##### ######## ############# #####################

## Repository manager. Keeps projects and their files around.

package provide vc::fossil::import::cvs::repository 1.0

# # ## ### ##### ######## ############# #####################
## Requirements

package require Tcl 8.4                          ; # Required runtime.
package require snit                             ; # OO system.
package require vc::tools::trouble               ; # Error reporting.
package require vc::tools::log                   ; # User feedback.
package require vc::tools::misc                  ; # Text formatting.
package require vc::tools::id                    ; # Indexing and id generation.
package require vc::fossil::import::cvs::project ; # CVS projects.
package require vc::fossil::import::cvs::state   ; # State storage.
package require struct::list                     ; # List operations.
package require fileutil                         ; # File operations.

# # ## ### ##### ######## ############# #####################
##

snit::type ::vc::fossil::import::cvs::repository {
    # # ## ### ##### ######## #############
    ## Public API

    typemethod base {path} {
	# Could be checked, easier to defer to the overall validation.
	set mybase $path
	return
    }

    typemethod add {path} {
	# Most things cannot be checked immediately, as the base is
	# not known while projects are added. We can and do check for
	# uniqueness. We accept multiple occurences of a name, and
	# treat them as a single project.

	if {[lsearch -exact $myprojpaths $path] >= 0} return
	lappend myprojpaths $path
	return
    }

    typemethod trunkonly! {} { set mytrunkonly 1 ; return }
    typemethod trunkonly  {} { return $mytrunkonly }

    typemethod projects {} {
	return [TheProjects]
    }

    typemethod base? {} { return $mybase }

    typemethod validate {} {
	if {![IsRepositoryBase $mybase msg]} {
	    trouble fatal $msg
	    # Without a good base directory checking any projects is
	    # wasted time, so we leave now.
	    return
	}
	foreach pp $myprojpaths {
	    if {![IsProjectBase $mybase/$pp $mybase/CVSROOT msg]} {
		trouble fatal $msg
	    }
	}
	return
    }

    typemethod defauthor   {a}               { $myauthor put $a }
    typemethod defcmessage {cm}              { $mycmsg   put $cm }
    typemethod defsymbol   {pid name}        { $mysymbol put [list $pid $name] }
    typemethod defmeta     {pid bid aid cid} { $mymeta   put [list $pid $bid $aid $cid] }

    typemethod commitmessageof {mid} {
	struct::list assign [$mymeta keyof $mid] pid bid aid cid
	return [$mycmsg keyof $cid]
    }

    typemethod getmeta {mid} {
	struct::list assign [$mymeta keyof $mid] pid bid aid cid
	return [list \
		    $myprojmap($pid) \
		    [$mysymbol keyof $bid] \
		    [$myauthor keyof $aid] \
		    [$mycmsg   keyof $cid]]
    }

    # pass I results
    typemethod printstatistics {} {
	set prlist [TheProjects]
	set npr [llength $prlist]

	log write 2 repository "Statistics: Scanned [nsp $npr project]"

	if {$npr > 1} {
	    set  bmax [max [struct::list map $prlist [myproc .BaseLength]]]
	    incr bmax 2
	    set  bfmt %-${bmax}s

	    set  nmax [max [struct::list map $prlist [myproc .NFileLength]]]
	    set  nfmt %${nmax}s
	} else {
	    set bfmt %s
	    set nfmt %s
	}

	set keep {}
	foreach p $prlist {
	    set nfiles [llength [$p filenames]]
	    set line "Statistics: Project [format $bfmt \"[$p printbase]\"] : [format $nfmt $nfiles] [sp $nfiles file]"
	    if {$nfiles < 1} {
		append line ", dropped"
	    } else {
		lappend keep $p
	    }
	    log write 2 repository $line
	}

	if {![llength $keep]} {
	    trouble warn "Dropped all projects"
	} elseif {$npr == [llength $keep]} {
	    log write 2 repository "Keeping all projects"
	} else {
	    log write 2 repository "Keeping [nsp [llength $keep] project]"
	    trouble warn "Dropped [nsp [expr {$npr - [llength $keep]}] {empty project}]"
	}

	# Keep reduced set of projects.
	set projects $keep
	return
    }

    # pass I persistence
    typemethod persist {} {
	::variable myprojmap
	state transaction {
	    foreach p [TheProjects] {
		$p persist
		set myprojmap([$p id]) $p
	    }
	}
	return
    }

    typemethod load {} {
	state transaction {
	    state foreachrow {
		SELECT pid, name FROM project ;
	    } {
		set project [project %AUTO% $name $type]

		lappend myprojpaths $name
		lappend myprojects  $project
		set myprojmap($pid) $project
		$project setid $pid
	    }
	    state foreachrow {
		SELECT fid, pid, name, visible, exec FROM file ;
	    } {
		$myprojmap($pid) addfile $name $visible $exec $fid
	    }
	}
	return
    }

    # pass II results
    typemethod printrevstatistics {} {
	log write 2 repository "Revision statistics"
	# number of revisions, symbols, repository wide, and per project ...

	set rcount [state one { SELECT COUNT (*) FROM revision }]
	set tcount [state one { SELECT COUNT (*) FROM tag      }]
	set bcount [state one { SELECT COUNT (*) FROM branch   }]
	set scount [state one { SELECT COUNT (*) FROM symbol   }]
	set acount [state one { SELECT COUNT (*) FROM author   }]
	set ccount [state one { SELECT COUNT (*) FROM cmessage }]
	set fmt %[string length [max [list $rcount $tcount $bcount $scount $acount $ccount]]]s

	log write 2 repository "Statistics: [format $fmt $rcount] [sp $rcount revision]"
	log write 2 repository "Statistics: [format $fmt $tcount] [sp $tcount tag]"
	log write 2 repository "Statistics: [format $fmt $bcount] [sp $bcount branch branches]"
	log write 2 repository "Statistics: [format $fmt $scount] [sp $scount symbol]"
	log write 2 repository "Statistics: [format $fmt $acount] [sp $acount author]"
	log write 2 repository "Statistics: [format $fmt $ccount] [sp $ccount {log message}]"

	set prlist [TheProjects]
	set npr [llength $prlist]

	if {$npr > 1} {
	    set  bmax [max [struct::list map $prlist [myproc .BaseLength]]]
	    incr bmax 2
	    set  bfmt %-${bmax}s
	} else {
	    set bfmt %s
	}

	foreach p $prlist {
	    set pid [$p id]
	    set prefix "Project [format $bfmt \"[$p printbase]\"]"
	    regsub -all {[^	]} $prefix { } blanks
	    set sep " : "

	    set rcount [state one { SELECT COUNT (*) FROM revision R, file F WHERE R.fid = F.fid AND F.pid = $pid }]
	    set tcount [state one { SELECT COUNT (*) FROM tag T,      file F WHERE T.fid = F.fid AND F.pid = $pid }]
	    set bcount [state one { SELECT COUNT (*) FROM branch B,   file F WHERE B.fid = F.fid AND F.pid = $pid }]
	    set scount [state one { SELECT COUNT (*) FROM symbol             WHERE pid = $pid                     }]
	    set acount [state one { SELECT COUNT (*) FROM author   WHERE aid IN (SELECT DISTINCT aid FROM meta WHERE pid = $pid) }]
	    set ccount [state one { SELECT COUNT (*) FROM cmessage WHERE cid IN (SELECT DISTINCT cid FROM meta WHERE pid = $pid) }]

	    log write 2 repository "Statistics: $prefix$sep[format $fmt $rcount] [sp $rcount revision]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $tcount] [sp $tcount tag]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $bcount] [sp $bcount branch branches]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $scount] [sp $scount symbol]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $acount] [sp $acount author]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $ccount] [sp $ccount {log message}]"
	}
	return
    }

    # pass II persistence
    typemethod persistrev {} {
	state transaction {
	    SaveAuthors
	    SaveCommitMessages
	    # TODO: Save symbols of all projects (before the revisions
	    # in the projects, as they are referenced by the meta
	    # tuples)
	    SaveMeta
	    foreach p [TheProjects] { $p persistrev }
	}
	return
    }

    typemethod loadsymbols {} {
	state transaction {
	    # We load the symbol ids at large to have the mapping
	    # right from the beginning.

	    state foreachrow {
		SELECT sid, pid, name, tag_count AS tc, branch_count AS bc, commit_count AS cc
		FROM symbol
	    } {
		$mysymbol map $sid [list $pid $name]
		set project $myprojmap($pid)

		set force  [$project hassymbol $name]
		set symbol [$project getsymbol $name]

		# Forcing happens only for the trunks.
		if {$force} { $symbol forceid $sid }

		# Set the loaded counts.
		$symbol defcounts $tc $bc $cc

		# Note: The type is neither retrieved nor set, for
		# this is used to load the pass II data, which means
		# that everything is 'undefined' at this point anyway.

		# future: $symbol load (blockers, and parents)
	    }

	    # Beyond the symbols we also load the author, commit log,
	    # and meta information.

	    state foreachrow {
		SELECT aid, name AS aname FROM author
	    } {
		$myauthor map $aid $aname
	    }
	    state foreachrow {
		SELECT cid, text FROM cmessage
	    } {
		$mycmsg map $cid $text
	    }
	    state foreachrow {
		SELECT mid, pid, bid, aid, cid FROM meta
	    } {
		$mymeta map $mid [list $pid $bid $aid $cid]
	    }
	}
	return
    }

    typemethod determinesymboltypes {} {
	foreach project [TheProjects] {
	    $project determinesymboltypes
	}
	return
    }

    typemethod projectof {pid} {
	return $myprojmap($pid)
    }


    # pass IV+ results
    typemethod printcsetstatistics {} {
	log write 2 repository "Changeset statistics"
	# number of revisions, symbols, repository wide, and per project ...

	set ccount [state one { SELECT COUNT (*) FROM changeset                }]
	set rcount [state one { SELECT COUNT (*) FROM changeset WHERE type = 0 }]
	set tcount [state one { SELECT COUNT (*) FROM changeset WHERE type = 1 }]
	set bcount [state one { SELECT COUNT (*) FROM changeset WHERE type = 2 }]
	set fmt %[string length [max [list $ccount $rcount $tcount $bcount]]]s

	log write 2 repository "Statistics: [format $fmt $ccount] [sp $ccount changeset]"
	log write 2 repository "Statistics: [format $fmt $rcount] [sp $rcount {revision changeset}]"
	log write 2 repository "Statistics: [format $fmt $tcount] [sp $tcount {tag symbol changeset}]"
	log write 2 repository "Statistics: [format $fmt $bcount] [sp $bcount {branch symbol changeset}]"

	set prlist [TheProjects]
	set npr [llength $prlist]

	if {$npr > 1} {
	    set  bmax [max [struct::list map $prlist [myproc .BaseLength]]]
	    incr bmax 2
	    set  bfmt %-${bmax}s
	} else {
	    set bfmt %s
	}

	foreach p $prlist {
	    set pid [$p id]
	    set prefix "Project [format $bfmt \"[$p printbase]\"]"
	    regsub -all {[^	]} $prefix { } blanks
	    set sep " : "

	    set ccount [state one { SELECT COUNT (*) FROM changeset WHERE pid = $pid              }]
	    set rcount [state one { SELECT COUNT (*) FROM changeset WHERE pid = $pid AND type = 0 }]
	    set tcount [state one { SELECT COUNT (*) FROM changeset WHERE pid = $pid AND type = 1 }]
	    set bcount [state one { SELECT COUNT (*) FROM changeset WHERE pid = $pid AND type = 2 }]

	    log write 2 repository "Statistics: $prefix$sep[format $fmt $ccount] [sp $ccount changeset]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $rcount] [sp $rcount {revision changeset}]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $tcount] [sp $tcount {tag symbol changeset}]"
	    log write 2 repository "Statistics: $blanks$sep[format $fmt $bcount] [sp $bcount {branch symbol changeset}]"
	}
	return
    }

    # # ## ### ##### ######## #############
    ## State

    typevariable mybase           {} ; # Base path to CVS repository.
    typevariable myprojpaths      {} ; # List of paths to all declared
				       # projects, relative to mybase.
    typevariable myprojects       {} ; # List of objects for all
				       # declared projects.
    typevariable myprojmap -array {} ; # Map from project ids to their
				       # objects.
    typevariable myauthor         {} ; # Names of all authors found,
				       # maps to their ids.
    typevariable mycmsg           {} ; # All commit messages found,
				       # maps to their ids.
    typevariable mymeta           {} ; # Maps all meta data tuples
				       # (project, branch, author,
				       # cmessage) to their ids.
    typevariable mysymbol         {} ; # Map symbols identified by
				       # project and name to their
				       # id. This information is not
				       # saved directly.
    typevariable mytrunkonly      0  ; # Boolean flag. Set by option
				       # processing when the user
				       # requested a trunk-only import

    # # ## ### ##### ######## #############
    ## Internal methods

    typeconstructor {
	set myauthor [vc::tools::id %AUTO%]
	set mycmsg   [vc::tools::id %AUTO%]
	set mymeta   [vc::tools::id %AUTO%]
	set mysymbol [vc::tools::id %AUTO%]
	return
    }

    proc .BaseLength {p} {
	return [string length [$p printbase]]
    }

    proc .NFileLength {p} {
	return [string length [llength [$p filenames]]]
    }

    proc IsRepositoryBase {path mv} {
	::variable mybase
	upvar 1 $mv msg
	if {![fileutil::test $mybase         edr msg {CVS Repository}]}      {return 0}
	if {![fileutil::test $mybase/CVSROOT edr msg {CVS Admin Directory}]} {return 0}
	return 1
    }

    proc IsProjectBase {path admin mv} {
	upvar 1 $mv msg
	if {![fileutil::test $path edr msg Project]} {return 0}
	if {
	    ($path eq $admin) ||
	    [string match $admin/* $path]
	} {
	    set msg "Administrative subdirectory $path cannot be a project"
	    return 0
	}
	return 1
    }

    proc TheProjects {} {
	upvar 1 type type
	::variable myprojects
	::variable myprojpaths

	if {![llength $myprojects]} {
	    set myprojects [EmptyProjects $myprojpaths]
	}
	return $myprojects
    }

    proc EmptyProjects {projpaths} {
	::variable mybase
	upvar 1 type type
	set res {}
	if {[llength $projpaths]} {
	    foreach pp $projpaths {
		lappend res [project %AUTO% $pp $type]
	    }
	} else {
	    # Base is the single project.
	    lappend res [project %AUTO% "" $type]
	}
	return $res
    }

    proc SaveAuthors {} {
	::variable myauthor
	foreach {name aid} [$myauthor get] {
	    state run {
		INSERT INTO author ( aid,  name)
		VALUES             ($aid, $name);
	    }
	}
	return
    }

    proc SaveCommitMessages {} {
	::variable mycmsg
	foreach {text cid} [$mycmsg get] {
	    state run {
		INSERT INTO cmessage ( cid,  text)
		VALUES               ($cid, $text);
	    }
	}
	return
    }

    proc SaveMeta {} {
	::variable mymeta
	foreach {key mid} [$mymeta get] {
	    struct::list assign $key pid bid aid cid
	    state run {
		INSERT INTO meta ( mid,  pid,  bid,  aid,  cid)
		VALUES           ($mid, $pid, $bid, $aid, $cid);
	    }
	}
	return
    }

    # # ## ### ##### ######## #############
    ## Configuration

    pragma -hasinstances   no ; # singleton
    pragma -hastypeinfo    no ; # no introspection
    pragma -hastypedestroy no ; # immortal

    # # ## ### ##### ######## #############
}

namespace eval ::vc::fossil::import::cvs {
    namespace export repository
    namespace eval repository {
	namespace import ::vc::fossil::import::cvs::project
	namespace import ::vc::fossil::import::cvs::state
	namespace import ::vc::tools::misc::*
	namespace import ::vc::tools::id
	namespace import ::vc::tools::trouble
	namespace import ::vc::tools::log
	log register repository
    }
}

# # ## ### ##### ######## ############# #####################
## Ready
return