Check-in Differences
Not logged in
Bounty program for improvements to Tcl and certain Tcl packages.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From 00532db4911111ae To 5c2d6c24ac6f1a49

2022-07-01
15:31
Tweaked the formatting of avg update times (show window size, elements going into the result) Bugfix: Normalize the project name before putting it into lists. Flatten to single line, remove superfluous whitespace. Look at the description of https://github.com/luaforge/tclua. The multi-line broke MD formatting of the table) Added run time stamps to site generator (only at the major places) check-in: b6d8811f29 user: work tags: vcs-plugged
14:21
1. db migration, main and site 1. mirror set renamed to project. 1. updated cmdr spec, most commands 1. several commands switch to take repos instead of projects 1. completed the conversion of common vcs to call out to the backoffice code 1. reworked repo, store, vcs handling for the new schema 1. iterated over commands to check for issues and fix them 1. previous not complete, see merge, split, web pages 1. even so, the majority of the functionality looks to be working again 1. especially add/update of repos, i.e. the core. 1. and yet, still more testing needed to validate handling of issues with network and/or repo servers. 1. issues should be more localised however, as access (especially to github) should now use smaller requests overall 1. website, touch up of generated pages (lists, store details, new list of forks for > 5 forks of a repo) 1. see TODO.md for general things outside of fixes check-in: 5c2d6c24ac user: work tags: vcs-plugged
2022-06-28
15:19
Fix v2/v3 repository migration check-in: 0f1fc429ce user: work tags: vcs-plugged
14:06
Fix eols in scratch area check-in: e0637d5770 user: work tags: vcs-plugged
14:04
Blind work in vcs layer have more commands call on the backoffice code. Untested. Likely broken. check-in: 00532db491 user: work tags: vcs-plugged
2019-10-30
17:30
`m vcs *`: - Converted `url-to-name` (was `name-from-url`) - Converted `update`. - Removed `revs`, `remotes`. - Still kept: `detect`. - Added tests for the converted commands. check-in: ce42f93675 user: aku tags: vcs-plugged

Added TODO.md.














































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

---

1. Check and fix merge
1. Check and fix split
1. Website - want more pages - statistics, project index/details
1. Test add/update handling of network/server issues, i.e. disconnects, timeouts, ...

---

1. merge - logic still based on projects -- rework to place repos at center
1. projects - list projects
1. projects - show project details
1. need stats command - summary of projects, repos, stores
1. repos - need commands to move repos between projects
1. repos - need commands to remove projects

   note: currently auto-removing a project when last repo removed
     - extend this to moves ?

1. note! forks and their origin can all be in different projects
1. new list - show only primary repos, exclude forks
1. log information saved at stores - should be at repos

     - issues happen when acessing a repo
     
   alternate: keep with store (on disk), but separate logs per repo.

1. refresh stores ? - old github stores can be big, containing shared data from all forks ... kill and recreate store to reduce it ?

   better:
     - rename old github repo projects, then re-add => new stores
     - disable old stores - hide from web indices ? (another repo flag)
     - more disk space
     - but keeps old state until new setup has initialized

     - Save old database, and pull the `store_github_forks` data for assessment of repos and their forks => handle the small ones first

1. export command (`dump`?) to save a store with associated metadata to external directory

   => save old github stores before removing from management

1. more various internal commands between packages - support for various list commands should be in repo now, instead of store

1. mirror config store - changes - broken

Changes to _scratch_/gh-org-pull.

1
2
3

4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
11



+







#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################
## Syntax: $0 user-or-org ?name?

# Pull a list of github repositories for a person or org.
# Resolve forked repositories to the main repository.
# Print the result as a mirror import spec.

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

Changes to _scratch_/git-tag-check.

1
2
3

4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
11



+







#!/usr/bin/env tclsh
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################
## Syntax: $0 gitdir

package require Tcl 8.6
package require m::futil

proc main {} {
    global argv

Changes to _scratch_/v3-exec.tcl.

Changes to _scratch_/vcs-api-3-schema.md.

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
27
28
29
30
31
32
33

34
35
36
37
38
39
40







-








### Failure

Flag for attend.

Note: Need a command to re-parent|elect-new-primary
      Query github to determine parent of a repo.


## Magic remote

### Success

Store is updated, nothing more.

Added doc/schema-v2-issues.md.





































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

# V2 Schema Issues

  1. Nit: `Mirror set` is not a good name for the concept. `Project` is better.

  2. The `Mirror set`, `Repository`, and `Store` relations are not
     nicely modeled in the tables. Using the indirect coupling of the
     last through the `Mirror set`/`VCS` combination is messy.

     The relation that a `repository` __has a_ (backing) `store` (1:n)
     is the core, and not modeled.

  3. The various fields of the `store_times` table belong to `store`,
     and `repository`. It is unclear and not remembered anymore why an
     adjunct table was added to the system, instead of the entity
     properly extended.

  4. Nit: The `store_times.attend` field should be named `has_issues`.

  5. Do we truly need current and previous values for size and commits ?

  6. Table `store_github_forks` is IMHO superfluous with the ideas
     around changing the fork handling currently hidden in the github
     driver. See next point.

  7. The github driver, with its complex internal handling of forks in
     a single store is messy and fragile. The fragility is mainly
     around the handling of tags per fork, and having to
     add/remove/rename/readd origins.

     The other issue is that various git/hub operations are linear in
     the number of forks. This becomes problematic above a 100 or so forks.

     It definitely prevents using some kind of timeout to break out of
     stuck processes. It cannot be decided if the process is truly
     stuck, or simply crawling through the pile of forks.

Added doc/schema-v2.md.
















































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# V2 - Mirroring. Backing up Sources

As of 2019-10-03T21:20 (`lib/db/db.tcl`, `SETUP-201910032120` and all preceding).

Streamlined v1 to basics.

  - Removed VCS operation hooks.
    VCS support is fixed, no plugin system.
    Near-plugin system through extensible set of VCS specific packages

  - Removed tags. Not needed for an initial setup.

## Entities

### Overview

|Table                  |Description                                            |
|---                    |---                                                    |
|mirror_set             |Group of repositories, same logical project            |
|mset_pending           |Projects waiting for update in current cycle           |
|rejected               |Rejected repositories, for use by future submissions   |
|reply                  |Mail replies for submission handling                   |
|repository             |A set of versioned files to back up                    |
|rolodex                |Shorthand repository references, recently seen         |
|state                  |Global system state and config                         |
|store                  |Local holder of a repository backup                    |
|store_github_forks     |Number of forks per github store                       |
|store_times            |Cycle information per store                            |
|submission             |Submitted repositories waiting for handling            |
|submission_handled     |Handled submissions for next sync with site database   |
|version_control_system |Information about supported VCS                        |

### Examples

|Entity                 |Example                                                        |
|---                    |---                                                            |
|Repository             |                                                               |
|                       |github@github:andreas.kupries/marpa                            |
|                       |https://chiselapp.com/user/andreas-kupries/repository/marpa    |
|                       |https://core.tcl-lang.org/akupries/marpa                       |
|Mirror Set             |                                                               |
|                       |Tcl Marpa                                                      |
|Version Control System |                                                               |
|                       |bazaar                                                         |
|                       |cvs                                                            |
|                       |fossil (__+__)                                                 |
|                       |git, github (__+__)                                            |
|                       |mercurial (hg) (__+__)                                         |
|                       |monotone                                                       |
|                       |rcs                                                            |
|                       |sccs                                                           |
|                       |svn (__+__)                                                    |

### Core Relations

  1. A `repository` __is managed by a__ `version control system` (n:1)
  1. A `repository` __belongs to a__ `mirror set` (n:1)
  1. A `store` __is managed by a__ `version control system` (n:1)
  1. A `store` __belongs to a__ `mirror set` (n:1)
  1. A `repository` __has a_ (backing) `store` (1:n)

The above as a diagram, with some of the adjunct tables added, and the last relation __not__ shown.

```
 Mset Pending ----\
                   \-> /--> Mirror Set <--------------\
 Rolodex --> Repository                                Store <-- Store Times
                       \--> Version Control System <--/    \ <-- Store Github Forks

```

### Entity Attributes

|Entity                 |Field                  |Type   |Modifiers              |Comments       |
|---                    |---                    |---    |---                    |---            |
|schema                 |                       |       |                       |               |
|                       |key                    |text   |PK                     |fix: `version` |
|                       |version                |int    |                       |               |
|~                      |~                      |~      |~                      |~              |
|mirror_set             |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |name                   |text   |unique                 |               |
|mset_pending           |                       |       |                       |               |
|                       |id                     |int    |PK, FK mirror_set      |               |
|rejected               |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |url                    |text   |unique                 |               |
|                       |reason                 |text   |                       |               |
|reply                  |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |name                   |text   |unique                 |               |
|                       |automail               |bool   |       |Send mail by default           |
|                       |isdefault              |bool   |       |Use when no reason given       |
|                       |text                   |text   |                       |               |
|repository             |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |url                    |text   |unique                 |Loction        |
|                       |vcs                    |int    |FK version_control_... | __index 1__   |
|                       |mset                   |int    |FK mirror_set          | __index 1__   |
|                       |active                 |bool   |                       |               |
|rolodex                |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |repository             |int    |unique, FK repository  |               |
|state                  |                       |       |                       |               |
|                       |name                   |text   |PK                     |               |
|                       |value                  |text   |                       |               |
|store                  |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |vcs                    |int    |FK version_control_... | __unique 1__  |
|                       |mset                   |int    |FK mirror_set          | __unique 1__  |
|                       |size_kb                |int    |                       |Kilobyte       |
|                       |size_previous          |int    |                       |ditto          |
|                       |commits_current        |int    |                       |               |
|                       |commits_previous       |int    |                       |               |
|store_github_forks     |                       |       |                       |               |
|                       |store                  |int    |PK, FK store           |               |
|                       |nforks                 |int    |                       |               |
|store_times            |                       |       |                       |               |
|                       |store                  |int    |PK, FK store           |               |
|                       |created                |int    |                       |Epoch          |
|                       |updated                |int    |                       |Epoch          |
|                       |changed                |int    |                       |Epoch          |
|                       |attend                 |bool   |                       |Has issues     |
|                       |min_seconds            |int    |                       |               |
|                       |max_seconds            |int    |                       |               |
|                       |window_seconds         |text   |                       |CSV, last N    |
|submission             |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |session                |text   | __unique 1__          |               |
|                       |url                    |text   | __unique 1__          | index 1       |
|                       |vcode                  |text   |nullable               | VCS.code      |
|                       |description            |text   |nullable               |               |
|                       |email                  |text   |                       |subm. email    |
|                       |submitter              |text   |nullable               |subm. name     |
|                       |sdate                  |int    |                       |epoch, index 2 |
|submission_handled     |                       |       |                       |               |
|                       |session                |text   | __unique 1__          |               |
|                       |url                    |text   | __unique 1__          |               |
|version_control_system |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |code                   |text   |unique                 |Short tag      |
|                       |name                   |text   |unique                 |Human Readable |

## State keys and semantics

|Key                            |Meaning                                |
|---                            |---                                    |
|limit                          |State, #repos per `list` page          |
|start-of-current-cycle         |State, epoch when update cycle started |
|start-of-previous-cycle        |State, epoch, previous update cycle    |
|store                          |State, path to stores on disk          |
|store-window-size              |State, #of update durations to retain  |
|take                           |State, #mirror sets to update per run  |
|top                            |State, repo shown at top of `list`     |
|~                              |~                                      |
|mail-debug                     |Mail transport, debug flag             |
|mail-host                      |Mail transport, smtpd host             |
|mail-pass                      |Mail transport, smtp password          |
|mail-port                      |Mail transport, smtpd port             |
|mail-sender                    |Mail transport, smtp sender            |
|mail-tls                       |Mail transport, tls flag               |
|mail-user                      |Mail transport, smtp user              |
|~                              |~                                      |
|mail-footer                    |Mail config, footer text               |
|mail-header                    |Mail config, header text               |
|mail-width                     |Mail config, table width limit         |
|report-mail-destination        |Mail config, destination               |
|~                              |~                                      |
|site-active                    |Site config, flag of use               |
|site-logo                      |Site config, url to logo               |
|site-mgr-mail                  |Site config, email of manager          |
|site-mgr-name                  |Site config, name of manager           |
|site-store                     |Site config, path to site on disk      |
|site-title                     |Site config, general title             |
|site-url                       |Site config, url of site               |

Added doc/schema-v3.md.

















































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# V3 - Mirroring. Backing up Sources

In planning, to address the [issues of V2](schema-v2-issues.md)

## Changes

  1. Renamed `mirror_set` to `project`.
  1. Renamed `mset_pending` to `repo_pending`.
  1. Moved `store_times.created` to `store`.
  1. Moved `store_times.updated` to `store`.
  1. Moved `store_times.changed` to `store`.
  1. Moved `store_times.attend` to `repository.has_issues`.
  1. Moved `store_times.min_seconds` to `repository.min_duration`.
  1. Moved `store_times.max_seconds` to `repository.max_duration`.
  1. Moved `store_times.window_seconds` to `repository`.
  1. Dropped table `store_times`.
  1. Dropped table `store_github_forks`.
  1. Redid the core relations.
  1. Made the fork handling explicit in the schema.
  1. Removed `store.mset`.
  1. Renamed `repository.mset` to `repository.project`.
  1. Added `repository.store`.
  1. Added `repository.checked`.

Redesign of the fork handling:

  1. A VCS driver may report forks on `setup` and `update` operations.

  1. The reported forks are automatically added to the project of the
     primary (aka fork_origin), as their own repositories, and activated.

     Unreported known forks are deactivated. __Not__ removed.

  1. Forks get their own store. While this blows up the disk space
     needed to handle the project it also makes handling much easier,
     as there is no need to fiddle with git(hub) tags and origins.

     If desired it is always possible to manually merge stores. Not
     recommended for git.

## Entities

### Overview

|Table                  |Description                                            |
|---                    |---                                                    |
|project                |Group of repositories, same logical project            |
|rejected               |Rejected repositories, for use by future submissions   |
|reply                  |Mail replies for submission handling                   |
|repo_pending           |Repositories waiting for update in current cycle       |
|repository             |A set of versioned files to back up                    |
|rolodex                |Shorthand repository references, recently seen         |
|state                  |Global system state and config                         |
|store                  |Local holder of a repository backup                    |
|submission             |Submitted repositories waiting for handling            |
|submission_handled     |Handled submissions for next sync with site database   |
|version_control_system |Information about supported VCS                        |

### Examples

|Entity                 |Example                                                        |
|---                    |---                                                            |
|Repository             |                                                               |
|                       |github@github:andreas.kupries/marpa                            |
|                       |https://chiselapp.com/user/andreas-kupries/repository/marpa    |
|                       |https://core.tcl-lang.org/akupries/marpa                       |
|Project                |                                                               |
|                       |Tcl Marpa                                                      |
|Version Control System |                                                               |
|                       |bazaar                                                         |
|                       |cvs                                                            |
|                       |fossil (__+__)                                                 |
|                       |git, github (__+__)                                            |
|                       |mercurial (hg) (__+__)                                         |
|                       |monotone                                                       |
|                       |rcs                                                            |
|                       |sccs                                                           |
|                       |svn (__+__)                                                    |

### Core Relations

  1. A `project` __has__ zero or more `repositories` (1:n).
  1. __(x)__ A `repository` __belongs to__ a single `project` (n:1).
  1. __(x)__ A `repository` __is managed by__ a single `version control system` (n:1).
  1. A `version control system` __manages__ zero or more `repositories` (1:n).
  1. __(x)__ A `repository` __has_ a single (backing) `store` (1:n).
  1. A `store` __contains the data__ of one or more __repositories (1:n).
  1. __(x)__ A `store` __is managed by__ a single `version control system` (n:1).
  1. A `version control system` __manages__ zero or more `stores` (1:n).
  1. __(x)__ A `repository` may __have__ a parent `repository` it is forked from (n:1).
  1. A `repository` __has__ zero or more forked `repositories` (1:n).

A checking contraint:

  1. A `repository` and its backing `store` are managed by the same `version control system`.

     IOW `repository.store.vcs == repository.vcs`.

Below we see the above as diagram, with the relations marked __(x)__
as the shown arrows / foreign key references, and some adjunct tables added.

```
rolodex ------>\
repo_pending -->\
     project <-- repository ------------------->\
                           \--> store ---------> version_control_system
```

### Entity Attributes

|Entity                 |Field                  |Type   |Modifiers              |Comments       |
|---                    |---                    |---    |---                    |---            |
|schema                 |                       |       |                       |               |
|                       |key                    |text   |PK                     |fix: `version` |
|                       |version                |int    |                       |               |
|~                      |~                      |~      |~                      |~              |
|__Main Database__      |                       |       |                       |               |
|~                      |~                      |~      |~                      |~              |
|project                |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |name                   |text   |unique                 |               |
|repo_pending           |                       |       |                       |               |
|                       |id                     |int    |PK, FK repository      |               |
|rejected               |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |url                    |text   |unique                 |               |
|                       |reason                 |text   |                       |               |
|reply                  |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |name                   |text   |unique                 |               |
|                       |automail               |bool   |       |Send mail by default           |
|                       |isdefault              |bool   |       |Use when no reason given       |
|                       |text                   |text   |                       |               |
|repository             |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |url                    |text   |unique                 |Location       |
|                       |project                |int    |FK project             | __index 1__   |
|                       |vcs                    |int    |FK version_control_... | __index 1__   |
|                       |store                  |int    |FK store               | __index 2__   |
|                       |fork_origin            |int    |FK repository, nullable| __index 3__   |
|                       |is_active              |bool   |                       |               |
|                       |has_issues             |bool   |                       |Has issues     |
|                       |min_duration           |int    |                       |               |
|                       |max_duration           |int    |                       |               |
|                       |window_duration        |text   |                       |CSV, last N    |
|                       |checked                |int    |                       |epoch          |
|rolodex                |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |repository             |int    |unique, FK repository  |               |
|state                  |                       |       |                       |               |
|                       |name                   |text   |PK                     |               |
|                       |value                  |text   |                       |               |
|store                  |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |vcs                    |int    |FK version_control_... |               |
|                       |size_kb                |int    |                       |Kilobyte       |
|                       |size_previous          |int    |                       |ditto          |
|                       |commits_current        |int    |                       |               |
|                       |commits_previous       |int    |                       |               |
|                       |created                |int    |                       |Epoch          |
|                       |updated                |int    |                       |Epoch          |
|                       |changed                |int    |                       |Epoch          |
|submission             |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |session                |text   | __unique 1__          |               |
|                       |url                    |text   | __unique 1__          | index 1       |
|                       |vcode                  |text   |nullable               | VCS.code      |
|                       |description            |text   |nullable               |               |
|                       |email                  |text   |                       |subm. email    |
|                       |submitter              |text   |nullable               |subm. name     |
|                       |sdate                  |int    |                       |epoch, index 2 |
|submission_handled     |                       |       |                       |               |
|                       |session                |text   | __unique 1__          |               |
|                       |url                    |text   | __unique 1__          |               |
|version_control_system |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |code                   |text   |unique                 |Short tag      |
|                       |name                   |text   |unique                 |Human Readable |
|~                      |~                      |~      |~                      |~              |
|__Site Database__      |                       |       |                       |               |
|~                      |~                      |~      |~                      |~              |
|cache_desc             |                       |       |                       |               |
|                       |expiry                 |int    |                       |epoch          |
|                       |url                    |text   |unique                 |               |
|                       |desc                   |text   |                       |               |
|cache_url              |                       |       |                       |               |
|                       |expiry                 |int    |                       |epoch          |
|                       |url                    |text   |unique                 |               |
|                       |ok                     |int    |                       |               |
|                       |resolved               |text   |                       |               |
|rejected               |                       |       |                       |               |
|                       |main.rejected          |       |                       |               |
|store_index            |                       |       |                       |               |
|                       |id                     |int    |PK                     |               |
|                       |name                   |text   | __unique 1__          |index 1        |
|                       |vcode                  |text   | __unique 1__          |               |
|                       |page                   |text   |                       |               |
|                       |remotes                |text   |                       |index 2        |
|                       |status                 |text   |                       |               |
|                       |size_kb                |int    |                       |               |
|                       |changed                |int    |                       |epoch          |
|                       |updated                |int    |                       |epoch          |
|                       |created                |int    |                       |epoch          |
|submission             |                       |       |                       |               |
|                       |main.submission        |       |                       |               |
|vcs                    |                       |       |                       |               |
|                       |main.version_control_system|   |                       |               |

## State keys and semantics

|Key                            |Meaning                                |
|---                            |---                                    |
|limit                          |State, #repos per `list` page          |
|start-of-current-cycle         |State, epoch when update cycle started |
|start-of-previous-cycle        |State, epoch, previous update cycle    |
|store                          |State, path to stores on disk          |
|store-window-size              |State, #of update durations to retain  |
|take                           |State, #repositories to update per run |
|top                            |State, repo shown at top of `list`     |
|~                              |~                                      |
|mail-debug                     |Mail transport, debug flag             |
|mail-host                      |Mail transport, smtpd host             |
|mail-pass                      |Mail transport, smtp password          |
|mail-port                      |Mail transport, smtpd port             |
|mail-sender                    |Mail transport, smtp sender            |
|mail-tls                       |Mail transport, tls flag               |
|mail-user                      |Mail transport, smtp user              |
|~                              |~                                      |
|mail-footer                    |Mail config, footer text               |
|mail-header                    |Mail config, header text               |
|mail-width                     |Mail config, table width limit         |
|report-mail-destination        |Mail config, destination               |
|~                              |~                                      |
|site-active                    |Site config, flag of use               |
|site-logo                      |Site config, url to logo               |
|site-mgr-mail                  |Site config, email of manager          |
|site-mgr-name                  |Site config, name of manager           |
|site-store                     |Site config, path to site on disk      |
|site-title                     |Site config, general title             |
|site-url                       |Site config, url of site               |

Deleted doc/schema.md.

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























































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
# V2 - Mirroring. Backing up Sources

Streamlined v1 to basics.

   - Removed VCS operation hooks.
     VCS support is coded fixed, no plugin system.

   - Removed tags. Not needed for an initial setup.

## Entities

   1. Repository.
      Examples:

      - https://core.tcl-lang.org/akupries/marpa/
      - github@github:andreas.kupries/marpa
      -	https://chiselapp.com/user/andreas-kupries/repository/marpa/

      A versioned collection of files.

   1. Mirror Set.
      Example:

      - First item.

      A group of repositories holding the same versioned collection of
      files.

   1. Version Control System (VCS).
      Examples:
      - git
      - fossil
      - bazaar
      - mercurial
      - monotone
      - svn
      - cvs
      - rcs
      - sccs

      An application (or collection thereof) to manage a repository.

   1. Store

      A repository, internal to the mirroring system. Each kind of VCS
      used by a sub-set of the repositories in a mirror set has an
      associated store, to hold the local mirror of the repositories
      in question.

   1. Name

      The name of a mirror set. This is an 1:1 relation.

      It is separate from the mirror set because this is also the hook
      where we can replace the link to the name with a link into a Tcl
      Package Pedia containing much more information.

## Entity Relations

```
Repository	has-a|is-managed-by-a	Version Control System
		n:1

Repository	belongs-to-a		Mirror Set
		n:1

Mirror Set	has-a			Name
		1:1

Store		has-a|is-managed-by-a	Version Control System
		n:1

Store		belongs-to-a		Mirror Set
		n:1
```

As a diagram

```
 Mset Pending ----\		     /-> Name
		   \-> /--> Mirror Set <--------------\
 Rolodex --> Repository				       Store <-- Store Times
		       \--> Version Control System <--/

```

## Entities & Attributes

### version_control_system

|Name	|Type	|Modifiers	|Comments	|
|---	|---	|---		|---		|
|id	|int	|PK		|		|
|code	|text	|unique		|Short tag	|
|name	|text	|unique		|Human Readable	|

### name

|Name	|Type	|Modifiers	|Comments	|
|---	|---	|---		|---		|
|id	|int	|PK		|		|
|name	|text	|unique		|		|

### mirror_set

|Name	|Type	|Modifiers	|Comments	|
|---	|---	|---		|---		|
|id	|int	|PK		|		|
|name	|int	|unique, FK name|1:1		|

### mset_pending

|Name	|Type	|Modifiers		|Comments	|
|---	|---	|---			|---		|
|id	|int	|PK, FK mirror_set	|		|

### repository

|Name	|Type	|Modifiers			|Comments	|
|---	|---	|---				|---		|
|id	|int	|PK				|		|
|url	|text	|unique				|Location	|
|vcs	|int	|FK version_control_system	| __index 1__	|
|mset	|int	|FK mirror_set			| __index 1__	|
|active	|bool	|				|		|

### rolodex

|Name		|Type	|Modifiers		|Comments	|
|---		|---	|---			|---		|
|id		|int	|PK			|		|
|repository	|int	|unique, FK repository	|		|

### store

|Name		|Type	|Modifiers			|Comments			|
|---		|---	|---				|---				|
|id		|int	|PK				|				|
|path		|text	|unique				|Relative to `state('store')`	|
|vcs		|int	|FK version_control_system	| __index 1__			|
|mset		|int	|FK mirror_set			| __index 1__			|
|size_kb	|int	|				|				|
|size_previous	|int	|				|				|
|commits_current|int	|				|				|
|commit_previous|int	|				|				|

### store_times

|Name		|Type	|Modifiers	|Comments			|
|---		|---	|---		|---				|
|store		|int	|PK, FK store	|				|
|created	|int	|		|epoch				|
|updated	|int	|		|epoch				|
|changed	|int	|		|epoch				|
|attend		|bool	|		|flag for issues		|
|min_seconds	|int	|		|min duration of updates	|
|max_seconds	|int	|		|max duration of update		|
|window_seconds	|text	|		|CSV line for durations		|

## Entities & Attributes around submission handling

### submission

|Name		|Type	|Modifiers	|Comments			|
|---		|---	|---		|---				|
|id		|int	|PK		|				|
|session	|text	|unique (+url)	|int. code for session ident	|
|url		|text	|unique (+sess)	|__index 1__, location		|
|vcode		|text	|nullable	|vcs				|
|description	|text	|nullable	|				|
|email		|text	|		|submitter email		|
|submitter	|text	|nullable	|				|
|sdate		|int	|		|__index 2__, epoch		|

### submission_handled

|Name	|Type	|Modifiers	|Comments	|
|---	|---	|---		|---		|
|session|text	|unique (+url)	|		|
|url	|text	|unique (+sess)	|		|

### rejected

|Name	|Type	|Modifiers	|Comments	|
|---	|---	|---		|---		|
|id	|int	|PK		|		|
|url	|text	|unique		|		|
|reason	|text	|		|		|

### reply

|Name		|Type	|Modifiers	|Comments			|
|---		|---	|---		|---				|
|id		|int	|PK		|				|
|name		|text	|unique		|				|
|automail	|bool	|		|Send mail by default when used	|
|isdefault	|bool	|		|Use this when no reason spec'd	|
|text		|text	|		|				|

## Entities & Attributes for Internal Management

### schema

|Name	|Type	|Modifiers	|Comments		|
|---	|---	|---		|---			|
|key	|text	|PK		|always 'version'	|
|version|int	|		|version number		|

### state

|Name	|Type	|Modifiers	|Comments		|
|---	|---	|---		|---			|
|name	|text	|PK		|			|
|value	|text	|		|			|

Known keys

|Key				|Meaning				|
|---				|---					|
|limit				|State, #repos per `list` page		|
|start-of-current-cycle		|State, epoch when update cycle started	|
|start-of-previous-cycle	|State, epoch, previous update cycle	|
|store				|State, path to stores on disk		|
|store-window-size		|State, #of update durations to retain	|
|take				|State, #mirror sets to update per run	|
|top				|State, repo shown at top of `list`	|
|~				|~					|
|mail-debug			|Mail transport, debug flag		|
|mail-host			|Mail transport, smtpd host		|
|mail-pass			|Mail transport, smtp password		|
|mail-port			|Mail transport, smtpd port		|
|mail-sender			|Mail transport, smtp sender		|
|mail-tls			|Mail transport, tls flag		|
|mail-user			|Mail transport, smtp user		|
|~				|~					|
|mail-width			|Mail config, table width limit		|
|mail-footer			|Mail config, footer text		|
|mail-header			|Mail config, header text		|
|report-mail-destination	|Mail config, destination		|
|~				|~					|
|site-active			|Site config, flag of use		|
|site-logo			|Site config, url to logo		|
|site-mgr-mail			|Site config, email of manager		|
|site-mgr-name			|Site config, name of manager		|
|site-store			|Site config, path to site on disk	|
|site-title			|Site config, general title		|
|site-url			|Site config, url of site		|

Changes to lib/cli/cmdr.tcl.

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







-
-
-
+
+
+

-
-
+
+



-
-
-
-
+
+
+
+








    state th {
	Terminal Height. Auto supplied to all commands.
    } { generate [lambda {args} {
	linenoise lines
    }] }

    common .optional-mirror-set {
	input mirror-set {
	    The mirror set to operate on.
    common .optional-project {
	input project {
	    The project to operate on.
	} { optional
	    validate [m::cmdr::vt mset]
	    generate [m::cmdr::call glue gen_current_mset]
	    validate [m::cmdr::vt project]
	    generate [m::cmdr::call glue gen_current_project]
	}
    }

    common .list-optional-mirror-set {
	input mirror-sets {
	    Repositories to operate on.
	} { list ; optional ; validate [m::cmdr::vt mset] }
    common .list-optional-project {
	input projects {
	    Projects to operate on.
	} { list ; optional ; validate [m::cmdr::vt project] }
    }

    common .optional-repository {
	input repository {
	    Repository to operate on.
	} { optional
	    validate [m::cmdr::vt repository]
281
282
283
284
285
286
287
288

289
290
291
292

293
294
295
296
297
298
299
281
282
283
284
285
286
287

288
289
290
291

292
293
294
295
296
297
298
299







-
+



-
+







	    input path {
		New location of the store
	    } { optional ; validate rwpath }
	} [m::cmdr::call glue cmd_store]

	private take {
	    description {
		Query/change the number of mirror sets processed per
		Query/change the number of repositories processed per
		update cycle.
	    }
	    input take {
		New number of mirror sets to process in one update.
		New number of projects to process in one update.
	    } { optional ; validate cmdr::validate::posint }
	} [m::cmdr::call glue cmd_take]

	private report {
	    description {
		Query/change the email address to send reports to.
	    }
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
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







-
+



+
+
+
















-
+










-
-
+
+



-
+

-
+






-
-
+
+

-
+

-
+

-
+




-
+





-
-
-
-
+
+
+
+


















-
+







-
-
-
-
-
-
+
+
+
+
+
+


-
+







	use .optional-repository
    } [m::cmdr::call glue cmd_remove]

    private add {
	use .cms
	description {
	    Add repository. The new repository is placed into its own
	    mirror set. Command tries to auto-detect vcs type if not
	    project. Command tries to auto-detect vcs type if not
	    specified. Command derives a name from the url if not
	    specified. New repository becomes current.
	}
	option track-forks {
	    Force tracking when seeing a large number of forks.
	} { presence }
	option vcs {
	    Version control system handling the repository.
	} {
	    validate [m::cmdr::vt vcs]
	    generate [m::cmdr::call glue gen_vcs]
	}
	state vcs-code {
	    Version control system handling the repository.
	    Internal code, derived from the option value (database id).
	} {
	    generate [m::cmdr::call glue gen_vcs_code]
	}
	input url {
	    Location of the repository to add.
	} { validate [m::cmdr::vt url] }
	option name {
	    Name for the mirror set to hold the repository.
	    Name for the project to hold the repository.
	} {
	    alias N
	    validate str
	    generate [m::cmdr::call glue gen_name]
	}
    } [m::cmdr::call glue cmd_add]

    private rename {
	use .cms
	description {
	    Change the name of the specified mirror set, or the mirror
	    set indicated by the current repository.
	    Change the name of the specified project, or
	    the project indicated by the current repository.

	    The rolodex does not change.
	}
	use .optional-mirror-set
	use .optional-project
	input name {
	    New name for the mirror set.
	    New name for the project.
	} { validate str }
    } [m::cmdr::call glue cmd_rename]

    private merge {
	use .cms
	description {
	    Merges the specified mirror sets into a single mirror
	    set. When only one mirror set is specified the set of the
	    Merges the specified projects into a single project.
	    When only one project is specified the set of the
	    current repository is used as the merge target. When no
	    mirror sets are specified at all the mirror sets of
	    projects are specified at all the projects of
	    current and previous repositories are merged, using
	    the mirror set of current as merge target
	    the prooject of current as merge target

	    The name of the primary mirror set becomes the name of the
	    The name of the primary project becomes the name of the
	    merge.

	    The rolodex does not change.
	}
	use .list-optional-mirror-set
	use .list-optional-project
    } [m::cmdr::call glue cmd_merge]

    private split {
	use .cms
	description {
	    Split the specified or current repository from its mirror
	    set. Generates a new mirror set for the repository. The
	    name will be derived from the original name. The
	    referenced repository becomes current.
	    Split the specified or current repository from its project.
	    Generates a new project for the repository. The name will be
	    derived from the original name. The referenced repository
	    becomes current.

	    If the referenced repository is a standalone already then
	    nothing is done.
	}
	use .optional-repository
    } [m::cmdr::call glue cmd_split]

    private current {
	use .cms.nav
	description {
	    Shows the rolodex.
	}
    } [m::cmdr::call glue cmd_current]
    alias @

    private export {
	use .cms.ex
	description {
	    Write the known set of repositories and mirror sets to
	    Write the known set of repositories and projects to
	    stdout, in a form suitable for (re)import.
	}
    } [m::cmdr::call glue cmd_export]

    private import {
	use .cms.ex
	description {
	    Read a set of repositories and mirror sets from the
	    specified file, or stdin, and add them here. Ignores known
	    repositories. Makes new mirror sets on name
	    conflicts. Ignores mirror sets with no repositories
	    (including only ignored repositories). Processes the
	    format generated by export.
	    Read a set of repositories and projects from the
	    specified file, or stdin, and add them here. Ignores
	    known repositories. Makes projects on name conflicts.
	    Ignores projects with no repositories (including only
	    ignored repositories). Processes the format generated
	    by export.
	}
	option dated {
	    Add datestamp to the generated mirror sets.
	    Add datestamp to the generated projects.
	} { presence }
	input spec {
	    Path to the file to read the import specification from.
	    Falls back to stdin when no file is specified.
	} { optional ; validate rchan ; default stdin }

    } [m::cmdr::call glue cmd_import]
510
511
512
513
514
515
516
517
518
519
520
521





522
523

524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540



541
542
543
544
545
546
547
513
514
515
516
517
518
519





520
521
522
523
524
525

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540



541
542
543
544
545
546
547
548
549
550







-
-
-
-
-
+
+
+
+
+

-
+














-
-
-
+
+
+







	    Swap current and previous repository
	}
    } [m::cmdr::call glue cmd_swap_current]

    private update {
	use .cms
	description {
	    Runs an update cycle on the specified mirror sets. When no
	    mirror sets are specified use the next `take` number of
	    mirror sets from the list of pending mirror sets. If no
	    mirror sets are pending refill the list with the entire
	    set of mirror sets and then take from the list.
	    Runs an update cycle on the specified repositories. When no
	    repositories are specified use the next `take` number of
	    repositories from the list of pending repositories. If no
	    repositories are pending refill the list with the entire
	    set of repositories and then take from the list.
	}
	use .list-optional-mirror-set
	use .list-optional-repository
    } [m::cmdr::call glue cmd_update]

    private updates {
	use .cms.in
	description {
	    Show compressed history of past updates.
	    Sorted by last changed, updated, created.
	    Empty lines between update cycles
	}
    } [m::cmdr::call glue cmd_updates]

    private pending {
	use .cms.in
	description {
	    Show list of currently pending mirror sets. I.e mirror
	    sets waiting for an update.  Order shown is the order they
	    are taken, from the top down.
	    Show list of currently pending repositories. I.e repositories
	    waiting for an update.  Order shown is the order they are taken,
	    from the top down.
	}
    } [m::cmdr::call glue cmd_pending]

    private issues {
	use .cms.in
	description {
	    Show list of active stores with issues.
574
575
576
577
578
579
580
581

582
583
584
585
586
587
588
577
578
579
580
581
582
583

584
585
586
587
588
589
590
591







-
+







	    alias L
	    validate [m::cmdr::vt limit]
	    generate [m::cmdr::call glue gen_limit]
	}
	input pattern {
	    When specified, search for repositories matching the
	    pattern.  This is a case-insensitive substring search on
	    repository urls and mirror set names. A search overrides
	    repository urls and project names. A search overrides
	    and voids any and all repository and limit specifications.
	    This also keeps the cursor unchanged. The rolodex however
	    is filled with the search results.
	} { optional ; validate str }
    } [m::cmdr::call glue cmd_list]

    private reset {
639
640
641
642
643
644
645
646

647
648
649
650
651
652
653
642
643
644
645
646
647
648

649
650
651
652
653
654
655
656







-
+







	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    option name {
		Name for the future mirror set to hold the submitted repository.
		Name for the future project to hold the submitted repository.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_name]
	    }
	} [m::cmdr::call glue cmd_submit]

665
666
667
668
669
670
671



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687

688
689
690
691
692
693
694
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692

693
694
695
696
697
698
699
700







+
+
+















-
+







	    option vcs {
		Version control system handling the repository.
		Override the submission.
	    } {
		validate [m::cmdr::vt vcs]
		generate [m::cmdr::call glue gen_submit_vcs]
	}
	    option track-forks {
		Force tracking when seeing a large number of forks.
	    } { presence }
	    option nomail {
		Disable generation and sending of acceptance mail.
	    } { presence }
	    state vcs-code {
		Version control system handling the repository.
		Internal code, derived from the option value (database id).
	    } {
		generate [m::cmdr::call glue gen_vcs_code]
	    }
	    state url {
		Location of the repository. Taken from the submission.
	    } { validate str
		generate [m::cmdr::call glue gen_submit_url]
	    }
	    option name {
		Name for the mirror set to hold the repository.
		Name for the project to hold the repository.
		Overrides the name from the submission.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_submit_name]
	    }
	} [m::cmdr::call glue cmd_accept]
950
951
952
953
954
955
956
957

958
959

960
961

962
963
964
965
966
967
968
956
957
958
959
960
961
962

963
964

965
966

967
968
969
970
971
972
973
974







-
+

-
+

-
+








	private test-vt-repo {
	    description {
		Show the knowledge map used by the repository validator.
	    }
	} [m::cmdr::call glue cmd_test_vt_repository]

	private test-vt-mset {
	private test-vt-project {
	    description {
		Show the knowledge map used by the mirror-set validator.
		Show the knowledge map used by the project validator.
	    }
	} [m::cmdr::call glue cmd_test_vt_mset]
	} [m::cmdr::call glue cmd_test_vt_project]

	private test-vt-submission {
	    description {
		Show the knowledge map used by the submission validator.
	    }
	} [m::cmdr::call glue cmd_test_vt_submission]

Changes to lib/cli/glue.tcl.

129
130
131
132
133
134
135
136

137
138
139
140
141
142
143
129
130
131
132
133
134
135

136
137
138
139
140
141
142
143







-
+







    debug.m/glue {[debug caller] | [$p config] }
    debug.m/glue {[debug caller] | --> $vcode ($vcs) }
    return $vcs
}

proc ::m::glue::gen_name {p} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::vcs

    # Derive a name from the url when no such was specified by the
    # user. Add a serial number if that name is already in use.
    set name [MakeName \
		  [m vcs name-from-url \
		       [$p config @vcs-code] \
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
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







-
+

-
+







-
-
-
-
+
+
+
+













-
+








    debug.m/glue {[debug caller] | [$p config] }
    debug.m/glue {[debug caller] | undefined }
    $p undefined!
    # Will not reach here
}

proc ::m::glue::gen_current_mset {p} {
proc ::m::glue::gen_current_project {p} {
    debug.m/glue {[debug caller] | }
    # Provide current as mirror set for operation when not specified
    # Provide current as project for operation when not specified
    # by the user. Fail if we have no current repository to trace
    # from.
    package require m::repo
    package require m::rolodex
    #
    set r [m rolodex top]
    if {$r ne {}} {
	set m [m repo mset $r]
	if {$m ne {}} {
	    debug.m/glue {[debug caller] | --> $m }
	    return $m
	set project [m repo project $r]
	if {$project ne {}} {
	    debug.m/glue {[debug caller] | --> $project }
	    return $project
	}
    }

    debug.m/glue {[debug caller] | [$p config] }
    debug.m/glue {[debug caller] | undefined }
    $p undefined!
    # Will not reach here
}

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

proc ::m::glue::cmd_import {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store
    package require m::url

    set dated [$config @dated]
    if {[$config @spec set?]} {
238
239
240
241
242
243
244
245

246
247
248

249
250
251
252
253
254
255
238
239
240
241
242
243
244

245
246
247

248
249
250
251
252
253
254
255







-
+


-
+







		  [ImportRead $sname [$config @spec]]]]
    SiteRegen
    OK
}

proc ::m::glue::cmd_export {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo

    m msg [m mset spec]
    m msg [m project spec]
}

proc ::m::glue::cmd_reply_add {config} {
    debug.m/glue {[debug caller] | }
    package require m::db
    package require m::reply

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







-
+











-
+







	}] show
    }
    OK
}

proc ::m::glue::cmd_show {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::repo
    package require m::state

    set all [$config @all]

    m db transaction {
	set n [m state limit]
	if {!$n} { set n [color note {adjust to terminal height}] }

	[table/d t {
	    $t add Store         [m state store]
	    $t add Limit         $n
	    $t add Take         "[m state take] ([m mset count-pending] pending/[m mset count] total)"
	    $t add Take         "[m state take] ([m repo count-pending] pending/[m repo count] total)"
	    $t add Window        [m state store-window-size]
	    $t add {Report To}   [m state report-mail-destination]
	    $t add {-} {}
	    $t add {Cycle, Last} [m format epoch [m state start-of-previous-cycle]]
	    $t add {Cycle, Now}  [m format epoch [m state start-of-current-cycle]]

	    if {$all} {
528
529
530
531
532
533
534
535

536
537
538
539
540
541
542
528
529
530
531
532
533
534

535
536
537
538
539
540
541
542







-
+







	if {[$config @take set?]} {
	    m state take [$config @take]
	}

	set n [m state take]
    }

    set g [expr {$n == 1 ? "mirror set" : "mirror sets"}]
    set g [expr {$n == 1 ? "project" : "projects"}]
    m msg "Per update, take [color note $n] $g"
    OK
}

proc ::m::glue::cmd_window {config} {
    debug.m/glue {[debug caller] | }
    package require m::state
582
583
584
585
586
587
588
589

590
591
592
593
594
595
596
597
598
599
600
601
602
603
604

605
606
607
608
609
610
611
612
613
614
615
616
617





618
619
620



621
622
623



624
625

626

627

628
629
630


631



632
633
634
635
636
637









638
639
640
641
642
643
644
582
583
584
585
586
587
588

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603

604
605
606
607
608
609
610

611





612
613
614
615
616



617
618
619
620


621
622
623


624
625
626

627



628
629
630
631
632
633
634
635




636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651







-
+














-
+






-

-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+

-
-
+
+
+
-
-
+

+
-
+
-
-
-
+
+

+
+
+


-
-
-
-
+
+
+
+
+
+
+
+
+







	}
    }] show
    OK
}

proc ::m::glue::cmd_add {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store

    m db transaction {
	Add $config
    }
    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_remove {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store

    m db transaction {
	set repo [$config @repository]
	m msg "Removing [color note [m repo name $repo]] ..."

	set rinfo [m repo get $repo]
	dict with rinfo {}
	# -> url	: repo url
	#    vcs	: vcs id
	#    vcode	: vcs code
        set rinfo [m repo get $repo]
        dict with rinfo {}
        # -> url    : repo url
        #    vcs    : vcs id
        # -> vcode  : vcs code
	#    mset	: mirror set id
	#    name	: mirror set name
	#    store      : store id, of backing store for the repo
        # -> project: project id
        # -> name   : project name
        # -> store  : id of backing store for repo

	m repo remove $repo

	m msg "Removing $vcode repository [color note $url] ..."
	m msg "from Project [color note $name]"
	
	# TODO MAYBE: stuff how much of the cascading remove logic
	# TODO MAYBE: into `repo remove` ?
	m repo remove $repo

	set siblings [m store remotes $store]
	# Remove store for the repo's vcs if no repositories for that
	set nsiblings [llength $siblings]
	# vcs remain in the mirror set.
	if {![m mset has-vcs $mset $vcs]} {
	    m msg "- Removing $vcode store ..."
	if {!$nsiblings} {
	    m msg "- Removing unshared $vcode store $store ..."
	    m store remove $store
	} else {
	    set x [expr {($nsiblings == 1) ? "repository" : "repositories"}]
	    m msg "- Keeping $vcode store $store still used by $nsiblings $x"
	}

	# Remove mirror set if no repositories remain at all.
	if {![m mset size $mset]} {
	    m msg "- Removing mirror set [color note $name] ..."
	    m mset remove $mset
	# Remove project if no repositories remain at all.
	set nsiblings [m project size $project]
	
	if {!$nsiblings} {
	    m msg "- Removing now empty project ..."
	    m project remove $project
	} else {
	    set x [expr {($nsiblings == 1) ? "repository" : "repositories"}]
	    m msg "- Keeping project still used by $nsiblings $x"
	}

	m rolodex drop $repo
	m rolodex commit
    }

    ShowCurrent $config
654
655
656
657
658
659
660












661

662
663

664
665
666
667
668
669
670

671
672
673

674
675
676
677
678

679
680

681
682
683
684
685
686
687








688
689





690
691
692
693
694



695
696
697





698
699
700


701
702
703
704
705




706
707
708
709
710
711
712





















713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728

















729
730

731
732
733
734
735




736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761


762



763

764
765

766
767

768
769
770
771


772













































773
774
775
776
777
778
779
780
781
782
783
784








785
786
787
788
789

790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808




809
810
811
812
813
814
815





816
817
818
819
820
821
822
823
824
825
826
827

828
829
830
831
832
833



834
835
836
837
838
839
840
841

842
843
844
845
846
847

848
849
850
851
852
853
854
855
856
857

858
859
860
861
862
863
864

865
866
867
868
869
870
871
872

873
874
875
876
877

878
879
880

881
882
883
884
885
886
887
888
889
890
891
892

893
894
895
896
897
898
899
900
901
902
903
904
905
906
907






908
909
910
911
912
913

914
915

916
917

918
919
920
921
922


923
924
925

926
927

928
929

930
931

932
933
934
935
936

937
938
939
940
941
942
943

944
945
946
947
948
949
950
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679

680
681

682
683
684
685
686
687
688

689
690
691

692
693
694
695

696
697
698
699
700







701
702
703
704
705
706
707
708


709
710
711
712
713





714
715
716
717
718

719
720
721
722
723
724


725
726





727
728
729
730




731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762








763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780

781
782




783
784
785
786
787






















788


789
790
791
792
793
794

795
796

797
798

799
800
801


802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855






856
857
858
859
860
861
862
863
864
865
866
867

868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883




884
885
886
887
888
889





890
891
892
893
894

895
896
897
898
899
900
901
902
903
904

905
906
907
908



909
910
911
912
913
914
915
916
917
918

919
920
921
922
923
924

925
926
927
928
929
930
931
932
933
934

935
936
937
938
939
940
941

942
943
944
945
946
947
948
949

950
951
952
953
954

955
956
957

958
959
960
961
962
963
964
965
966
967
968
969

970
971
972
973
974
975
976
977
978
979






980
981
982
983
984
985
986
987
988
989
990

991
992

993
994

995
996
997
998


999
1000
1001
1002

1003
1004

1005
1006

1007
1008

1009
1010
1011
1012
1013

1014
1015
1016
1017
1018
1019
1020

1021
1022
1023
1024
1025
1026
1027
1028







+
+
+
+
+
+
+
+
+
+
+
+
-
+

-
+






-
+


-
+



-

+


+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+


-
+
+
+
+
+

-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
-
+
+

+
+
+
-
+

-
+

-
+


-
-
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
-
-
-
-
+
+
+
+
+
+
+
+




-
+















-
-
-
-
+
+
+
+


-
-
-
-
-
+
+
+
+
+
-










-
+



-
-
-
+
+
+







-
+





-
+









-
+






-
+







-
+




-
+


-
+











-
+









-
-
-
-
-
-
+
+
+
+
+
+





-
+

-
+

-
+



-
-
+
+


-
+

-
+

-
+

-
+




-
+






-
+







	    set line [string range $line 0 ${w}-5]...
	}
	lappend r $line
    }
    join $r \n
}

proc m::glue::Short {repo} {
    set ri [m repo get $repo]
    dict with ri {}

    set active [color {*}[dict get {
	0 {warning offline}
	1 {note UP}
    } [expr {!!$active}]]]

    return "$url ([SIB [expr {!$issues}]] $active)"   
}

proc ::m::glue::cmd_details {config} {
proc ::m::glue::cmd_details {config} { ;# XXX REWORK due the project/repo/store relation changes
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::state
    package require m::store
    package require linenoise

    set w [$config @tw] ;#linenoise columns
    set w [$config @tw]    ;#linenoise columns
    # table/d -> 2 columns, 7 overhead, 1st col 14 wide =>
    set w [expr {$w - 21}] ;# max width for col 2.
    

    m db transaction {
	set full [$config @full]
	set repo [$config @repository]
	m msg "Details of [color note [m repo name $repo]] ..."

	# Basic repository details ...........................
	set rinfo [m repo get $repo]
	dict with rinfo {}
	#m msg "Details of [color note $url] ..."
	# -> url	: repo url
	#    vcs	: vcs id
	#    vcode	: vcs code
	# *  mset	: mirror set id
	# *  name	: mirror set name
	# *  store      : store id, of backing store for the repo

	# -> url    : repo url
	#    active : usage state
	#    vcs    : vcs id
	#    vcode  : vcs code
        # *  project: project id
        # *  name   : project name
        # -> store  : id of backing store for repo
	#    min_sec: minimal time spent on setup/update
	# Get pieces ...
	
	#    max_sec: maximal time spent on  setup/update
	#    win_sec: last n times for setup/update
	#    checked: epoch of last check
	#    origin : repository this is forked from, or empty

	lassign [m store remotes $store] remotes plugin
	lappend r Remotes $remotes
	if {[llength $plugin]} {
	    lappend r {*}$plugin
	}
	set spent [StatsTime $min_sec $max_sec $win_sec]
	
	# Get store details ...

	set path [m store path $store]
	set sd   [m store get $store]
	set sd   [m store get  $store]
	dict unset sd vcs
	dict unset sd min_sec
	dict unset sd max_sec
	dict unset sd win_sec
	dict with sd {}
	# -> size, sizep
	#    commits, commitp
	#  size, sizep
	#  commits, commitp
	#    vcs
	#    vcsname
	#    created
	#    changed
	#    updated
	#  vcsname
	#  created
	#  changed
	#  updated
	#    min_sec, max_sec, win_sec

	set spent [StatsTime $min_sec $max_sec $win_sec]
	
	lassign [m vcs caps $store] stdout stderr
	set stdout [string trim $stdout]
	set stderr [string trim $stderr]
	
	# Find repositories sharing the store ................

	set storesibs [m store repos $store]
	
	# Find repositories which are siblings of the same origin

	set forksibs {}
	set dorigin  {}
	if {$origin ne {}} {
	    set forksibs [m repo forks $origin]
	    set dorigin [Short $origin]
	}
	
	# Find repositories which are siblings of the same project

	set projectsibs [m repo for $project]

	#puts O(($origin))/\nR(($repo))/\nS(($storesibs))/\nF(($forksibs))/\nP(($projectsibs))
	
	# Compute derived information ...

	set status  [SI $stderr]
	set export  [m vcs export $vcs $store]
	set dcommit [DeltaCommitFull $commits $commitp]
	set dsize   [DeltaSizeFull $size $sizep]
	set changed [color note [m format epoch $changed]]
	set updated [m format epoch $updated]
	set created [m format epoch $created]
	
	[table/d t {
	    $t add Status        $status
	    $t add {Mirror Set}  $name
	    $t add VCS           $vcsname
	    $t add {Local Store} $path
	    $t add Size          $dsize
	    $t add Commits       $dcommit

	set active [color {*}[dict get {
	    0 {warning offline}
	    1 {note UP}
	} [expr {!!$active}]]]

	set s [[table/d s {
	    $s borders 0
	    set sibs 0
	    foreach sibling $storesibs {
		if {$sibling == $repo} continue
		incr sibs
		$s add ${sibs}. [Short $sibling]
	    }
	    if {$sibs} { $s add {} {} }
	    $s add Size $dsize
	    $s add Commits       $dcommit
	    if {$export ne {}} {
		$t add Export $export
		$s add Export $export
	    }
	    $t add {Update Stats} $spent
	    $t add {Last Change}  $changed
	    $t add {Last Check}   $updated
	    $t add Created        $created
	    $s add {Update Stats} $spent
	    $s add {Last Change}  $changed
	    $s add {Last Check}   $updated
	    $s add Created        $created

	    set active 1
	    foreach {label urls} $r {
		$t add $label "\#[llength $urls]"
		# TODO: options to show all, part, none
		if {$label eq "Forks"} break
		foreach url [lsort -dict $urls] {
		    incr id
		    set a "    "
		    if {$active} {
			set a [dict get	[m repo get [m repo id $url]] active]
			if {$a} {
			    set a "    "
			} else {
			    set a "off "
			}
		    }
		    $t add $id [L "$a$url"]
		}
		unset -nocomplain id
		incr active -1
	    }

	    if {!$full} {
		set nelines #[llength [split $stderr \n]]
		set nllines #[llength [split $stdout \n]]
		set nelines [llength [split $stderr \n]]
		set nllines [llength [split $stdout \n]]

		if {$nelines == 0} { set nelines [color note {no log}] }
		if {$nllines == 0} { set nllines [color note {no log}] }
		
		$t add Operation $nllines
		$s add Operation $nllines
		if {$stderr ne {}} {
		    $t add "Notes & Errors" [color bad $nelines]
		    $s add "Notes & Errors" [color bad $nelines]
		} else {
		    $t add "Notes & Errors" $nelines
		    $s add "Notes & Errors" $nelines
		}
	    } else {
		if {$stdout ne {}} { $t add Operation        [L $stdout] }
		if {$stderr ne {}} { $t add "Notes & Errors" [L $stderr] }
		if {$stdout ne {}} { $s add Operation        [L $stdout] }
		if {$stderr ne {}} { $s add "Notes & Errors" [L $stderr] }
	    }
	}] show return]
	
	[table/d t {
	    $t add {} [color note $url]
	    if {$origin ne {}} {
		$t add Origin $dorigin
	    }
	    $t add Status        "$status $active @[color note [m format epoch $checked]]"
	    $t add Project       $name
	    $t add VCS           $vcsname

	    $t add {Local Store} $path
	    $t add {}            $s

	    # Show other locations serving the project, except for forks.
	    # Forks are shown separately.
	    set sibs 0
	    foreach sibling $projectsibs {
		if {$sibling == $repo} continue
		if {$sibling == $origin} continue
		if {$sibling in $storesibs} continue
		if {$sibling in $forksibs} continue
		if {!$sibs} { $t add Other {} }
		incr sibs
		$t add ${sibs}. [Short $sibling]		
	    }

	    set threshold 20
	    # Show the sibling forks. Only the first, only if not sharing the store.
	    set sibs 0
	    foreach sibling $forksibs {
		if {$sibling == $repo} continue
		if {$sibling == $origin} continue
		if {$sibling in $storesibs} continue
		if {!$sibs} { $t add Related {} }
		incr sibs

		# 
		if {$sibs > $threshold} continue
		$t add ${sibs}. [Short $sibling]
	    }
	    if {$sibs > $threshold} {
		$t add {} "(+[expr {$sibs - $threshold}] more)"
	    }

	}] show
    }
    OK
}

proc ::m::glue::SI {stderr} {
    if {$stderr eq {}} {
	return [color good OK]
    } else {
	set status images/bad.svg
	return [color bad ATTEND]
    }
    SIB [expr {$stderr eq {}}]
}

proc ::m::glue::SIB {ok} {
    color {*}[dict get {
	0 {bad ATTEND}
	1 {good OK}
    } $ok]
}

proc ::m::glue::cmd_enable {flag config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store

    set op [expr {$flag ? "Enabling" : "Disabling"}]

    m db transaction {
	foreach repo [$config @repositories] {
	    m msg "$op [color note [m repo name $repo]] ..."

	    set rinfo [m repo get $repo]
	    dict with rinfo {}
	    # -> url	: repo url
	    #    vcs	: vcs id
	    #    vcode	: vcs code
	    #    mset	: mirror set id
	    #    name	: mirror set name
	    #    store  : store id, of backing store for the repo
	    
	    #    project: project id
	    #    name	: project name
	    #    store  : id of backing store for repo

	    m repo enable $repo $flag

	    # Note: We do not manipulate `mset_pending`. An existing
	    # mirror set is always in `mset_pending`, even if all its
	    # remotes are inactive. The commands to retrieve the
	    # pending msets (all, or taken for update) is where we do
	    # the filtering, i.e. exclusion of those without active
	    # Note: We do not manipulate `repo_pending`. An existing
	    # repo is always in `repo_pending`, even if it is
	    # inactive. The commands to retrieve the pending repos
	    # (all, or taken for update) is where we do the filtering,
	    # i.e. exclusion of the inactive.
	    # remotes.
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_rename {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::store

    m db transaction {
	set mset    [$config @mirror-set] ; debug.m/glue {mset    : $mset}
	set newname [$config @name]       ; debug.m/glue {new name: $newname}
	set oldname [m mset name $mset]
	set project [$config @project] ; debug.m/glue {project : $project}
	set newname [$config @name]    ; debug.m/glue {new name: $newname}
	set oldname [m project name $project]

	m msg "Renaming [color note $oldname] ..."
	if {$newname eq $oldname} {
	    m::cmdr::error \
		"The new name is the same as the current name." \
		NOP
	}
	if {[m mset has $newname]} {
	if {[m project has $newname]} {
	    m::cmdr::error \
		"New name [color note $newname] already present" \
		HAVE_ALREADY NAME
	}

	Rename $mset $newname
	Rename $project $newname
    }

    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_merge {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store
    package require m::vcs

    m db transaction {
	set msets [Dedup [MergeFill [$config @mirror-sets]]]
	set msets [Dedup [MergeFill [$config @projects]]]
	# __Attention__: Cannot place the mergefill into a generate
	# clause, the parameter logic is too simple (set / not set) to
	# handle the case of `set only one`.
	debug.m/glue {msets = ($msets)}

	if {[llength $msets] < 2} {
	    m::cmdr::error \
		"All repositories are already in the same mirror set." \
		"All repositories are already in the same project." \
		NOP
	}

	set secondaries [lassign $msets primary]
	m msg "Target:  [color note [m mset name $primary]]"
	m msg "Target:  [color note [m project name $primary]]"

	foreach secondary $secondaries {
	    m msg "Merging: [color note [m mset name $secondary]]"
	    m msg "Merging: [color note [m project name $secondary]]"
	    Merge $primary $secondary
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_split {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store
    package require m::vcs

    m db transaction {
	set repo [$config @repository]
	set rinfo [m repo get $repo]
	dict with rinfo {}
	# -> url	: repo url
	#    vcs	: vcs id
	#    vcode	: vcs code
	#    mset	: mirror set id
	#    name	: mirror set name
	#    store      : store id, of backing store for the repo
	# -> url    : repo url
	#    vcs    : vcs id
	#    vcode  : vcs code
	#    project: project id
	#    name   : project name
	#    store  : id of backing store for repo

	m msg "Attempting to separate"
	m msg "  Repository [color note $url]"
	m msg "  Managed by [color note [m vcs name $vcs]]"
	m msg "From"
	m msg "  Mirror set [color note $name]"
	m msg "  Project    [color note $name]"

	if {[m mset size $mset] < 2} {
	if {[m project size $mset] < 2} {
	    m::cmdr::error \
		"The mirror set is to small for splitting" \
		"The project is to small for splitting" \
		ATOMIC
	}

	set newname [MakeName $name]
	set msetnew [m mset add $newname]
	set newname    [MakeName $name]
	set projectnew [m project add $newname]

	m msg "New"
	m msg "  Mirror set [color note $newname]"
	m msg "  Project    [color note $newname]"

	m repo move/1 $repo $msetnew
	m repo move/1 $repo $projectnew

	if {![m mset has-vcs $mset $vcs]} {
	if {![m project has-vcs $mset $vcs]} {
	    # The moved repository was the last user of its vcs in the
	    # original mirror set. We can simply move its store over
	    # original project. We can simply move its store over
	    # to the new holder to be ok.

	    m msg "  Move store ..."

	    m store move $store $msetnew
	    m store move $store $projectnew
	} else {
	    # The originating mset still has users for the store used
	    # by the moved repo. Need a new store for the moved repo.

	    m msg "  Split store ..."

	    m store cleave $store $msetnew
	    m store cleave $store $projectnew
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}
982
983
984
985
986
987
988
989

990
991

992
993
994
995

996
997
998
999



1000
1001
1002


1003



1004
1005
1006




1007


1008

1009
1010
1011
1012





1013
1014
1015


1016
1017
1018
1019



1020
1021
1022
1023






1024
1025
1026
1027



1028
1029

1030
1031
1032
1033
1034



1035

1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049













































1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062



1063
1064
1065



1066
1067
1068
1069
1070
1071



1072
1073
1074
1075
1076
1077

1078
1079
1080
1081
1082
1083
1084


1085

1086
1087
1088
1089
1090


1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104

1105
1106
1107
1108
1109
1110
1111
1112



1113
1114
1115
1116




1117
1118

1119
1120
1121

1122


1123
1124
1125
1126
1127
1128


1129
1130
1131
1132

1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143

1144
1145
1146
1147
1148
1149
1150

1151
1152

1153
1154
1155
1156
1157


1158
1159
1160
1161
1162
1163




1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180


1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193

1194
1195
1196
1197
1198
1199
1200

1201
1202

1203
1204


1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224


1225
1226
1227
1228
1229
1230
1231
1060
1061
1062
1063
1064
1065
1066

1067
1068
1069
1070
1071
1072
1073

1074
1075
1076


1077
1078
1079
1080


1081
1082

1083
1084
1085
1086


1087
1088
1089
1090
1091
1092
1093

1094




1095
1096
1097
1098
1099



1100
1101




1102
1103
1104




1105
1106
1107
1108
1109
1110




1111
1112
1113
1114

1115





1116
1117
1118
1119
1120














1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191


1192
1193
1194
1195
1196
1197
1198
1199

1200
1201
1202
1203
1204
1205
1206
1207
1208
1209

1210
1211
1212
1213


1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228

1229
1230
1231
1232
1233
1234



1235
1236
1237
1238
1239
1240

1241
1242
1243
1244
1245

1246
1247
1248

1249
1250
1251
1252
1253
1254
1255
1256


1257
1258
1259
1260
1261

1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272

1273
1274
1275
1276
1277
1278
1279

1280
1281

1282
1283
1284



1285
1286






1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305


1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319

1320
1321
1322
1323
1324
1325
1326

1327
1328

1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351


1352
1353
1354
1355
1356
1357
1358
1359
1360







-
+


+



-
+


-
-
+
+
+

-
-
+
+
-
+
+
+

-
-
+
+
+
+

+
+
-
+
-
-
-
-
+
+
+
+
+
-
-
-
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+

-
+
-
-
-
-
-
+
+
+

+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+













+
+
+



+
+
+




-
-
+
+
+





-
+







+
+
-
+



-
-
+
+













-
+





-
-
-
+
+
+



-
+
+
+
+

-
+


-
+

+
+




-
-
+
+



-
+










-
+






-
+

-
+


-
-
-
+
+
-
-
-
-
-
-
+
+
+
+















-
-
+
+












-
+






-
+

-
+


+
+


















-
-
+
+








    ShowCurrent $config
    OK
}

proc ::m::glue::cmd_update {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::state
    package require m::store
    package require struct::set

    set startcycle [m state start-of-current-cycle]
    set nowcycle   [clock seconds]
    

    m db transaction {
	set verbose  [$config @verbose]
	set msets    [UpdateSets $startcycle $nowcycle [$config @mirror-sets]]
	debug.m/glue {msets = ($msets)}
	set repos    [UpdateRepos $startcycle $nowcycle [$config @repositories]]

	debug.m/glue {repositories = ($repos)}

	foreach mset $msets {
	    set mname [m mset name $mset]
	foreach repo $repos {
	    set ri [m repo get $repo]
	    m msg "Updating Mirror Set [color note $mname] ..."
	    dict with ri {}
	    # url, active, issues, vcs, vcode, project, name, store
	    # min/max/win_sec, checked, origin

	    set stores [m mset stores $mset]
	    debug.m/glue {stores = ($stores)}
	    set si [m store get $store]
	    # size, vcs, sizep, commits, commitp, vcsname, updated, changed, created
	    # (atted, min/max/win, remote, active)
	    set before [dict get $si commits]

	    set durl [color note $url]
	    if {$origin eq {}} { set durl [color bg-cyan $durl] }
	    foreach store $stores {
	    
		set vname [m store vcs-name $store]
		if {$verbose} {
		    m msg "  [color note $vname] store ... "
		} else {
	    m msg "Updating repository $durl ..."
	    m msg "In project          [color note $name]"
	    if {$verbose} {
		m msg  "  [color note [string totitle $vcode]] store ... "
	    } else {
		    m msg* "  $vname store ... "
		}

		m msg* "  [string totitle $vcode] store ... "
	    }
		# TODO MAYBE: List the remotes we are pulling from ?
		# => VCS layer, notification callback ...
		set counts [m store update $store $nowcycle [clock seconds]]
		lassign $counts before after forks remotes spent
	    set primary [expr {$origin eq {}}]

	    # -- has_issues, is_active/enable -- fork handling
		debug.m/glue {update = ($counts)}
		
		set    suffix ""
		append suffix ", in " [color note [m format interval $spent]]

	    set now [clock seconds]
	    lassign [m store update $primary $url $store $nowcycle $now $before] \
		ok duration commits size forks
	    set attend [expr {!$ok || [m store has-issues $store]}]
	    set suffix ", in [color note [m format interval $duration]]"
		append suffix " ("    [color note [lindex $remotes 0]] ")"
		if {$forks ne {}} {
		    append suffix \n "  Github: Currently tracking [color note [llength $forks]] additional forks"
		}
	    
	    m repo times  $repo $duration $now $attend
	    if {!$primary && $attend} { m repo enable $repo 0 }

		if {$before < 0} {
	    if {!$ok} {
		    # Highlevel VCS url check failed for this store.
		    # Results in the stderr log.
		    lassign [m vcs caps $store] _ e
		    m msg "[color bad Fail]$suffix"
		    m msg $e
		lassign [m vcs caps $store] _ e
		m msg "[color bad Fail]$suffix"
		m msg $e

		continue		
		} elseif {$before != $after} {
		    set delta [expr {$after - $before}]
		    if {$delta < 0} {
			set mark bad
		    } else {
			set mark note
			set delta +$delta
		    }
		    m msg "[color note Changed] $before $after ([color $mark $delta])$suffix"
		} elseif {$verbose} {
		    m msg "[color note "No changes"]$suffix"
		} else {
		    m msg "No changes$suffix"
		}
	    } elseif {$before != $commits} {
		set delta [expr {$commits - $before}]
		if {$delta < 0} {
		    set mark bad
		} else {
		    set mark note
		    set delta +$delta
		}
		m msg "[color note Changed] $before $commits ([color $mark $delta])$suffix"
	    } elseif {$verbose} {
		m msg "[color note "No changes"]$suffix"
	    } else {
		m msg "No changes$suffix"
	    }

	    if {$primary} {
		# check currently found forks against what is claimed by the system
		set forks_prev [m repo fork-locations $repo]
		
		lassign [struct::set intersect3 $forks_prev $forks] same removed added
		# previous - current => removed from previous
		# current  - previous => added over previous

		# Actions:
		# - The removed forks are detached from the primary.
		#   We keep the repository. Activation state is unchanged
		#
		# - Unchanged forks are reactivated if they got disabled.
		#
		# - New forks are attempted to be added back
		#   This may actually reclaim a fork which was declaimed before.
		#
		#   Note: Only these new forks have to be validated!
		#   Note: Tracking threshold is irrelevant here.
		
		foreach r $removed {
		    m msg "  [color warning {Detaching lost}] [color note $r]"
		    m repo declaim [m repo id $r]
		}
		foreach r $same {
		    # m msg "  Unchanged      [color note $r], activating"
		    m repo enable [m repo id $r]
		}

		AddForks $added $repo $vcs $vcode $name $project
	    }
	}
    }

    SiteRegen
    OK
}

proc ::m::glue::cmd_updates {config} {
    debug.m/glue {[debug caller] | }
    package require m::store

    m db transaction {

	# m store updates XXX rework actually repos
	
	# TODO: get status (stderr), show - store id
	set series {}
	foreach row [TruncH [m store updates] [expr {[$config @th]-1}]] {


	    
	    if {[lindex $row 0] eq "..."} {
		lappend series [list ... {} {} {} {} {} {}]
		continue
	    }
	    # store mname vcode changed updated created size active remote
	    # sizep commits commitp mins maxs lastn
	    # store mname vcode changed updated created size active
	    # remote sizep commits commitp mins maxs lastn url origin

	    dict with row {}
	    if {$created eq "."} {
		lappend series [list - - - - - - -]
		continue
	    }
	    

	    set changed [m format epoch $changed]
	    set updated [m format epoch $updated]
	    set created [m format epoch $created]
	    set dsize   [DeltaSize $size $sizep]
	    set dcommit [DeltaCommit $commits $commitp]
	    set lastn   [LastTime $lastn]

	    if {$origin eq {}} { set url [color bg-cyan $url] }

	    lappend series [list $mname $vcode $dsize $dcommit $lastn $changed $updated $created]
	    lappend series [list $url $vcode $dsize $dcommit $lastn $changed $updated $created]
	}
    }
    lassign [TruncW \
		 {{Mirror Set} VCS Size Commits Time Changed Updated Created} \
		 {1 0 0 0 0 0 0 0} \
		 {Project VCS Size Commits Time Changed Updated Created} \
		 {1       0   0    0       0    0       0       0} \
		 $series [$config @tw]] \
	titles series
    m msg "Cycles: [m format epoch [m state start-of-previous-cycle]] ... [m format epoch [m state start-of-current-cycle]] ..."
    [table t $titles {
	foreach row $series {
	    $t add {*}$row
	}
    }] show
    OK
}

proc ::m::glue::cmd_pending {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::state

    set tw [$config @tw]
    set th [$config @th] ; incr th -1 ;# Additional line before the table (counts).

    set nmset    [m mset count]
    set npending [m mset count-pending]
    
    set nrepo    [m repo count]
    set npending [m repo count-pending]

    m db transaction {
	set series {}
	set take   [m state take]
	foreach {mname numrepo} [m mset pending] {

	foreach {pname url origin nforks} [m repo pending] {
	    if {$origin eq {}} { set url [color bg-cyan $url] }
	    set row {}
	    if {$take} {
		lappend series [list * $mname $numrepo]
		lappend row *
		incr take -1
	    } else {
		lappend series [list {} $mname $numrepo]
		lappend row {}
	    }
	    lappend row $url $nforks $pname
	    lappend series $row
	}
    }

    lassign [TruncW \
		 {{} {Mirror Set} #Repositories} \
		 {0 1 0} \
		 {{} Repository Forks Project} \
		 {0  0          0     1} \
		 [TruncH $series $th] $tw] \
	titles series

    puts @[color note $npending]/$nmset
    puts @[color note $npending]/$nrepo
    [table t $titles {
	foreach row $series {
	    $t add {*}$row
	}
    }] show
    OK
}

proc ::m::glue::cmd_issues {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store

    m db transaction {
	set series {}
	foreach row [m store issues] {
	foreach row [m store issues] {	;# XXX rework actually repo issues
	    dict with row {}
	    # store mname vcode changed updated created size active remote
	    # store mname vcode changed updated created size active remote rid url
	    set size [m format size $size]

	    # urls of repos associated with the store
	    set urls [lindex [m store remotes $store] 0]
		
	    if {$origin eq {}} { set url [color bg-cyan $url] }

	    foreach url $urls {
		set rid [m repo id $url]
		lappend series [list $rid $url $mname $vcode $size]
		m rolodex push $rid
	    }
	}
	    lappend series [list $rid $url $mname $vcode $size]
	    m rolodex push $rid
	}

	m rolodex commit
	set n [llength $series]

	set table {}
	foreach row $series {
	    incr n -1
	    set row [lassign $row rid]
	    set dex [m rolodex id $rid]
	    set tag @$dex
	    if {$n == 1} { lappend tag @p }
	    if {$n == 0} { lappend tag @c }
	    lappend table [list $tag {*}$row]
	}
    }
    lassign [TruncW \
		 {Tag Repository Set VCS Size} \
		 {0 1 3 0 0} \
		 {Tag Repository Project VCS Size} \
		 {0   0          1       0   0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 1 note] ;# 1 => url
	}
    }] show
    OK
}

proc ::m::glue::cmd_disabled {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store

    m db transaction {
	set series {}
	foreach row [m store disabled] {
	foreach row [m store disabled] {	# XXX REWORK actually repo state
	    dict with row {}
	    # store mname vcode changed updated created size active remote attend rid url
	    # store mname vcode changed updated created size active remote attend rid url origin
	    set size [m format size $size]

	    if {$origin eq {}} { set url [color bg-cyan $url] }
	    
	    lappend series [list $rid $url $mname $vcode $size]
	    m rolodex push $rid
	}
	m rolodex commit
	set n [llength $series]

	set table {}
	foreach row $series {
	    incr n -1
	    set row [lassign $row rid]
	    set dex [m rolodex id $rid]
	    set tag @$dex
	    if {$n == 1} { lappend tag @p }
	    if {$n == 0} { lappend tag @c }
	    lappend table [list $tag {*}$row]
	}
    }
    lassign [TruncW \
		 {Tag Repository Set VCS Size} \
		 {0 1 3 0 0} \
		 {Tag Repository Project VCS Size} \
		 {0   0          1       0   0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 1 note] ;# 1 => url
	}
    }] show
1245
1246
1247
1248
1249
1250
1251






1252
1253
1254

1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269

1270
1271
1272
1273
1274
1275
1276
1277
1278

1279
1280
1281
1282
1283

1284
1285
1286
1287
1288
1289
1290
1291
1292


1293
1294
1295
1296

1297
1298
1299
1300
1301
1302
1303
1304
1305
1306


1307
1308
1309
1310
1311
1312
1313
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388

1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403

1404
1405
1406
1407
1408
1409
1410
1411
1412

1413
1414
1415
1416
1417

1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432

1433
1434
1435
1436
1437
1438
1439
1440
1441


1442
1443
1444
1445
1446
1447
1448
1449
1450







+
+
+
+
+
+


-
+














-
+








-
+




-
+









+
+



-
+








-
-
+
+







	    set series [m repo search $pattern]
	} else {
	    # No search, show a chunk of the list as per options.
	    if {[$config @repository set?]} {
		set repo [$config @repository]
		set ri [m repo get $repo]
		dict with ri {}
		# -> url    : repo url
		#    vcs    : vcs id
		#    vcode  : vcs code
		#    project: project id
		# -> name   : project name
		#    store  : id of backing store for repo
		set first [list $name $url]
		debug.m/glue {from request: $first}
		unset name url vcs vcode store ri
		unset name url vcs vcode store ri project
	    } else {
		set first [m state top]
		debug.m/glue {from state: $first}
	    }
	    set limit [$config @limit]
	    if {$limit == 0} {
		set limit [expr {[$p config @th]-7}]
	    }

	    lassign [m repo get-n $first $limit] next series

	    debug.m/glue {next   ($next)}
	    m state top $next
	}
	# series = list (dict (mset url rid vcode sizekb active sizep commits commitp mins maxs lastn))
	# series = list (dict (primary name url rid vcode sizekb active sizep commits commitp mins maxs lastn))

	debug.m/glue {series ($series)}

	set n 0
	foreach row $series {
	    m rolodex push [dict get $row id]
	    incr n
	}
	

	set idx -1
	set table {}
	foreach row $series {
	    dict with row {}
	    # name url id vcode sizekb active sizep commits commitp mins maxs lastn
	    # primary name url id vcode sizekb active sizep commits commitp mins maxs lastn
	    incr idx
	    #set url [color note $url]
	    set ix  [m rolodex id $id]
	    set tag {}
	    if {$ix  ne {}}     { lappend tag @$ix }
	    if {$idx == ($n-2)} { lappend tag @p }
	    if {$idx == ($n-1)} { lappend tag @c }
	    set a [expr {$active ? "A" : "-"}]

	    if {$primary} { set url [color bg-cyan $url] }
	    
	    set dsize   [DeltaSize $sizekb $sizep]
	    set dcommit [DeltaCommit $commits $commitp]
	    set lastn   [LastTime $lastn]
	    

	    lappend table [list $tag $a $url $name $vcode $dsize $dcommit $lastn]
	    # ................. 0    1   2    3    4      5      6        7
	}
    }

    # See also ShowCurrent
    # TODO: extend list with store times ?
    lassign [TruncW \
		 {Tag {} Repository Set VCS Size Commits Time} \
		 {0 0 1 2 0 -1 -1 0} \
		 {Tag {} Repository Project VCS Size Commits Time} \
		 {0   0  0          1       0   -1   -1      0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
1367
1368
1369
1370
1371
1372
1373
1374

1375
1376
1377
1378
1379
1380
1381
1504
1505
1506
1507
1508
1509
1510

1511
1512
1513
1514
1515
1516
1517
1518







-
+







	    set when [m format epoch $when]

	    lappend series [list $id $when $url $vcode $desc $email $submitter]
	}
    }
    lassign [TruncW \
		 {{} When Url VCS Description Email Submitter} \
		 {0 0 2 0 3 0 1} \
		 {0  0    0   0   3           0     1} \
		 $series [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
1390
1391
1392
1393
1394
1395
1396
1397

1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415

1416
1417
1418
1419
1420
1421
1422
1527
1528
1529
1530
1531
1532
1533

1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551

1552
1553
1554
1555
1556
1557
1558
1559







-
+

















-
+







	set series {}
	foreach {url reason} [m submission rejected] {
	    lappend series [list $url $reason]
	}
    }
    lassign [TruncW \
		 {Url Reason} \
		 {1 0} \
		 {1   0} \
		 $series [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 0 note] ;# 0 => url
	}
    }] show
    OK
}

proc ::m::glue::cmd_submit {config} {
    debug.m/glue {[debug caller] | }
    package require m::submission
    package require m::repo

    # session id for cli, daily rollover, keyed to host and user
    set sid "cli.[expr {[clock second] % 86400}]/[info hostname]/$::tcl_platform(user)"
    

    m db transaction {
	set url       [Url $config]
	set email     [$config @email]
	set submitter [$config @submitter]
	set vcode     [$config @vcs-code]
	set desc      [$config @name]
	set url       [m vcs url-norm $vcode $url]
1450
1451
1452
1453
1454
1455
1456
1457

1458
1459
1460
1461
1462
1463
1464
1587
1588
1589
1590
1591
1592
1593

1594
1595
1596
1597
1598
1599
1600
1601







-
+







    }
    SiteRegen
    OK
}

proc ::m::glue::cmd_accept {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project
    package require m::repo
    package require m::rolodex
    package require m::store
    package require m::submission
    package require m::mail::generator
    package require m::mailer

1605
1606
1607
1608
1609
1610
1611
1612

1613
1614
1615
1616
1617
1618
1619

1620
1621






1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632

1633
1634
1635
1636
1637
1638

1639
1640

1641
1642

1643
1644
1645
1646

1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660

1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674

1675
1676
1677
1678
1679
1680
1681
1742
1743
1744
1745
1746
1747
1748

1749
1750
1751
1752
1753
1754
1755
1756
1757


1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773

1774
1775
1776
1777
1778
1779

1780
1781

1782
1783

1784
1785
1786
1787

1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801

1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815

1816
1817
1818
1819
1820
1821
1822
1823







-
+







+
-
-
+
+
+
+
+
+










-
+





-
+

-
+

-
+



-
+













-
+













-
+







	m mailer to [$config @destination] [m mail generator reply $message {}]
    } else {
	m db transaction {
	    set message [ComeAroundMail [$config @tw] [m state start-of-current-cycle] [clock seconds]]
	}
	m msg $message
    }
    OK    
    OK
}

proc ::m::glue::cmd_test_mail_config {config} {
    debug.m/glue {[debug caller] | }
    package require m::mailer
    package require m::mail::generator

    try {
    m mailer to [$config @destination] [m mail generator test]
    OK    
	m mailer to [$config @destination] [m mail generator test]
    } on error {e o} {
	m msg [color bad $e]
	exit
    }
    OK
}

proc ::m::glue::cmd_test_vt_repository {config} {
    debug.m/glue {[debug caller] | }
    package require m::repo

    set map [m repo known]
    [table/d t {
	foreach k [lsort -dict [dict keys $map]] {
	    set v [dict get $map $k]
	    $t add $k $v
	    $t add $v $k
	}
    }] show
    OK
}

proc ::m::glue::cmd_test_vt_mset {config} {
proc ::m::glue::cmd_test_vt_project {config} {
    debug.m/glue {[debug caller] | }
    package require m::mset
    package require m::project

    set map [m mset known]
    set map [m project known]
    [table/d t {
	foreach k [lsort -dict [dict keys $map]] {
	    set v [dict get $map $k]
	    $t add $k $v
	    $t add $v $k
	}
    }] show
    OK
}

proc ::m::glue::cmd_test_vt_reply {config} {
    debug.m/glue {[debug caller] | }
    package require m::reply

    set map [m reply known]
    [table/d t {
	foreach k [lsort -dict [dict keys $map]] {
	    set v [dict get $map $k]
	    $t add $k $v
	    $t add $v $k
	}
    }] show
    OK
}

proc ::m::glue::cmd_test_vt_submission {config} {
    debug.m/glue {[debug caller] | }
    package require m::submission

    set map [m submission known]
    [table/d t {
	foreach k [lsort -dict [dict keys $map]] {
	    set v [dict get $map $k]
	    $t add $k $v
	    $t add $v $k
	}
    }] show
    OK
}

proc ::m::glue::cmd_debug_levels {config} {
    debug.m/glue {[debug caller] | }
1741
1742
1743
1744
1745
1746
1747
1748


1749
1750
1751
1752
1753
1754
1755


1756
1757
1758
1759
1760
1761
1762
1883
1884
1885
1886
1887
1888
1889

1890
1891
1892
1893
1894
1895
1896
1897

1898
1899
1900
1901
1902
1903
1904
1905
1906







-
+
+






-
+
+







}

proc ::m::glue::ImportRead {label chan} {
    debug.m/glue {[debug caller] | }
    m msg "Reading [color note $label] ..."
    return [split [string trim [read $chan]] \n]
    #         :: list (command)
    # command :: list ('M' name)
    # command :: list ('M' name)	- old: 'M'irrorset
    #          | list ('P' name)	- new: 'P'roject
    #          | list ('R' vcode url)
}

proc ::m::glue::ImportVerify {commands} {
    debug.m/glue {}
    # commands :: list (command)
    # command  :: list ('M' name)
    # command  :: list ('M' name)	- old: 'M'irrorset
    #           | list ('P' name)	- new: 'P'roject
    #           | list ('R' vcode url)

    m msg "Verifying ..."

    foreach {code name} [m vcs all] {
	dict set vcs $code .
	dict set vcs $name .
1776
1777
1778
1779
1780
1781
1782
1783


1784
1785
1786
1787
1788
1789
1790
1920
1921
1922
1923
1924
1925
1926

1927
1928
1929
1930
1931
1932
1933
1934
1935







-
+
+







	set command [string trim $command]

	# skip empty lines
	if {$command eq {}} continue

	lassign $command cmd a b
	switch -exact -- $cmd {
	    M {
	    M -
	    P {
		Ping "  $command"
		# M name --> a = name, b = ((empty string))
		if {[llength $command] != 2} {
		    lappend msg "Line [format $lfmt $lno]: Bad syntax: $command"
		}
	    }
	    R {
1871
1872
1873
1874
1875
1876
1877
1878

1879
1880
1881
1882
1883
1884
1885
2016
2017
2018
2019
2020
2021
2022

2023
2024
2025
2026
2027
2028
2029
2030







-
+








		dict set seen $resolved $lno
		lappend x $resolved
	        lappend repo $vcs $resolved
	    }
	    M {
		if {![llength $repo]} {
		    m msg "Line $lno: [color warning Skip] empty mirror set [color note $vcs]"
		    m msg "Line $lno: [color warning Skip] empty project [color note $vcs]"
		    set repo {}
		    continue
		}
		foreach r $x { dict lappend seen $r $vcs } ;# vcs = mname
		unset x

		lappend command $repo
1924
1925
1926
1927
1928
1929
1930
1931

1932
1933
1934
1935

1936
1937
1938


1939
1940
1941
1942
1943
1944
1945
1946
1947
1948

1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959

1960
1961
1962
1963
1964
1965
1966



1967
1968
1969
1970
1971

1972
1973
1974
1975



1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990

1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007



2008
2009
2010
2011
2012
2013
2014
2069
2070
2071
2072
2073
2074
2075

2076
2077
2078
2079

2080
2081


2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092

2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103

2104
2105
2106
2107
2108



2109
2110
2111
2112
2113
2114
2115

2116
2117



2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134

2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149



2150
2151
2152
2153
2154
2155
2156
2157
2158
2159







-
+



-
+

-
-
+
+









-
+










-
+




-
-
-
+
+
+




-
+

-
-
-
+
+
+














-
+














-
-
-
+
+
+







    return
}

proc ::m::glue::Import1 {date mname repos} {
    debug.m/glue {[debug caller] | }
    # repos = list (vcode url ...)

    m msg "Handling [color note $mname] ..."
    m msg "Handling project [color note $mname] ..."

    if {[llength $repos] == 2} {
	lassign $repos vcode url
	# The mirror set contains only a single repository.
	# The project contains only a single repository.
	# We might be able to skip the merge
	if {![m mset has $mname]} {
	    # No mirror set of the given name exists.
	if {![m project has $mname]} {
	    # No project of the given name exists.
	    # Create directly in final form. Skip merge.
	    try {
		ImportMake1 $vcode $url $mname
	    } trap {M VCS CHILD} {e o} {
		# Revert creation of mset and repository
		set repo [m rolodex top]
		set mset [m repo mset $repo]
		m repo remove  $repo
		m rolodex drop $repo
		m mset remove  $mset
		m project remove  $mset

		m msg "[color bad {Unable to import}] [color note $mname]: $e"
		# No rethrow, the error in the child is not an error
		# for the whole command. Continue importing the remainder.
	    }
	    return
	}
    }

    # More than a single repository in this set, or the destination
    # mirror set exists. Merging is needed. And the untrusted nature
    # project exists. Merging is needed. And the untrusted nature
    # of the input means that we cannot be sure that merging is even
    # allowed.

    # Two phases:
    # - Create the repositories. Each in its own mirror set, like for `add`.
    #   Set names are of the form `import_<date>`, plus a serial number.
    #   Comes with associated store.
    # - Create the repositories. Each in its own project, like for
    #   `add`.  Project names are of the form `import_<date>`, plus a
    #   serial number.  Comes with associated store.
    #
    # - Go over the repositories again and merge them.  If a
    #   repository is rejected by the merge keep it separate. Retry
    #   merging using the rejections. The number of retries is finite
    #   because each round finalizes at least one mirror set and its
    #   because each round finalizes at least one project and its
    #   repositories of the finite supply. At the end of this phase we
    #   have one or more mirror sets each with maximally merged
    #   repositories. Each finalized mirror set is renamed to final
    #   form, based on the incoming mname and date.
    #   have one or more projects each with maximally merged
    #   repositories. Each finalized project is renamed to final form,
    #   based on the incoming mname and date.

    set serial 0
    set r {}
    foreach {vcode url} $repos {
	try {
	    set tmpname import${date}_[incr serial]
	    set data    [ImportMake1 $vcode $url $tmpname]
	    dict set r $url $data
	} trap {M VCS CHILD} {e o} {
	    # Revert creation of mset and repository
	    set repo [m rolodex top]
	    set mset [m repo mset $repo]
	    m repo remove  $repo
	    m rolodex drop $repo
	    m mset remove  $mset
	    m project remove  $mset

	    m msg "[color bad {Unable to use}] [color note $url]: $e\n"
	    # No rethrow, the error in the child is not an error
	    # for the whole command. Continue importing the remainder.
	}
    }

    if {![dict size $r]} {
	# All inputs fail, report and continue with the remainder
	m msg "[color bad {Unable to import}] [color note $mname]: No repositories"
	return
    }

    set rename 1
    if {[m mset has $mname]} {
	# Targeted mirror set exists. Make it first in the merge list.
	set mset [m mset id $mname]
    if {[m project has $mname]} {
	# Targeted project exists. Make it first in the merge list.
	set mset [m project id $mname]
	set repos [linsert $repos 0 dummy_vcode @$mname]
	dict set r @$mname [list dummy_vcs $mset dummy_store]
	set rename 0
    }

    while on {
	set remainder [lassign $repos v u]
2037
2038
2039
2040
2041
2042
2043
2044
2045


2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058

2059

2060

2061



2062
2063
2064
2065
2066
2067
2068
2069
2070
2071











2072
2073

2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093

2094
2095
2096
2097
2098
2099
2100

2101
2102
2103
2104

2105

2106
2107




2108



2109
2110

2111


2112
2113
2114
2115
2116










2117



2118
2119
2120















































2121


2122
2123
2124




















2125
2126
2127
2128
2129
2130
2131
2182
2183
2184
2185
2186
2187
2188


2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202

2203
2204
2205
2206
2207

2208
2209
2210
2211









2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223

2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243

2244
2245
2246
2247
2248
2249
2250

2251
2252
2253
2254

2255
2256
2257


2258
2259
2260
2261

2262
2263
2264
2265

2266
2267
2268
2269





2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283



2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363







-
-
+
+












-
+

+

+
-
+
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
+



















-
+






-
+



-
+

+
-
-
+
+
+
+
-
+
+
+

-
+

+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







	    Rename $msetp [MakeName $mname]
	    set rename 0
	}

	if {![llength $unmatched]} break

	# Retry to merge the leftovers.  Note, each iteration
	# finalizes at least one mirror set, ensuring termination of
	# the loop.
	# finalizes at least one project, ensuring termination of the
	# loop.
	set repos $unmatched
	set rename 1
    }

    m rolodex commit
    return
}

proc ::m::glue::ImportMake1 {vcode url base} {
    debug.m/glue {[debug caller] | }
    set vcs     [m vcs id $vcode]
    set tmpname [MakeName $base]
    set mset    [m mset add $tmpname]
    set project [m project add $tmpname]
    set url     [m vcs url-norm $vcode $url]
    set vcode   [m vcs code $vcs]

    m msg "> [string totitle $vcode] repository [color note $url]"
    m rolodex push [m repo add $vcs $mset $url]
    
    # -----------------------
    # vcs project url

    m msg "  Setting up the $vcode store for [color note $url] ..."
    lassign [m store add $vcs $mset $tmpname $url] store spent forks
    m msg "  [color note Done] in [color note [m format interval $spent]]"
    if {$forks ne {}} {
	m msg "  Github: Currently tracking [color note [llength $forks]] additional forks"
	foreach f $forks {
	    m msg "  - [color note $f]"
	}
    }
    lassign [AddStoreRepo $vcs $vcode $tmpname $url $project] repo forks
    set store [m repo store $repo]

    # Forks are not processed. It is expected that forks are in the import file.
    # The next update of the primary will link them to the origin.
    set nforks [llength $forks]
    if {$nforks} {
	m msg "  [color warning "Forks found ($nforks), ignored"]"
    }
    
    m rolodex push $repo

    return [list $vcs $mset $store]
    return [list $vcs $project $store]
}

proc ::m::glue::Add {config} {
    debug.m/glue {[debug caller] | }
    set url   [Url $config]
    set vcs   [$config @vcs]
    set vcode [$config @vcs-code]
    set name  [$config @name]
    set url   [m vcs url-norm $vcode $url]
    # __Attention__: Cannot move the url normalization into a
    # when-set clause of the parameter. That generates a
    # dependency cycle:
    #
    #   url <- vcode <- vcs <- url

    m msg "Attempting to add"
    m msg "  Repository [color note $url]"
    m msg "  Managed by [color note [m vcs name $vcs]]"
    m msg "New"
    m msg "  Mirror set [color note $name]"
    m msg "  Project    [color note $name]"

    if {[m repo has $url]} {
	m::cmdr::error \
	    "Repository already present" \
	    HAVE_ALREADY REPOSITORY
    }
    if {[m mset has $name]} {
    if 0 {if {[m project has $name]} {
	m::cmdr::error \
	    "Name already present" \
	    HAVE_ALREADY NAME
    }
    }}

    # Relevant entities
    # TODO MAYBE: stuff how much of this logic into `repo add` ?

    #  1. repository
    #  2. store
    #  3. project
    #
    set mset [m mset add $name]
    # As the repository references the other two these have to be initialized first.
    # The creation of the repository caps the process.
    # Issues roll database changes back.

    m rolodex push [m repo add $vcs $mset $url]
    m msg "Actions ..."

    # ---------------------------------- Project
    if {![m project has $name]} {
    m msg "  Setting up the $vcode store ..."
    lassign [m store add $vcs $mset $name $url] _ spent forks
    
    m rolodex commit
    m msg "  [color note Done] in [color note [m format interval $spent]]"
	m msg* "  Setting up the project ... "
	set project [m project add $name]
	OKx
    } else {
	m msg "  Project is known"
    }

    lassign [AddStoreRepo $vcs $vcode $name $url $project] repo forks

    # ---------------------------------- Forks
    if {$forks ne {}} {
	set threshold 22
	set nforks [llength $forks]
	m msg "Found [color note $nforks] forks to track."
	m msg "  Github: Currently tracking [color note [llength $forks]] additional forks"
	foreach f $forks {
	    m msg "  - [color note $f]"
	
	if {![$config @track-forks] && ($nforks > $threshold)} {
	    m msg [color warning "Auto-tracking threshold of $threshold forks exceeded"]
	    m::cmdr::error "Please confirm using [color note --track-forks] that this many forks should be tracked." \
		TRACKING-THRESHOLD
	}

	AddForks $forks $repo $vcs $vcode $name $project
    }

    # ----------------------------------
    m msg "Setting new primary as current repository"
    
    m rolodex push $repo
    m rolodex commit

    return
}

proc ::m::glue::AddForks {forks repo vcs vcode name project} {
    debug.m/glue {[debug caller] | }

    set nforks [llength $forks]
    set format %-[string length $nforks]d
    set pad [string repeat " " [expr {3+[string length $nforks]}]]
    
    foreach fork $forks {
	incr k
	m msg "  [color cyan "([format $format $k])"] Fork [color note $fork] ... "

	if {[m repo has $fork]} {
	    m msg "  $pad[color note "Already known, claiming it"]"
	    
	    # NOTE: The fork exists in a different project. We
	    # leave that part alone.  The ERD allows that, a fork
	    # and its origin do not have to be in the same
	    # project.
	    
	    m repo claim $repo [m repo id $fork]
	    continue
	}
	
	# Note: Fork urls have to be validated, same as the primary location.
	if {![m url ok $fork xr]} {
	    m msg "  [color warning {Not reachable}], might be private or gone"
	    m msg "  Ignored"
	    continue
	}

	AddStoreRepo $vcs $vcode $name $fork $project $repo
    }
    return
}

proc ::m::glue::AddStoreRepo {vcs vcode name url project {origin {}}} {
    debug.m/glue {[debug caller] | }

    # ---------------------------------- Store
    m msg* "  Setting up the $vcode store ... "
    lassign [m store add $vcs $name $url] \
	store duration commits size forks
    #   id    seconds  int     int  list(url)
    set x [expr {($commits == 1) ? "commit" : "commits"}]
    m msg "[color good OK] in [color note [m format interval $duration]] ($commits $x, $size KB)"
    
    # ---------------------------------- Repository
    
    m msg* "  Creating repository ... "    
    set repo [m repo add $vcs $project $store $url $duration $origin]
    OKx

    return [list $repo $forks]
}

proc ::m::glue::InvalE {label key} {
    set v [m state $key]
    return [list [Inval $label {$v ne {}}] $v]
}

proc ::m::glue::Inval {x isvalid} {
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167







2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179

2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197





2198
2199
2200
2201

2202
2203

2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216

2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231

2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242


2243
2244
2245
2246

2247
2248
2249
2250
2251
2252
2253
2254
2255

2256
2257
2258
2259
2260
2261
2262
2263

2264
2265
2266
2267
2268
2269
2270
2271

2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284

2285
2286
2287
2288
2289
2290
2291
2292

2293
2294
2295

2296
2297
2298

2299
2300

2301
2302
2303
2304
2305


2306
2307

2308
2309
2310
2311


2312
2313
2314
2315
2316
2317
2318
2386
2387
2388
2389
2390
2391
2392







2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410

2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437

2438
2439

2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452

2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467

2468
2469
2470
2471
2472
2473
2474
2475
2476
2477


2478
2479
2480
2481
2482

2483
2484
2485
2486
2487
2488
2489
2490
2491

2492
2493
2494
2495
2496
2497
2498
2499

2500
2501
2502
2503
2504
2505
2506
2507

2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520

2521
2522
2523
2524
2525
2526
2527
2528

2529
2530
2531

2532
2533
2534

2535
2536

2537
2538
2539
2540


2541
2542
2543

2544
2545
2546


2547
2548
2549
2550
2551
2552
2553
2554
2555







-
-
-
-
-
-
-
+
+
+
+
+
+
+











-
+


















+
+
+
+
+



-
+

-
+












-
+














-
+









-
-
+
+



-
+








-
+







-
+







-
+












-
+







-
+


-
+


-
+

-
+



-
-
+
+

-
+


-
-
+
+







	if {$n} {
	    set id -1
	    set series {}
	    foreach r $rolodex {
		incr id
		set rinfo [m repo get $r]
		dict with rinfo {}
		# -> url	: repo url
		#    vcs	: vcs id
		#    vcode	: vcs code
		#    mset	: mirror set id
		#    name	: mirror set name
		#    store  : store id, of backing store for the repo
		
		# -> url    : repo url
		#    vcs    : vcs id
		# -> vcode  : vcs code
		#    project: project id
		# -> name   : project name
		#    store  : id of backing store for repo

		lappend tag @$id
		if {$id == ($n-2)} { lappend tag @p }
		if {$id == ($n-1)} { lappend tag @c }
		lappend series [list $tag $url $name $vcode]
		unset tag
	    }
	}
    }
    if {$n} {
	lassign [TruncW \
		     {{} {} {} {}} \
		     {0 1 3 0} \
		     {0  1  3  0} \
		     $series [$config @tw]] \
	    titles series
	[table t $titles {
	    $t borders 0
	    $t headers 0
	    foreach row $series {
		$t add {*}[C $row 1 note] ;# 1 => url
	    }
	}] show
    }
    return
}

proc ::m::glue::OK {} {
    debug.m/glue {[debug caller] | }
    m msg [color good OK]
    return -code return
}

proc ::m::glue::OKx {} {
    debug.m/glue {[debug caller] | }
    m msg [color good OK]
}

proc ::m::glue::MakeName {prefix} {
    debug.m/glue {[debug caller] | }
    if {![m mset has $prefix]} { return $prefix }
    if {![m project has $prefix]} { return $prefix }
    set n 1
    while {[m mset has ${prefix}#$n]} { incr n }
    while {[m project has ${prefix}#$n]} { incr n }
    return "${prefix}#$n"
}

proc ::m::glue::ComeAroundMail {width current newcycle} {
    debug.m/glue {[debug caller] | }
    package require m::db
    package require m::state
    package require m::store
    package require m::format

    # Get updates and convert into a series for the table. A series we
    # can compress width-wise before formatting.
    set series {}    
    set series {}
    foreach row [m store updates] {
	dict with row {}
	# store mname vcode changed updated created size active remote
	# sizep commits commitp mins maxs lastn
	if {$created eq "."} continue ;# ignore separations
	if {$changed < $current} continue ;# older cycle

	set dcommit [DeltaCommit $commits $commitp]
	set dsize   [DeltaSize $size $sizep]
	set changed [m format epoch/short $changed]
	set spent   [LastTime $lastn]

	lappend series [list $changed $vcode $mname $spent $dsize $dcommit]
    }
    

    lappend mail "\[[info hostname]\] Cycle Report."
    lappend mail "Cycle\nFrom [clock format $current]\nTo   [clock format $newcycle]"
    set n [llength $series]
    if {!$n} {
	lappend mail "Found no changes."
    } else {
	lappend mail "Found @/n/@ changed repositories:\n"

	lassign [TruncW \
		     {Changed VCS {Mirror Set} Time Size Commits} \
		     {0 0 1 0 0 0} \
		     {Changed VCS Project Time Size Commits} \
		     {0       0   1       0    0    0} \
		     $series \
		     $width] \
	    titles series
	

	table t $titles {
	    foreach row $series {
		$t add {*}$row
	    }
	}
	lappend mail [$t show return]
	$t destroy
    }
    

    MailFooter mail
    return [string map [list @/n/@ $n] [join $mail \n]]
}

proc ::m::glue::ComeAround {newcycle} {
    debug.m/glue {[debug caller] | }
    # Called when the update cycle comes around back to the start.
    # Creates a mail reporting on all the mirror sets which where
    # Creates a mail reporting on all the projects which where
    # changed in the previous cycle.

    set current [m state start-of-current-cycle]
    m state start-of-previous-cycle $current
    m state start-of-current-cycle  $newcycle

    m msg "Cycle complete, coming around and starting new ..."
    

    set email [m state report-mail-destination]

    if {$email eq {}} {
	debug.m/glue {[debug caller] | Skipping report without destination}
	# Nobody to report to, skipping report
	m msg "- [color warning {Skipping mail report, no destination}]"
	return
    }

    package require m::mail::generator
    package require m::mailer
    m msg "- [color good "Mailing report to"] [color note $email]"
    

    set comearound [ComeAroundMail [m state mail-width] $current $newcycle]
    m mailer to $email [m mail generator reply $comearound {}]

    m msg [color good OK]
    return
}

proc ::m::glue::UpdateSets {start now msets} {
proc ::m::glue::UpdateRepos {start now repos} {
    debug.m/glue {[debug caller] | }

    set n [llength $msets]
    set n [llength $repos]
    if {$n} {
	# The note below is not shown when the user explicitly
	# specifies the mirror sets to process. Because that is
	# specifies the repositories to process. Because that is
	# outside any cycle.
	return $msets
	return $repos
    }

    set take     [m state take]
    set nmset    [m mset count]
    set npending [m mset count-pending]
    set nrepo    [m repo count]
    set npending [m repo count-pending]

    m msg "In cycle started on [m format epoch $start]: $take/$npending/$nmset"
    m msg "In cycle started on [m format epoch $start]: $take/$npending/$nrepo"

    # No repositories specified.
    # Pull mirror sets directly from pending
    return [m mset take-pending $take \
    # Pull repositories directly from pending
    return [m repo take-pending $take \
		::m::glue::ComeAround $now]
}

proc ::m::glue::Dedup {values} {
    debug.m/glue {[debug caller] | }
    # While keeping the order
    set res {}
2326
2327
2328
2329
2330
2331
2332
2333

2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353


2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367

2368
2369
2370

2371
2372
2373
2374
2375
2376
2377
2378
2379

2380
2381
2382
2383
2384
2385
2386
2387
2388

2389
2390
2391
2392
2393
2394
2395
2563
2564
2565
2566
2567
2568
2569

2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588


2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603

2604
2605
2606

2607
2608
2609
2610
2611
2612
2613
2614
2615

2616
2617
2618
2619
2620
2621
2622
2623
2624

2625
2626
2627
2628
2629
2630
2631
2632







-
+


















-
-
+
+













-
+


-
+








-
+








-
+







}

proc ::m::glue::MergeFill {msets} {
    debug.m/glue {[debug caller] | }
    set n [llength $msets]

    if {!$n} {
	# No mirror sets. Use the mirror sets for current and previous
	# No project. Use the projects for current and previous
	# repository as merge target and source

	set target [m rolodex top]
	if {$target eq {}} {
	    m::cmdr::error \
		"No current repository to indicate merge target" \
		MISSING CURRENT
	}
	set origin [m rolodex next]
	if {$origin eq {}} {
	    m::cmdr::error \
		"No previously current repository to indicate merge source" \
		MISSING PREVIOUS
	}
	lappend msets [m repo mset $target] [m repo mset $origin]
	return $msets
    }
    if {$n == 1} {
	# A single mirror set is the merge origin. Use the mirror set
	# of the current repository as merge target.
	# A single project is the merge origin. Use the project of the
	# current repository as merge target.
	set target [m rolodex top]
	if {$target eq {}} {
	    m::cmdr::error \
		"No current repository to indicate merge target" \
		MISSING CURRENT
	}
	return [linsert $msets 0 [m repo mset $target]]
    }
    return $msets
}

proc ::m::glue::Rename {mset newname} {
    debug.m/glue {[debug caller] | }
    m mset rename $mset $newname
    m project rename $mset $newname

    # TODO MAYBE: stuff cascading logic into `mset rename` ?
    foreach store [m mset stores $mset] {
    foreach store [m project stores $mset] {
	m store rename $store $newname
    }
    return
}

proc ::m::glue::Merge {target origin} {
    debug.m/glue {[debug caller] | }

    # Target and origin are mirror sets.
    # Target and origin are projects
    #
    # - Check that all the origin's repositories fit into the target.
    #   This is done by checking the backing stores of the vcs in use
    #   for compatibility.
    #
    # - When they do the stores are moved or merged, depending on
    # - presence of the associated vcs in the target.

    set vcss [m mset used-vcs $origin]
    set vcss [m project used-vcs $origin]

    # Check that all the origin's repositories fit into the target.
    foreach vcs $vcss {
	# Ignore vcs which are not yet used by the target
	# Assumed to be compatible.
	if {![m store has $vcs $target]} continue

2410
2411
2412
2413
2414
2415
2416
2417
2418


2419
2420
2421
2422
2423
2424
2425
2647
2648
2649
2650
2651
2652
2653


2654
2655
2656
2657
2658
2659
2660
2661
2662







-
-
+
+







	    m store move $ostore $target
	} else {
	    m store merge [m store id $vcs $target] $ostore
	}
    }

    # Move the repositories, drop the origin set, empty after the move
    m repo move/mset $origin $target
    m mset remove    $origin
    m repo move/project $origin $target
    m project remove $origin
    return
}

proc ::m::glue::MailConfigShow {t {prefix {}}} {
    debug.m/glue {[debug caller] | }

    set u [m state mail-user]
2553
2554
2555
2556
2557
2558
2559
2560
2561


2562
2563
2564
2565
2566
2567
2568
2790
2791
2792
2793
2794
2795
2796


2797
2798
2799
2800
2801
2802
2803
2804
2805







-
-
+
+







    return $series
}

##
## TODO column specific minimum widths
## TODO column specific shaving (currently all on the right, urls: left better, or middle)
## TODO column specific shave commands (ex: size rounding)
## TODO 
## TODO 
## TODO
## TODO
##

proc ::m::glue::TruncW {titles weights series width} {
    # series  :: list (row)
    # row     :: list (0..n-1 str)
    # weights :: list (0..k-1 int)
    # titles  :: list (0..n-1 str) - Hard min (for now: include in full width)
2576
2577
2578
2579
2580
2581
2582
2583

2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599

2600
2601
2602
2603
2604
2605
2606
2607
2608
2609

2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620

2621
2622
2623
2624
2625
2626
2627

2628
2629
2630
2631
2632

2633
2634
2635
2636
2637
2638
2639
2640

2641
2642
2643
2644
2645




2646



2647
2648
2649
2650
2651
2652

2653
2654
2655
2656

2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673

2674
2675
2676
2677
2678
2679
2680
2681

2682
2683
2684
2685
2686
2687
2688
2813
2814
2815
2816
2817
2818
2819

2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835

2836
2837
2838
2839
2840
2841
2842
2843
2844
2845

2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856

2857
2858
2859
2860
2861
2862
2863

2864
2865
2866
2867
2868

2869
2870
2871
2872
2873
2874
2875
2876

2877
2878
2879
2880
2881

2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894

2895
2896
2897
2898

2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915

2916
2917
2918
2919
2920
2921
2922
2923

2924
2925
2926
2927
2928
2929
2930
2931







-
+















-
+









-
+










-
+






-
+




-
+







-
+




-
+
+
+
+

+
+
+





-
+



-
+
















-
+







-
+







    set n [llength [lindex $series 0]]
    set k [llength $weights]

    debug.m/glue { terminal     : $width }
    debug.m/glue { len(series)  : [llength $series] }
    debug.m/glue { len(row)     : $n }
    debug.m/glue { len(weights) : $k ($weights)}
    

    if {$n < $k} {
	set d [expr {$k - $n}]
	set weights [lreplace $weights end-$d end]
	# TODO: Check arith (off by x ?)
    }
    if {$n > $k} {
	set d [expr {$n - $k}]
	lappend weights {*}[lrepeat $d 0]
    }

    # Remove table border overhead to get usable terminal space
    set width [expr {$width - (3*$n+1)}]

    debug.m/glue { terminal'    : $width (-[expr {3*$n+1}]) }
    debug.m/glue { weights'     : ($weights)}
    

    # Compute series column widths (max len) for all columns.  If the
    # total width is larger than width we have to shrink by weight.
    # Note: Min column width after shrinking is 6 (because we want to
    # show something for each column).  If shrink by weight goes below
    # this min width bump up to it and remove the needed characters
    # from the weight 0 columns, but not below min width.
    set min 6

    while {$k} { incr k -1 ; set wc($k) 0 }
    

    foreach row [linsert $series 0 $titles] {
	set col 0
	foreach el $row {
	    set n [string length $el]
	    if {$n > $wc($col)} { set wc($col) $n }
	    incr col
	}
    }

    debug.m/glue { col.widths  = [W wc] }
    

    # max width over all rows.

    set fw 0
    foreach {_ v} [array get wc] { incr fw $v }

    debug.m/glue { full        = $fw vs terminal $width }
	

    # Nothing to do if the table fits already
    if {$fw <= $width} { return [list $titles $series] }

    # No fit, start shrinking.
    

    # Sum of weights to apportion
    set tw 0
    foreach w $weights { if {$w <= 0} continue ; incr tw $w }

    # Number of characters over the allowed width.
    set over [expr {$fw - $width}]
    debug.m/glue { over         : $over }
    

    # Shrink columns per weight
    set col 0 ; set removed 0
    foreach w $weights {
	set c $col ; incr col
	if {$w <= 0} continue
	if {$w <= 0} {
	    debug.m/glue { ($col): skip }
	    continue
	}
	set drop [format %.0f [expr {double($over * $w)/$tw}]]

	debug.m/glue { ($col): drop $drop int(($over*$w)/$tw)) }
	
	incr removed $drop
	incr wc($c) -$drop
    }
    # --assert: removed >= over
    debug.m/glue { removed      : $removed }
    # Rounding may cause removed < over, leaving too much chracters behind.
    # Rounding may cause removed < over, leaving too many characters behind.
    # Run a constant shaver, on the weighted cols
    set over [expr {$over - $removed}]
    if {$over} { ShaveWeighted wc $weights $over }
    

    debug.m/glue { col.widths  = [W wc] }

    # If a weighted column has become to small, i.e. less than the
    # allowed min, in the above we bump it back to that width and will
    # shave these then from other columns.
    set col 0
    set under 0
    foreach w $weights {
	set c $col ; incr col
	if {($w <= 0) || ($wc($c) >= $min)} continue
	incr under [expr {$min - $wc($c)}]
	set wc($c) $min
    }

    debug.m/glue { under        : $under }
    debug.m/glue { col.widths  = [W wc] }
    

    # Claw back the added characters from other columns now, as much
    # as we can.  We try to shrink other weighted columns first before
    # goign for the unweighted, i.e. strongly fixed ones.
    if {$under} { set under [ShaveWeighted   wc $weights $under] }
    if {$under} { set under [ShaveUnweighted wc $weights $under] }

    debug.m/glue { col.widths  = [W wc] }
    

    # At last, truncate the series elements to the chosen column
    # widths. Same for the titles.
    set new {}
    foreach row $series {
	set col 0
	set newrow {}
	foreach el $row {
2700
2701
2702
2703
2704
2705
2706
2707

2708
2709
2710
2711
2712
2713
2714
2943
2944
2945
2946
2947
2948
2949

2950
2951
2952
2953
2954
2955
2956
2957







-
+







    foreach el $titles {
	if {[string length $el] > $wc($col)} {
	    set el [string range $el 0 $wc($col)-1]
	}
	lappend newtitles $el
	incr col
    }
    

    return [list $newtitles $new]
}

proc ::m::glue::ShaveWeighted {wv weights shave} {
    set min 6 ;# todo place in common
    upvar 1 $wv wc
    set changed 1
2756
2757
2758
2759
2760
2761
2762


2763
2764
2765
2766


2767
2768
2769

2770
2771
2772
2773

2774
2775
2776
2777
2778
2779


2780
2781
2782
2783
2784
2785
2786
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009


3010
3011

3012

3013




3014

3015
3016
3017


3018
3019
3020
3021
3022
3023
3024
3025
3026







+
+


-
-
+
+
-

-
+
-
-
-
-
+
-



-
-
+
+







    return [m format interval [lindex [split [string trim $lastn ,] ,] end]]
}

proc ::m::glue::StatsTime {mins maxs lastn} {
    set mins [expr {$mins < 0 ? "+Inf" : [m format interval $mins]}]
    set maxs [m format interval $maxs]

    # See also ::m::repo::times, ::m::web::site::Store
    
    append text "$mins ... $maxs"

    set lastn [split [string trim $lastn ,] ,]
    set n     [llength $lastn]
    set lastn [m format win $lastn]
    set n [llength $lastn]

    if {!$n} { return $text }
    

    set maxn [m state store-window-size]
    if {$n > $maxn} {
	set over    [expr {$n - $maxn}]
	set lastn [lreplace $lastn 0 ${over}-1]
    set lastn [m format win-trim $lastn [m state store-window-size]]
    }
    set n       [llength $lastn]
    set total   [expr [join $lastn +]]
    set avg     [m format interval [format %.0f [expr {double($total)/$n}]]]
    
    append text " ($avg * $n)"

    append text " \[avg $avg (over $n)]"
    return $text
}

proc ::m::glue::DeltaSizeFull {current previous} {
    append text [m format size $current]
    if {$previous != $current} {
	if {$current < $previous} {
2824
2825
2826
2827
2828
2829
2830
2831

2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847

2848
2849
2850
2851
2852
2853
3064
3065
3066
3067
3068
3069
3070

3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086

3087
3088
3089
3090
3091
3092
3093







-
+















-
+






    if {$delta < 0} {
	set color bad
    } else {
	# delta > 0
	set color note
	set delta +$delta
    }
    

    append text $current " (" [color $color "$previous ($delta)"] ")"
    return $text
}

proc ::m::glue::DeltaCommit {current previous} {
    if {$previous == $current} { return $current }

    set delta [expr {$current - $previous}]
    if {$delta < 0} {
	set color bad
    } else {
	# delta > 0
	set color note
	set delta +$delta
    }
    

    append text $current " (" [color $color "$delta"] ")"
    return $text
}

# # ## ### ##### ######## ############# ######################
return

Changes to lib/db/db.tcl.

1
2
3
4
5
6
7

8
9
10

11
12

13
14
15
16
17
18
19
1
2
3
4
5
6

7
8
9

10
11

12
13
14
15
16
17
18
19






-
+


-
+

-
+







## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Mirror database - core access and schema

# @@ Meta Begin
# Package m::db 0
# Meta author   {Andreas Kupries}
# Meta author	{Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     Main database access and schema
# Meta summary	   Main database access and schema
# Meta description Main database access and schema
# Meta subject    {database access} schema main
# Meta subject	  {database access} schema main
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide m::db 0

# # ## ### ##### ######## ############# #####################
## Requisites
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
100
101
102
103
104
105
106

107
108
109
110
111
112
113
114







-
+








    I+
    C name  TEXT  NOT NULL  UNIQUE
    T name

    # - -- --- ----- -------- -------------
    ## Mirror Set - Group of repositories holding the same logical set
    ##              of files/content.
    ##		    of files/content.

    I+
    C name  INTEGER  NOT NULL ^name UNIQUE
    T mirror_set

    # - -- --- ----- -------- -------------
    ## Repository - A set of versioned files to back up
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
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







-
+







-
+









-
-
-
-
+
+
+
+



-
+










-
+

-
-
-
+
+
+






-
+







    C vcs   INTEGER  NOT NULL  ^version_control_system
    C mset  INTEGER  NOT NULL  ^mirror_set
    U vcs mset
    T store

    # - -- --- ----- -------- -------------
    ## Version Control System - Applications able to manage
    ##                          repositories
    ##				repositories

    I+
    C code  TEXT  NOT NULL  UNIQUE ; # Short semi-internal tag
    C name  TEXT  NOT NULL  UNIQUE ; # Human readable name
    T version_control_system

    >+ 'fossil' 'Fossil'
    >+ 'git'    'Git'
    >+ 'git'	'Git'

    ## State tables
    # - -- --- ----- -------- -------------
    ## Client state - Named values

    C name   TEXT  NOT NULL  PRIMARY KEY
    C value  TEXT  NOT NULL
    T state

    > 'limit'               '20'              ;# Show this many repositories per `list`
    > 'store'               '~/.mirror/store' ;# Directory for the backend stores
    > 'take'                '5'               ;# Check this many mirrors sets per `update`
    > 'top'                 ''                ;# State for `list`, next repository to show.
    > 'limit'		    '20'	      ;# Show this many repositories per `list`
    > 'store'		    '~/.mirror/store' ;# Directory for the backend stores
    > 'take'		    '5'		      ;# Check this many mirrors sets per `update`
    > 'top'		    ''		      ;# State for `list`, next repository to show.

    # - -- --- ----- -------- -------------
    ## Mirror Set Pending - List of repositories waiting for an update
    ##                      to process them
    ##			    to process them

    C mset  INTEGER  NOT NULL  ^mirror_set  PRIMARY KEY
    T mset_pending

    # - -- --- ----- -------- -------------
    ## Store Times - Per store the times of last update and change
    #
    # Notes on the recorded times:
    #
    # - Invariant: changed <= updated
    #   Because not every update causes a change.
    #	Because not every update causes a change.

    C store    INTEGER  NOT NULL  ^store PRIMARY KEY
    C updated  INTEGER  NOT NULL
    C changed  INTEGER  NOT NULL
    C store    INTEGER	NOT NULL  ^store PRIMARY KEY
    C updated  INTEGER	NOT NULL
    C changed  INTEGER	NOT NULL
    T store_times

    # - -- --- ----- -------- -------------
    ## Rolodex - Short hand references to recently seen repositories

    I
    C repository INTEGER NOT NULL  ^repository  UNIQUE
    C repository INTEGER NOT NULL  ^repository	UNIQUE
    T rolodex

    # - -- --- ----- -------- -------------
    return
}

proc ::m::db::SETUP-201810092200 {} {
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
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







-
+


-
+


-
+






-
-
-
-
+
+
+
+

-
+













-
-
+
+

-
+

-
+

-
-
+
+

-
+

-
-
-
-
+
+
+
+












-
-
+
+

-
+






-
+















-
-
+
+

-
+


















-
-
+
+
















-
-
+
+

-
-
-
-
+
+
+
+











-
+













-
-
-
+
+
+







proc ::m::db::SETUP-201810111600 {} {
    debug.m/db {}
    # Added column `created` to `store_times`
    #
    # Notes on the recorded times:
    #
    # - Invariant: changed <= updated
    #   Because not every update causes a change.
    #	Because not every update causes a change.
    #
    # - Invariant: created <= changed
    #   Because a change can happen only after we have store
    #	Because a change can happen only after we have store
    #
    # - (created == changed)
    #   -> Never seen any change for this store.
    #	-> Never seen any change for this store.
    #
    # Overall
    #		created <= changed <= updated

    D m::db
    # - -- --- ----- -------- -------------
    C store    INTEGER  NOT NULL  ^store PRIMARY KEY
    C created  INTEGER  NOT NULL
    C updated  INTEGER  NOT NULL
    C changed  INTEGER  NOT NULL
    C store    INTEGER	NOT NULL  ^store PRIMARY KEY
    C created  INTEGER	NOT NULL
    C updated  INTEGER	NOT NULL
    C changed  INTEGER	NOT NULL
    < store_times  store updated updated changed
    #                    ^ use last update as fake creation
    #			 ^ use last update as fake creation

    return
}

proc ::m::db::SETUP-201810121600 {} {
    debug.m/db {}
    # Added mail configuration to the general state table

    set h {This is a semi-automated mail by @cmd@, on behalf of @sender@.}

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #                           -- Debugging
    > 'mail-debug'  '0'         ;# Bool. Activates low-level debugging in smtp/mime
    #				-- Debugging
    > 'mail-debug'  '0'		;# Bool. Activates low-level debugging in smtp/mime

    #                           -- SMTP configuration
    #				-- SMTP configuration
    > 'mail-host'   'localhost' ;# Name of the mail-relay host to talk to
    > 'mail-port'   '25'        ;# Port where the mail-relay host accepts SMTP
    > 'mail-port'   '25'	;# Port where the mail-relay host accepts SMTP
    > 'mail-user'   'undefined' ;# account accepted by the mail-relay host
    > 'mail-pass'   ''          ;# and associated credentials
    > 'mail-tls'    '0'         ;# Bool. Activates TLS to secure SMTP transactions
    > 'mail-pass'   ''		;# and associated credentials
    > 'mail-tls'    '0'		;# Bool. Activates TLS to secure SMTP transactions

    #                           -- Mail content configuration
    #				-- Mail content configuration
    > 'mail-sender' 'undefined' ;# Email address to place into From/Sender headers
    > 'mail-header' '$h'        ;# Text to place before the generated content
    > 'mail-footer' ''          ;# Text to place after the generated content
    #                            # Note: Template processing happens after the content
    #                            # is assembled, i.e. affects header and footer.
    > 'mail-header' '$h'	;# Text to place before the generated content
    > 'mail-footer' ''		;# Text to place after the generated content
    #				 # Note: Template processing happens after the content
    #				 # is assembled, i.e. affects header and footer.

    return
}

proc ::m::db::SETUP-201810131603 {} {
    debug.m/db {}
    # Add tables for rejection mail content
    # (submission processing)

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C name      TEXT    NOT NULL UNIQUE
    C automail  INTEGER NOT NULL
    C name	TEXT	NOT NULL UNIQUE
    C automail	INTEGER NOT NULL
    C isdefault INTEGER NOT NULL
    C text      TEXT    NOT NULL
    C text	TEXT	NOT NULL
    T reply

    set sm "It is spam"
    set om "It is off-topic here"
    set rm "It was intentionally removed before and we will not add it again"

    >+ 'spam'     0 1 '$sm' ;# default reason
    >+ 'spam'	  0 1 '$sm' ;# default reason
    >+ 'offtopic' 1 0 '$om'
    >+ 'removed'  1 0 '$rm'

    return
}

proc ::m::db::SETUP-201810141600 {} {
    debug.m/db {}
    # Add tables for external submissions
    # - submissions
    # - rejected submissions (for easy auto-rejection on replication)

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C url       TEXT NOT NULL UNIQUE
    C email     TEXT NOT NULL
    C url	TEXT NOT NULL UNIQUE
    C email	TEXT NOT NULL
    C submitter TEXT
    C sdate     INTEGER NOT NULL
    C sdate	INTEGER NOT NULL
    T submission
    X sdate

    I+
    C url    TEXT NOT NULL UNIQUE
    C reason TEXT NOT NULL
    T rejected

    return
}

proc ::m::db::SETUP-201810311600 {} {
    debug.m/db {}
    # Added column `size_kb` for store size to `store`.

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C vcs     INTEGER  NOT NULL  ^version_control_system
    C mset    INTEGER  NOT NULL  ^mirror_set
    C vcs     INTEGER  NOT NULL	 ^version_control_system
    C mset    INTEGER  NOT NULL	 ^mirror_set
    C size_kb INTEGER  NOT NULL
    U vcs mset
    < store  id vcs mset '0'

    package require m::store
    m::store::InitialSizes
    return
}

proc ::m::db::SETUP-201811152300 {} {
    debug.m/db {}
    # Added site configuration to the general state table

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #                           -- Debugging
    > 'site-active'   '0'              ;# Site status (active or not)
    #				-- Debugging
    > 'site-active'   '0'	       ;# Site status (active or not)
    > 'site-store'    '~/.mirror/site' ;# Location where website is generated
    > 'site-mgr-mail' ''               ;# Mail address of the site manager
    > 'site-mgr-name' ''               ;# Name of the site manager
    > 'site-title'    'Mirror'         ;# Name of the site
    > 'site-url'      ''               ;# The url the site will be published at
    > 'site-mgr-mail' ''	       ;# Mail address of the site manager
    > 'site-mgr-name' ''	       ;# Name of the site manager
    > 'site-title'    'Mirror'	       ;# Name of the site
    > 'site-url'      ''	       ;# The url the site will be published at

    return
}

proc ::m::db::SETUP-201811162301 {} {
    debug.m/db {}
    # Added more site configuration to the general state table

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #                           -- Debugging
    #				-- Debugging
    > 'site-logo' '' ;# Path or url to the site logo.

    return
}

proc ::m::db::SETUP-201811202300 {} {
    debug.m/db {}
    # Added flag 'active' to repository.
    # Default: yes.

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C url    TEXT     NOT NULL  UNIQUE
    C vcs    INTEGER  NOT NULL  ^version_control_system
    C mset   INTEGER  NOT NULL  ^mirror_set
    C url    TEXT     NOT NULL	UNIQUE
    C vcs    INTEGER  NOT NULL	^version_control_system
    C mset   INTEGER  NOT NULL	^mirror_set
    C active INTEGER  NOT NULL
    < repository  id url vcs mset '1'
    X vcs mset

    return
}

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
503
504
505






506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526


527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544

545
546
547
548
549
550
551
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524


525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543

544
545
546
547
548
549
550
551







-
-
+
+

-
-
-
+
+
+


















-
-
-
+
+
+

-
-
-
+
+
+

















-
-
+
+














-
-
-
-
-
+
+
+
+
+

















-
-
-
-
-
+
+
+
+
+






-
+




-
-
-
-
-
-
+
+
+
+
+
+



















-
-
+
+

















-
+







    # submissions table. Initialized to empty. Further dropped unique
    # requirement from url, allowing multiple submissions of the same,
    # enabling fixing of description, vcode. Added index instead.

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C url         TEXT NOT NULL
    C vcode       TEXT
    C url	  TEXT NOT NULL
    C vcode	  TEXT
    C description TEXT
    C email       TEXT NOT NULL
    C submitter   TEXT
    C sdate       INTEGER NOT NULL
    C email	  TEXT NOT NULL
    C submitter	  TEXT
    C sdate	  INTEGER NOT NULL
    < submission  id url '' '' email submitter sdate
    X sdate
    X url

    return
}

proc ::m::db::SETUP-201811282200 {} {
    debug.m/db {}
    # Added special column `session` to the submissions
    # table. Initialized to a value the other parts (cli, CGI) will
    # not generate.  Made url + session unique, i.e. primary key.  A
    # session is allowed to overwrite its submissions, but not the
    # submissions of other sessions.

    D m::db
    # - -- --- ----- -------- -------------
    I+
    C session     TEXT NOT NULL
    C url         TEXT NOT NULL
    C vcode       TEXT
    C session	  TEXT NOT NULL
    C url	  TEXT NOT NULL
    C vcode	  TEXT
    C description TEXT
    C email       TEXT NOT NULL
    C submitter   TEXT
    C sdate       INTEGER NOT NULL
    C email	  TEXT NOT NULL
    C submitter	  TEXT
    C sdate	  INTEGER NOT NULL
    U session url
    < submission  id ':lock:' url vcode description email submitter sdate
    X sdate
    X url

    return
}

proc ::m::db::SETUP-201812042200 {} {
    debug.m/db {}
    # Added sync helper table.
    # Remember all submissions handled locally (accepted or rejected),
    # for deletion from the CGI site database on next sync. Note that
    # we only need the key information, i.e. url + session id.

    D m::db
    # - -- --- ----- -------- -------------
    C session     TEXT NOT NULL
    C url         TEXT NOT NULL
    C session	  TEXT NOT NULL
    C url	  TEXT NOT NULL
    U session url
    T submission_handled

    return
}

proc ::m::db::SETUP-201901112300 {} {
    debug.m/db {}
    # Added column `attend` to `store_times`.
    # Column records presence of issues in the
    # last update for the store.

    D m::db
    # - -- --- ----- -------- -------------
    C store    INTEGER  NOT NULL  ^store PRIMARY KEY
    C created  INTEGER  NOT NULL
    C updated  INTEGER  NOT NULL
    C changed  INTEGER  NOT NULL
    C attend   INTEGER  NOT NULL
    C store    INTEGER	NOT NULL  ^store PRIMARY KEY
    C created  INTEGER	NOT NULL
    C updated  INTEGER	NOT NULL
    C changed  INTEGER	NOT NULL
    C attend   INTEGER	NOT NULL
    < store_times  store updated updated changed '0'
    # fake "no issues" during creation ...........^

    package require m::store
    m::store::InitialIssues
    return
}

proc ::m::db::SETUP-201901222300 {} {
    debug.m/db {}

    # Extended the tables `store` and `store_times` with columns to
    # track size changes (KB, #revisions) and time statistics for
    # updates.

    D m::db
    # - -- --- ----- -------- -------------
    C store          INTEGER  NOT NULL  ^store PRIMARY KEY
    C created        INTEGER  NOT NULL
    C updated        INTEGER  NOT NULL
    C changed        INTEGER  NOT NULL
    C attend         INTEGER  NOT NULL
    C store	     INTEGER  NOT NULL	^store PRIMARY KEY
    C created	     INTEGER  NOT NULL
    C updated	     INTEGER  NOT NULL
    C changed	     INTEGER  NOT NULL
    C attend	     INTEGER  NOT NULL
    C min_seconds    INTEGER  NOT NULL ;# overall minimum time spent on update
    C max_seconds    INTEGER  NOT NULL ;# overall maximum time spent on update
    C window_seconds STRING   NOT NULL ;# time spent on last N updates (list of int)
    < store_times  store updated updated changed attend '-1' '0' ''

    # Note: A min_seconds value of -1 represents +Infinity.
    

    T^ state
    > 'store-window-size' '10' ;# Window size for `store.window_seconds`

    I+
    C vcs              INTEGER  NOT NULL  ^version_control_system
    C mset             INTEGER  NOT NULL  ^mirror_set
    C size_kb          INTEGER  NOT NULL
    C size_previous    INTEGER  NOT NULL
    C commits_current  INTEGER  NOT NULL
    C commits_previous INTEGER  NOT NULL
    C vcs	       INTEGER	NOT NULL  ^version_control_system
    C mset	       INTEGER	NOT NULL  ^mirror_set
    C size_kb	       INTEGER	NOT NULL
    C size_previous    INTEGER	NOT NULL
    C commits_current  INTEGER	NOT NULL
    C commits_previous INTEGER	NOT NULL
    U vcs mset
    < store  id vcs mset size_kb size_kb '0' '0'

    package require m::store
    m::store::InitialCommits
    return
}

proc ::m::db::SETUP-201901242300 {} {
    debug.m/db {}

    # New table `store_github_forks`. Adjunct to table `store`, like
    # `store_times`. Difference: Not general, specific to github
    # stores. Tracks the number of forks. Primary source is the local
    # git repository, the information in the table is derived. Used for
    # easier access to statistics (size x forks ~?~ update time).

    D m::db
    # - -- --- ----- -------- -------------
    C store          INTEGER  NOT NULL  ^store PRIMARY KEY
    C nforks         INTEGER  NOT NULL
    C store	     INTEGER  NOT NULL	^store PRIMARY KEY
    C nforks	     INTEGER  NOT NULL
    T store_github_forks

    package require m::store
    m::store::InitialForks
    return
}

proc ::m::db::SETUP-201901252300 {} {
    debug.m/db {}
    # Added more state, start of the current cycle.

    D m::db
    # - -- --- ----- -------- -------------
    T^ state

    > 'start-of-current-cycle' '[clock seconds]' ;# As epoch
    #  Fake start for now, self corrects when it comes around.
    

    return
}

proc ::m::db::SETUP-201901252301 {} {
    debug.m/db {}
    # And (local) email address for reporting

565
566
567
568
569
570
571
572

573
574
575
576
577
578
579
565
566
567
568
569
570
571

572
573
574
575
576
577
578
579







-
+







    D m::db
    # - -- --- ----- -------- -------------
    T^ state

    > 'start-of-previous-cycle' '[clock seconds]' ;# As epoch
    #  Fake start for now, self corrects when it comes around
    #  next time.
    

    return
}

proc ::m::db::SETUP-201902052301 {} {
    debug.m/db {}
    # Added `svn` to the set of supported VCS.

588
589
590
591
592
593
594
595
596


597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617

618
619
620
621











































































































































622
623
624
625
626
588
589
590
591
592
593
594


595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616

617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765







-
-
+
+




















-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





proc ::m::db::SETUP-201910031116 {} {
    debug.m/db {}
    # Extended mail configuration, width to use for tables.

    D m::db
    # - -- --- ----- -------- -------------
    T^ state
    #                           -- Mail content configuration
    > 'mail-width' '200'        ;# Width of tables placed into content
    #				-- Mail content configuration
    > 'mail-width' '200'	;# Width of tables placed into content

    return
}

proc ::m::db::SETUP-201910032120 {} {
    debug.m/db {}

    # Drop table `name` as superfluous. The only user of this
    # information is table `mirror_set`. Fold the data into a
    # modified `mirror_set`. Further drop the auto-increment.

    D m::db
    # - -- --- ----- -------- -------------

    I
    C name  TEXT  NOT NULL  UNIQUE

    <= mirror_set {
	SELECT M.id
	,      N.name
	FROM @@   M
	FROM @@	  M
	,    name N
	WHERE M.name = N.id
    }

    / name

    return
}

proc ::m::db::SETUP-202207020000 {} {
    debug.m/db {}

    D m::db
    # - -- --- ----- -------- -------------

    # Move to schema V3
    # - The main change is the explicit representation and handling of
    #	forks.
    # - A number of small changes renaming and moving various tables
    #	and fields.

    # - -- --- ----- -------- -------------
    # Drop `mset_pending`, and replace with proper `repo_pending`.
    #
    ## Repository Pending - List of repositories waiting for an update
    ##			    to process them

    C repository  INTEGER  NOT NULL  ^repository  PRIMARY KEY
    T repo_pending

    # - -- --- ----- -------- -------------
    ## Rename `mirror_set` to `project` as a more suitable name.

    R "ALTER TABLE mirror_set RENAME TO project"

    # - -- --- ----- -------- -------------
    ## Redo the repositories
    #
    ## - Rename `mset` to `project
    ## - Add store_times.*_seconds
    ## - Add store reference
    ## - Add checked stamp
    ## - Drop
    #
    ## ATTENTION -- This is done before updating the store schema
    ## because the code to find and link the store requires the mset
    ## reference to be dropped.

    I+
    C url	      TEXT     NOT NULL	 UNIQUE
    C project	      INTEGER  NOT NULL	 ^project
    C vcs	      INTEGER  NOT NULL	 ^version_control_system
    C store	      INTEGER  NOT NULL	 ^store
    C fork_origin     INTEGER		 ^repository
    C is_active	      INTEGER  NOT NULL
    C has_issues      INTEGER  NOT NULL
    C checked	      INTEGER  NOT NULL ;# epoch
    C min_duration    INTEGER  NOT NULL ;# overall minimum time spent on update
    C max_duration    INTEGER  NOT NULL ;# overall maximum time spent on update
    C window_duration STRING   NOT NULL ;# time spent on last N updates (list of int)
    < repository id url mset vcs -1    NULL active 0    0   0   0   ''
    #               url proj vcs store fork act    issu chk min max win
    
    # Store linkage and store_times related information needs code.
    foreach {repo mset vcs url} [R {
	SELECT id
	,      project
	,      vcs
	,      url
	FROM   repository
    }] {
	# Locate store for repository
	set store [R [string map [list :mset $mset :vcs $vcs] {
	    SELECT id
	    FROM   store
	    WHERE  mset = :mset
	    AND	   vcs	= :vcs
	}]]

	lassign [R [string map [list :store $store] {
	    SELECT mset, vcs
	    FROM store
	    WHERE id = :store
	}]] msets vcss
	
	#puts stderr "XXX repo = $url/$mset/$vcs => S$store/$msets/$vcss"
	
	# Get time information
	lassign [R [string map [list :store $store] {
	    SELECT min_seconds
	    ,	   max_seconds
	    ,	   window_seconds
	    FROM   store_times
	    WHERE  store = :store
	}]] min max win

	# update repository with store and times
	R [string map [list :id $repo :min $min :max $max :win $win :store $store] {
	    UPDATE repository
	    SET store		= :store
	    ,	min_duration	= :min
	    ,	max_duration	= :max
	    ,	window_duration = ':win'
	    WHERE id = :id
	}]
    }

    # - -- --- ----- -------- -------------
    ## Redo the stores
    ## - Drop project reference, add various store_times fields.

    I+
    C vcs	       INTEGER	NOT NULL  ^version_control_system
    C size_kb	       INTEGER	NOT NULL
    C size_previous    INTEGER	NOT NULL
    C commits_current  INTEGER	NOT NULL
    C commits_previous INTEGER	NOT NULL
    C created	       INTEGER	NOT NULL
    C updated	       INTEGER	NOT NULL
    C changed	       INTEGER	NOT NULL
    <= store {
	SELECT S.id
	,      S.vcs
	,      S.size_kb
	,      S.size_previous
	,      S.commits_current
	,      S.commits_previous
	,      T.created
	,      T.updated
	,      T.changed
	FROM  @@	  S
	,     store_times T
	WHERE T.store = S.id
    }

    # - -- --- ----- -------- -------------
    ## Drop various tables which became superfluous due to the
    ## preceding changes.

    / mset_pending
    / store_github_forks
    / store_times

    return
}

# # ## ### ##### ######## ############# #####################
return

Changes to lib/db/site.tcl.

247
248
249
250
251
252
253

































254
255
256
257
258
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





    C code  TEXT  NOT NULL  UNIQUE ; # Short semi-internal tag
    C name  TEXT  NOT NULL  UNIQUE ; # Human readable name
    T vcs

    # No fixed values here. Copy from main table
    # `version_control_system` on sync.
    
    return
}

proc ::m::site::SETUP-202207020000 {} {
    debug.m/db {}

    D m::site
    # - -- --- ----- -------- -------------

    # Move to schema V3
    # - It is now possible to have multiple stores of the same
    #   kind for a project. Due to forks of a github repository
    #   being explicit, with separate stores.
    #
    # => Drop constraint UNIQUE(name, vcode)

    I+
    C name     TEXT     NOT NULL
    C vcode    TEXT     NOT NULL
    C page     TEXT     NOT NULL  UNIQUE
    C remotes  TEXT     NOT NULL
    C status   TEXT     NOT NULL -- icon name
    C size_kb  INTEGER  NOT NULL
    C changed  INTEGER  NOT NULL
    C updated  INTEGER  NOT NULL
    C created  INTEGER  NOT NULL

    < store_index  \
	id name vcode page remotes status size_kb changed updated created

    X name
    X remotes

    return
}

# # ## ### ##### ######## ############# #####################
return

Deleted lib/logic/mset.tcl.

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





















































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################

# @@ Meta Begin
# Package m::mset 0
# Meta author      {Andreas Kupries}
# Meta category    ?
# Meta description ?
# Meta location    https://core.tcl-lang.org/akupries/m
# Meta platform    tcl
# Meta require     ?
# Meta subject     ?
# Meta summary     ?
# @@ Meta End

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

package require Tcl 8.5
package require m::db
package require m::repo
package require m::rolodex
package require debug
package require debug::caller

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

namespace eval ::m {
    namespace export mset
    namespace ensemble create
}
namespace eval ::m::mset {
    namespace export \
	all add remove rename has \
	name used-vcs has-vcs size \
	stores take-pending pending known \
	repos spec id count count-pending
    namespace ensemble create
}

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

debug level  m/mset
debug prefix m/mset {[debug caller] | }

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

proc ::m::mset::spec {} {
    debug.m/mset {}

    set lines {}
    foreach {mset mname} [all] {
	foreach repo [repos $mset] {
	    set ri [m repo get $repo]
	    dict with ri {}
	    # -> url	: repo url
	    #    vcs	: vcs id
	    # -> vcode	: vcs code
	    #    mset	: mirror set id
	    #    name	: mirror set name
	    #    store  : store id, of backing store for
	    lappend lines [list R $vcode $url]
	}
	lappend lines [list M $mname]
    }
    return [join $lines \n]
}

proc ::m::mset::known {} {
    debug.m/mset {}

    # Return map to mirror set ids.
    # Keys:
    # - rolodex ids (+ '@current', '@', '@prev')
    # - repository urls
    # - mirror set names

    set map {}
    set mid {}

    # Repository and mirror set information in one trip.
    m db eval {
	SELECT M.id   AS id
	,      M.name AS name
	,      R.id   AS rid
	,      R.url  AS url
	FROM   repository R
	,      mirror_set M
	WHERE  R.mset = M.id
    } {
	dict set mid $rid $id
	dict set map [string tolower $url]  $id
	dict set map [string tolower $name] $id
    }

    # See also m::repo::known
    # Note, different ids! mset, not repo.
    set c {}
    set p {}
    set id -1
    foreach r [m rolodex get] {
	set p $c ; set c $r ; incr id
	dict set map "@$id" [dict get $mid $r]
    }
    if {$p ne {}} {
	set p [dict get $mid $p]
	dict set map @prev $p
	dict set map @-1   $p
    }
    if {$c ne {}} {
	set c [dict get $mid $c]
	dict set map @current $c
	dict set map @        $c
    }

    return $map
}

proc ::m::mset::all {} {
    debug.m/mset {}
    return [m db eval {
	SELECT id
	,      name
	FROM   mirror_set
	ORDER BY name ASC
    }]
}

proc ::m::mset::id {name} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT id
	FROM   mirror_set
	WHERE  name = :name
    }]
}

proc ::m::mset::count {} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   mirror_set
    }]
}

proc ::m::mset::count-pending {} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   mset_pending
    }]
}

proc ::m::mset::add {name} {
    debug.m/mset {}

    m db eval {
	INSERT
	INTO mirror_set
	VALUES ( NULL, :name )
    }

    set mset [m db last_insert_rowid]

    m db eval {
	INSERT
	INTO mset_pending
	VALUES ( :mset )
    }

    return $mset
}

proc ::m::mset::remove {mset} {
    debug.m/mset {}

    # TODO FILL mset/remove -- Verify that the mset has no references
    # anymore, from neither repositories nor stores

    return [m db eval {
	DELETE
	FROM  mirror_set
	WHERE id = :mset
	;
	DELETE
	FROM  mset_pending
	WHERE mset = :mset
    }]
}

proc ::m::mset::rename {mset name} {
    debug.m/mset {}
    m db eval {
	UPDATE mirror_set
	SET    name = :name
	WHERE  id   = :mset
    }
    return
}

proc ::m::mset::has {name} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   mirror_set
	WHERE  name = :name
    }]
}

proc ::m::mset::stores {mset} {
    debug.m/mset {}
    return [m db eval {
	SELECT id
	FROM   store
	WHERE  mset = :mset
    }]
}

proc ::m::mset::repos {mset} {
    debug.m/mset {}
    return [m db eval {
	SELECT id
	FROM   repository
	WHERE  mset = :mset
    }]
}

proc ::m::mset::used-vcs {mset} {
    debug.m/mset {}
    return [m db eval {
	SELECT DISTINCT vcs
	FROM   repository
	WHERE  mset = :mset
    }]
}

proc ::m::mset::size {mset} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
	WHERE  mset = :mset
    }]
}

proc ::m::mset::has-vcs {mset vcs} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
	WHERE  mset = :mset
	AND    vcs  = :vcs
    }]
}

proc ::m::mset::name {mset} {
    debug.m/mset {}
    return [m db onecolumn {
	SELECT name
	FROM   mirror_set
	WHERE  id = :mset
    }]
}

proc ::m::mset::pending {} {
    debug.m/mset {}
    return [m db eval {
	SELECT name
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = P.mset
		AND   R.active) AS arc
	FROM   mset_pending P
	,      mirror_set   M
	WHERE P.mset = M.id
	AND   arc > 0
	ORDER BY P.ROWID
    }]
}

proc ::m::mset::take-pending {take args} {
    debug.m/mset {}

    # Ask for one more than actually request. This will cause a
    # short-read (with refill) not only when the table contains less
    # than take elements, but also when it contains exactly that many.
    # If the read is not short we know that at least one element is
    # left.
    incr take

    set taken [m db eval {
	SELECT P.mset
	FROM   mset_pending P
	WHERE (SELECT count (*)
	       FROM  repository R
	       WHERE R.mset = P.mset
	       AND   R.active) > 0
	LIMIT :take
    }]
    if {[llength $taken] < $take} {
	# Short read. Clear taken (fast), and refill for next
	# invokation.
	m db eval {
	    DELETE
	    FROM   mset_pending
	    ;
	    INSERT
	    INTO   mset_pending
	    SELECT id
	    FROM   mirror_set
	}

	if {[llength $args]} {
	    # Invoke callback to report that the overall cycle just
	    # came around and started anew.
	    try {
		uplevel 1 $args
	    } on error {e o} {
		# TODO -- Report (internal) error, but do not crash.
	    }
	}
    } else {
	# Full read. Clear taken, the slow way.  Drop the unwanted
	# sentinel element from the end of the result.
	set taken [lreplace [K $taken [unset taken]] end end]
	m db eval [string map [list %% [join $taken ,]] {
	    DELETE
	    FROM mset_pending
	    WHERE mset in (%%)
	}]
    }

    return $taken
}

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

proc ::m::mset::K {x y} { set x }

# # ## ### ##### ######## ############# ######################
package provide m::mset 0
return

Added lib/logic/project.tcl.



















































































































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################

# @@ Meta Begin
# Package m::project 0
# Meta author      {Andreas Kupries}
# Meta category    ?
# Meta description ?
# Meta location    https://core.tcl-lang.org/akupries/m
# Meta platform    tcl
# Meta require     ?
# Meta subject     ?
# Meta summary     ?
# @@ Meta End

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

package require Tcl 8.5
package require m::db
package require m::repo
package require m::rolodex
package require debug
package require debug::caller

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

namespace eval ::m {
    namespace export project
    namespace ensemble create
}
namespace eval ::m::project {
    namespace export \
	all add remove rename has \
	name used-vcs has-vcs size \
	stores known spec id count
    namespace ensemble create
}

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

debug level  m/project
debug prefix m/project {[debug caller] | }

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

proc ::m::project::spec {} {
    debug.m/project {}

    set lines {}
    foreach {project pname} [all] {
	foreach repo [m repo for $project] {
	    set ri [m repo get $repo]
	    dict with ri {}
	    # -> url	: repo url
	    #    vcs	: vcs id
	    # -> vcode	: vcs code
	    #    project: project id
	    #    name	: project name
	    #    store  : id of backing store for repo
	    lappend lines [list R $vcode $url]
	}
	lappend lines [list P $pname]
    }
    return [join $lines \n]
}

proc ::m::project::known {} {
    debug.m/project {}

    # Return map to project ids.
    # Keys:
    # - rolodex ids (+ '@current', '@', '@prev')
    # - repository urls
    # - project names

    set map {}
    set mid {}

    # Repository and project information in one trip.
    m db eval {
	SELECT P.id   AS id
	,      P.name AS name
	,      R.id   AS rid
	,      R.url  AS url
	FROM   repository R
	,      project    P
	WHERE  R.project = P.id
    } {
	dict set mid $rid $id
	dict set map [string tolower $url]  $id
	dict set map [string tolower $name] $id
    }

    # See also m::repo::known
    # Note, different ids! project, not repo.
    set c {}
    set p {}
    set id -1
    foreach r [m rolodex get] {
	set p $c ; set c $r ; incr id
	dict set map "@$id" [dict get $mid $r]
    }
    if {$p ne {}} {
	set p [dict get $mid $p]
	dict set map @prev $p
	dict set map @-1   $p
    }
    if {$c ne {}} {
	set c [dict get $mid $c]
	dict set map @current $c
	dict set map @        $c
    }

    return $map
}

proc ::m::project::all {} {
    debug.m/project {}
    return [m db eval {
	SELECT id
	,      name
	FROM   project
	ORDER BY name ASC
    }]
}

proc ::m::project::id {name} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT id
	FROM   project
	WHERE  name = :name
    }]
}

proc ::m::project::count {} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   project
    }]
}

proc ::m::project::add {name} {
    debug.m/project {}

    m db eval {
	INSERT
	INTO project
	VALUES ( NULL, :name )
    }

    return [m db last_insert_rowid]
}

proc ::m::project::remove {project} {
    debug.m/project {}

    # TODO FILL project/remove -- Verify that the project has no references
    # anymore, from neither repositories nor stores

    return [m db eval {
	DELETE
	FROM  project
	WHERE id = :project
    }]
}

proc ::m::project::rename {project name} {
    debug.m/project {}
    m db eval {
	UPDATE project
	SET    name = :name
	WHERE  id   = :project
    }
    return
}

proc ::m::project::has {name} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   project
	WHERE  name = :name
    }]
}

proc ::m::project::stores {project} {
    debug.m/project {}
    return [m db eval {
	SELECT S.id
	FROM   store      S
	,      repository R
	WHERE  S.id = R.store
	AND    R.project = :project
    }]
}

proc ::m::project::used-vcs {project} {
    debug.m/project {}
    return [m db eval {
	SELECT DISTINCT vcs
	FROM   repository
	WHERE  project = :project
    }]
}

proc ::m::project::size {project} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
	WHERE  project = :project
    }]
}

proc ::m::project::has-vcs {project vcs} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
	WHERE  project = :project
	AND    vcs     = :vcs
    }]
}

proc ::m::project::name {project} {
    debug.m/project {}
    return [m db onecolumn {
	SELECT name
	FROM   project
	WHERE  id = :project
    }]
}

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

proc ::m::project::K {x y} { set x }

# # ## ### ##### ######## ############# ######################
package provide m::project 0
return

Changes to lib/logic/repo.tcl.

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







+











-
-
+
+
+
+







#         ( sizep, commits, commitp, mins, maxs, lastn )

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

package require Tcl 8.5
package require m::state
package require m::rolodex
package require m::format
package require debug
package require debug::caller

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

namespace eval ::m {
    namespace export repo
    namespace ensemble create
}
namespace eval ::m::repo {
    namespace export \
	add remove enable move/mset move/1 has get name \
	known get-n mset search id count
	add remove enable move/project move/1 has get name \
	store known get-n for forks project search id count \
	claim count-pending add-pending drop-pending pending \
	take-pending declaim times fork-locations
    namespace ensemble create
}

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

debug level  m/repo
debug prefix m/repo {[debug caller] | }
59
60
61
62
63
64
65
66
67


68
69
70
71
72
73
74
62
63
64
65
66
67
68


69
70
71
72
73
74
75
76
77







-
-
+
+







	SELECT id
	,      url
	FROM   repository
    } {
	dict set map [string tolower $url] $id
    }

    # See also m::mset::known
    # Note, different ids! repo, not mset
    # See also m::project::known
    # Note, different ids! repository, not project
    set c {}
    set p {}
    set id -1
    foreach r [m rolodex get] {
	set p $c ; set c $r ; incr id
	dict set map "@$id" $r
    }
82
83
84
85
86
87
88
89

90
91

92
93

94
95

96
97
98
99
100
101
102
85
86
87
88
89
90
91

92
93

94
95

96
97

98
99
100
101
102
103
104
105







-
+

-
+

-
+

-
+







    }
    
    return $map
}

proc ::m::repo::name {repo} {
    debug.m/repo {}
    # TODO MAYBE - repo name - cache?    
    # TODO MAYBE - in-memory cache of mapping repo -> name
    return [m db onecolumn {
	SELECT R.url || ' (: ' || M.name || ')'
	SELECT R.url || ' (: ' || P.name || ')'
	FROM   repository R
	,      mirror_set M
	,      project    P
	WHERE  R.id = :repo
	AND    M.id = R.mset
	AND    P.id = R.project
    }]
}

proc ::m::repo::has {url} {
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+


+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
+
+
+
+
+
+
+
+
+
+
+



-
-








-
+






-
+
+


-
-
+
+

+
+
+
+
+

-
+


-
-
-
-
+
+
+
+
-












-
+
+




-
-
-
-
+
+
+
+




-
+


-
-
+

-
-
-
-
+
+







+







    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repository
    }]
}

proc ::m::repo::times {repo duration now issues} {
    debug.m/repo {}
    # Read current state

    m db eval {
	SELECT min_duration    AS mins
	,      max_duration    AS maxs
	,      window_duration AS window
	FROM   repository
	WHERE  id = :repo
    } {}

    debug.m/repo {lastr = ($window)}

    # See also ::m::glue::StatsTime, ::m::web::site::Store

    set window [m format win $window]
    
    debug.m/repo {mins  = $mins}
    debug.m/repo {maxs  = $maxs}
    debug.m/repo {lastn = ($window)}

    # Modify based on the incoming duration.
    
    if {($mins eq {}) || ($mins < 0) || ($duration < $mins)} { set mins $duration }
    if {                                 $duration > $maxs}  { set maxs $duration }

    lappend window $duration
    set window [m format win-trim $window [m state store-window-size]]
    debug.m/repo {last' = ($window)}

    set window ,[join $window ,],
    debug.m/repo {last. = ($window)}

    # And write the results back
    
    m db eval {
	UPDATE repository
	SET    min_duration    = :mins
	,      max_duration    = :maxs
	,      window_duration = :window
	,      checked         = :now
	,      has_issues      = :issues
	WHERE  id              = :repo
    }
    return
}

proc ::m::repo::add {vcs mset url} {
proc ::m::repo::add {vcs project store url duration {origin {}}} {
    debug.m/repo {}

    set now [clock seconds]
    
    if {$origin ne {}} {
    m db eval {
	INSERT
	INTO   repository
	VALUES ( NULL, :url, :vcs, :mset, 1 )
        m db eval {
	    INSERT
	    INTO   repository
	    VALUES ( NULL	-- id
		   , :url	-- url
		   , :project	-- project
		   , :vcs	-- vcs
		   , :store	-- store
		   , :origin	-- fork_origin
		   , 1		-- is_active
		   , 0		-- has_issues
		   , :now	-- checked
		   , :duration	-- min_duration
		   , :duration	-- max_duration
		   , :duration	-- window_duration
		   )
	}
    } else {
        m db eval {
	    INSERT
	    INTO   repository
	    VALUES ( NULL	-- id
		   , :url	-- url
		   , :project	-- project
		   , :vcs	-- vcs
		   , :store	-- store
		   , NULL	-- fork_origin
		   , 1		-- is_active
		   , 0		-- has_issues
		   , :now	-- checked
		   , :duration	-- min_duration
		   , :duration	-- max_duration
		   , :duration	-- window_duration
		   )
	}
    }

    return [m db last_insert_rowid]
}

proc ::m::repo::for {project} {
    debug.m/project {}
    return [m db eval {
	SELECT id
	FROM   repository
	WHERE  project = :project
    }]
}

proc ::m::repo::mset {repo} {
proc ::m::repo::forks {repo} {
    debug.m/project {}
    return [m db eval {
	SELECT id
	FROM   repository
	WHERE  fork_origin = :repo
    }]
}

proc ::m::repo::fork-locations {repo} {
    debug.m/project {}
    return [m db eval {
	SELECT url
	FROM   repository
	WHERE  fork_origin = :repo
    }]
}

proc ::m::repo::project {repo} {
    debug.m/repo {}
    set mset [m db onecolumn {
	SELECT mset
    set project [m db onecolumn {
	SELECT project
	FROM   repository
	WHERE  id = :repo
    }]
    debug.m/repo {=> ($project)}
    return $project
}

proc ::m::repo::store {repo} {
    debug.m/project {}
    return [m db eval {
	SELECT store
	FROM   repository
	WHERE  id = :repo
    }]
    debug.m/repo {=> ($mset)}
    return $mset
}

proc ::m::repo::get {repo} {
    debug.m/repo {}

    # Given a repository (by id) follow all the links in the database
    # to retrieve everything related to it
    # - repository (url)
    # - mirror set (id, and name)
    # - project    (id, and name)
    # - vcs        (id, and code)
    # - store      (id)
    # - active
    
    set details [m db eval {
	SELECT 'url'    , R.url
	,      'active' , R.active
	,      'active' , R.is_active
	,      'issues' , R.has_issues
	,      'vcs'    , R.vcs
	,      'vcode'  , V.code
	,      'mset'   , R.mset
	,      'name'   , M.name
	,      'project', R.project
	,      'name'   , P.name
	,      'store'  , S.id
	,      'min_sec', min_duration
	,      'max_sec', max_duration
	,      'win_sec', window_duration
	,      'checked', checked
	,      'origin' , fork_origin
	FROM   repository             R
	,      mirror_set             M
	,      project                P
	,      version_control_system V
	,      store                  S
	WHERE  R.id   = :repo
	AND    M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.vcs  = R.vcs
	WHERE  R.id = :repo
	AND    P.id = R.project
	AND    V.id = R.vcs
	AND    S.id = R.store
	AND    S.mset = R.mset
    }]
    debug.m/repo {=> ($details)}
    return $details
}


proc ::m::repo::search {substring} {
    debug.m/repo {}

    set sub [string tolower $substring]
    set series {}
    m db eval {
	SELECT M.name             AS name
	SELECT P.name             AS name
	,      R.fork_origin      AS origin
	,      R.url              AS url
	,      R.id               AS rid
	,      V.code             AS vcode
	,      S.size_kb          AS sizekb
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      R.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      mirror_set             M
	,      project                P
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  M.id   = R.mset
	WHERE  P.id   = R.project
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = T.store
	ORDER BY M.name ASC
	AND    S.id   = R.store
	ORDER BY P.name ASC
	,        R.url  ASC
    } {
	if {
	    ([string first $sub [string tolower $name]] < 0) &&
	    ([string first $sub [string tolower $url ]] < 0)
	} continue
	lappend series [dict create \
		primary [expr {$origin eq {}}] \
		name    $name \
		url     $url \
		id      $rid \
		vcode   $vcode \
	        sizekb  $sizekb \
		active  $active \
		sizep   $sizep \
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
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







-
+
+




-
-
-
-
+
+
+
+




-
+


-
-
+

-
-
-
+

-
-
+
+

-
+




+







	debug.m/repo {first = ($first)}
    }
    lassign $first mname uname

    set lim [expr {$n + 1}]
    set replist {}
    m db eval {
	SELECT M.name             AS name
	SELECT P.name             AS name
	,      R.fork_origin      AS origin
	,      R.url              AS url
	,      R.id               AS rid
	,      V.code             AS vcode
	,      S.size_kb          AS sizekb
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      R.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      mirror_set             M
	,      project                P
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  M.id   = R.mset
	WHERE  P.id   = R.project
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = T.store
	AND    S.id   = R.store
	-- cursor start clause ...
	AND ((M.name > :mname) OR
	     ((M.name = :mname) AND
	AND ((P.name > :mname) OR
	     ((P.name = :mname) AND
	      (R.url >= :uname)))
	ORDER BY M.name ASC
	ORDER BY P.name ASC
	,        R.url  ASC
	LIMIT :lim
    } {
	lappend replist [dict create \
		primary [expr {$origin eq {}}] \
		name    $name \
		url     $url \
		id      $rid \
		vcode   $vcode \
		sizekb  $sizekb \
		active  $active \
		sizep   $sizep \
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
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536

537
538














539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604

605
606
607
608
609
610
611
612

613
614
615

616
617
618



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633







+
+
+
+







-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+


-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
-
+







-
+


-
+


-
-
-
+
+
+




+
+
+
+





proc ::m::repo::remove {repo} {
    debug.m/repo {}
    return [m db eval {
	DELETE
	FROM  repository
	WHERE id = :repo
	; -- - - -- --- ----- clear origin links in forks
	UPDATE repository
	SET    fork_origin = NULL
	WHERE  fork_origin = :repo
    }]
}

proc ::m::repo::enable {repo {flag 1}} {
    debug.m/repo {}
    return [m db eval {
	UPDATE repository
	SET    active = :flag
	WHERE  id = :repo
	SET    is_active = :flag
	WHERE  id        = :repo
    }]
}

proc ::m::repo::declaim {repo} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    fork_origin = NULL
	WHERE  id          = :repo
    }
    return
}

proc ::m::repo::claim {origin fork} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    fork_origin = :origin
	WHERE  id          = :fork
    }
    return
}

proc ::m::repo::move/project {projectold projectnew} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    project = :projectnew
	WHERE  project = :projectold
    }
    return
}

proc ::m::repo::move/1 {repo projectnew} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    project = :projectnew
	WHERE  id      = :repo
    }
    return
}

# # ## ### ##### ######## ############# ######################
## Management of pending repositories

proc ::m::repo::count-pending {} {
    debug.m/repo {}
    return [m db onecolumn {
	SELECT count (*)
	FROM   repo_pending
    }]
}

proc ::m::repo::add-pending {repo} {
    debug.m/repo {}
    m db eval {
	INSERT
	INTO repo_pending
	VALUES ( :repo )
    }
    return
}

proc ::m::repo::drop-pending {repo} {
    debug.m/repo {}
    return [m db eval {
	DELETE
	FROM  repo_pending
	WHERE repository = :repo
    }]
    return
}

proc ::m::repo::move/mset {msetold msetnew} {
proc ::m::repo::pending {} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    mset = :msetnew
	WHERE  mset = :msetold
    }
    return
}

proc ::m::repo::move/1 {repo msetnew} {
    debug.m/repo {}
    m db eval {
	UPDATE repository
	SET    mset = :msetnew
	WHERE  id   = :repo
    return [m db eval {
	SELECT P.name        AS name
	,      R.url         AS url
	,      R.fork_origin AS origin
	,      (SELECT count (*)
		FROM repository X
		WHERE fork_origin = R.id) AS nforks
	FROM repository R
	,    project    P
	WHERE R.project = P.id
	AND   R.is_active
	ORDER BY R.ROWID
    }]
}

proc ::m::repo::take-pending {take args} {
    debug.m/repo {}

    # Ask for one more than actually requested by the
    # configuration. This will cause a short-read (with refill) not
    # only when the table contains less than `take` elements, but also
    # when it contains exactly that many.  If the read is not short we
    # know that at least one element is left.
    incr take

    set taken [m db eval {
	SELECT P.repository
	FROM   repo_pending P
	,      repository   R
	WHERE  R.id = P.repository
	AND    R.is_active
	LIMIT :take
    }]
    if {[llength $taken] < $take} {
	# Short read. Clear taken (fast), and refill for next
	# invokation.
	m db eval {
	    DELETE
	    FROM   repo_pending
	    ;
	    INSERT
	    INTO   repo_pending
	    SELECT id
	    FROM   repository
	}

	if {[llength $args]} {
	    # Invoke callback to report that the overall cycle just
	    # came around and started anew.
	    try {
		uplevel 1 $args
	    } on error {e o} {
		# TODO -- Report (internal) error, but do not crash.
	    }
	}
    } else {
	# Full read. Clear taken, the slow way.  Drop the unwanted
	# sentinel element from the end of the result.
	set taken [lreplace [K $taken [unset taken]] end end]
	m db eval [string map [list %% [join $taken ,]] {
	    DELETE
	    FROM repo_pending
	    WHERE repository in (%%)
	}]
    }

    return
    return $taken
}

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

proc ::m::repo::FIRST {} {
    debug.m/repo {}
    # First known repository.
    # Ordered by mirror set name, then url
    # Ordered by project name, then url

    return [m db eval {
	SELECT M.name
	SELECT P.name
	,      R.url
	FROM   repository R
	,      mirror_set M
	WHERE  R.mset = M.id
	ORDER BY M.name ASC
	,      project    P
	WHERE  R.project = P.id
	ORDER BY P.name ASC
	,        R.url  ASC
	LIMIT 1
    }]
}

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

proc ::m::repo::K {x y} { set x }

# # ## ### ##### ######## ############# ######################
package provide m::repo 0
return

Changes to lib/logic/store.tcl.

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







-
+

-
+
+










-
+

-
-
+
+
-
-
-
+
-
-
+
+

-
-
-
+
+
-
-
-
+
-
-
+







-
-
-
-








namespace eval ::m {
    namespace export store
    namespace ensemble create
}
namespace eval ::m::store {
    namespace export \
	add remove move rename merge cleave update has check \
	add remove move rename merge cleave update has check path \
	id vcs-name updates by-name by-size by-vcs move-location \
	get remotes total-size count search issues disabled path
	get getx repos remotes total-size count search issues disabled \
	has-issues
    namespace ensemble create
}

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

debug level  m/store
debug prefix m/store {[debug caller] | }

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

proc ::m::store::add {vcs mset name url} {
proc ::m::store::add {vcs name url} {
    debug.m/store {}

    set store [Add $vcs $mset]
    
    set store [Add $vcs]

    set started [clock seconds]
    set counts  [m vcs setup $store $vcs $name $url]
    set state [m vcs setup $store $vcs $name $url]
    set spent   [expr {[clock seconds] - $started}]
    lassign $counts _ after forks
    dict with state {}
    # commits, size, forks, duration

    Spent   $store $spent
    Size    $store
    Commits $store $after
    Size    $store $size
    Commits $store $commits
    if {$forks ne {}} {
	ForksSetNew $store [llength $forks]
    }


    return [list $store $spent $forks]
    return [list $store $duration $commits $size $forks]
}

proc ::m::store::remove {store} {
    debug.m/store {}
    set vcs [VCS $store]

    m db eval {
	DELETE
	FROM  store_times
	WHERE store = :store
	;
	DELETE
	FROM  store
	WHERE id = :store
    }

    m vcs cleanup $store $vcs
    return
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
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







-
-
-
+
+
+
-

-
-
-
-
+
+

-
-
-
+
+
+
+
+

-
-
+

-
-
-
-
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
+







    set new  [Add $vcs $msetnew ]
    set name [MSName $msetnew]
    m vcs cleave $vcs $store $new $name
    Size $new
    return
}

proc ::m::store::update {store cycle now} {
    debug.m/store {}

proc ::m::store::has-issues {store} {
    return [expr {[lindex [m vcs caps $store] 1] ne {}}]
}
    set vcs [VCS $store]

    # Get all repositories for this store (same VCS, same mirror set),
    # then feed everything to the vcs layer.

    set remotes [Remotes $store 1]
proc ::m::store::update {primary url store cycle now before} {
    debug.m/store {}

    set started [clock seconds]
    set counts  [m vcs update $store $vcs $remotes]
    set spent   [expr {[clock seconds] - $started}]
    set vcs   [VCS $store]
    set state [m vcs update $store $vcs $url $primary]
    dict with state {}
    # ok, commits, size, forks, duration
    if {!$primary} { set forks {} }

    lassign $counts before after forks
    debug.m/store {update = ($counts)}
    debug.m/store {update = ($state)}

    Attend  $store
    Spent   $store $spent
    Size    $store
    Commits $store $after
    if {!$ok} {
	Size    $store $size
	Commits $store $commits
    if {$forks ne {}} {
	ForksSet $store [llength $forks]
    }

	Times   $store $cycle $now [expr {$commits != $before}]
    if {$after != $before} {
	m db eval {
	    UPDATE store_times
	    SET updated = :cycle
	    ,   changed = :now
	    WHERE store = :store
	}
    }
    } else {
	m db eval {
	    UPDATE store_times
	    SET updated = :cycle
	    WHERE store = :store
	}

    }
    return [linsert $counts end $remotes $spent]
    return [list $ok $duration $commits $size $forks]
}

proc ::m::store::move {store msetnew} {
    debug.m/store {}
    # copy of `m mset name` - outline? check for dependency circles
    set newname [MSName $msetnew]
    set vcs     [VCS $store]
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+

-





-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
-

-


-
+
-
-
+
+
+




-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+







}

proc ::m::store::path {store} {
    debug.m/store {}
    return [m vcs path $store]
}

proc ::m::store::getx {repos} {	;# XXX REWORK move to repo package
    debug.m/store {}

    lappend map @@ [join $repos ,]
    set series {}
    m db eval [string map $map {
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
	,    store                  S
	,    project                P
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	AND   R.id      IN (@@)
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    }] {
	Srow series ;# upvar column variables
    }
    return $series
}
    
proc ::m::store::get {store} {
    debug.m/store {}
    m db eval {
    set details [m db eval {
	SELECT 'size'    , S.size_kb
	,      'mset'    , S.mset
	,      'vcs'     , S.vcs
	,      'sizep'   , S.size_previous
	,      'commits' , S.commits_current
	,      'commitp' , S.commits_previous
	,      'vcsname' , V.name
	,      'updated' , T.updated
	,      'changed' , T.changed
	,      'created' , T.created
	,      'attend'  , T.attend
	,      'min_sec' , T.min_seconds
	,      'max_sec' , T.max_seconds
	,      'win_sec' , T.window_seconds
	,      'remote'  , (SELECT count (*)
	,      'updated' , S.updated
	,      'changed' , S.changed
	,      'created' , S.created
	,      'attend'  , (SELECT sum          (R.has_issues)      FROM repository R WHERE R.store = S.id)
	,      'min_sec' , (SELECT min          (R.min_duration)    FROM repository R WHERE R.store = S.id)
	,      'max_sec' , (SELECT max          (R.max_duration)    FROM repository R WHERE R.store = S.id)
	,      'win_sec' , (SELECT group_concat (R.window_duration) FROM repository R WHERE R.store = S.id)
	,      'remote'  , (SELECT count        (*)                 FROM repository R WHERE R.store = S.id)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      'active'  , (SELECT count (*)
	,      'active'  , (SELECT sum          (is_active)         FROM repository R WHERE R.store = S.id)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM   store                  S
	,      store_times            T
	,      version_control_system V
	WHERE S.id    = :store
	AND   T.store = S.id
	AND   S.vcs   = V.id
	AND   V.id    = S.vcs
    }
    }]
    debug.m/store {=> ($details)}
    return $details
}

proc ::m::store::remotes {store} {
    debug.m/store {}
    set vcs [VCS $store]
    lappend r [Remotes            $store] ;# Database
    lappend r [m vcs remotes $vcs $store] ;# Plugin supplied
    return $r
    return [Remotes $store]
}

proc ::m::store::repos {store} {
    debug.m/store {}
    return [m db eval {
	SELECT R.id
	FROM   repository R
	,      store      S
	WHERE S.id    = :store
	AND   R.store = S.id
    }]
}

proc ::m::store::vcs-name {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT V.name
	FROM   store                  S
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
503
504



505
506
507
508
509
510
511
512
513
514

515
516


517

518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535








536
537
538
539

540
541
542
543
544
545
546
547




548
549
550

551


552
553

554
555
556
557
558
559





560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581








582
583
584
585

586
587
588
589
590
591
592
593




594
595
596

597


598
599

600
601
602
603
604
605





606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626

627
628
629
630
631


632
633

634
635






























636
637
638
639
640
641
642
643
644
645



646
647
648
649
650

651
652
653
654
655
656
657

658
659
660
661


662
663
664
665
666
667
668
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
503
504
505
506
507

508
509
510
511
512

513
514
515
516
517
518
519
520
521
522
523








524
525
526
527
528
529
530
531




532








533
534
535
536
537
538
539
540

541
542
543

544
545





546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564








565
566
567
568
569
570
571
572




573








574
575
576
577
578
579
580
581

582
583
584

585
586





587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611

612
613
614
615
616

617
618
619

620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661

662
663
664
665
666
667
668

669
670
671
672
673
674
675

676
677
678
679
680
681
682
683
684
685
686
687
688
689







+



+
+



-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-

-
+

-
-
-












-
+


+
+



-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-

-
+

-
-
-
-
-
+
+
+
+
+




-
+




-
+


+
+



-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

-
-
-
+
+
-
-
-
-
+
+









-
+


+
+



-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-

-
+

-
-
-
+
+
+















-
+


+
+


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-

-
+

-
-
-
+
+
+









-
+


+
+


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-

-
+

-
-
-
+
+
+









-
+


+
+
-
+










-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+



+
-
+
+

-
+

-
-
-
-
-
+
+
+
+
+














-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+



+
-
+
+

-
+

-
-
-
-
-
+
+
+
+
+




















-
+




-
+
+

-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
+
+
+




-
+






-
+




+
+







proc ::m::store::count {} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT count (*)
	FROM store
    }]
}

proc ::m::store::search {substring} {
    debug.m/store {}

    # List stores ...
    
    set sub [string tolower $substring]
    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	if {
	    [string first $sub [string tolower $mname]] < 0
	} continue
	Srow series ;# upvar column variables
    }
    return $series
}

proc ::m::store::issues {} {
proc ::m::store::issues {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List repositories ...
    
    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   T.attend  = 1    -- Flag for "has issues"
	AND   active    > 0    -- Flag for "not completely disabled"
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	WHERE R.store = S.id
	AND   R.has_issues  = 1    -- Flag for "has issues"
	AND   R.is_active   > 0    -- Flag for "not completely disabled"
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow series ;# upvar column variables
	Srow+origin series ;# upvar column variables
    }
    return $series
}

proc ::m::store::disabled {} {
proc ::m::store::disabled {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List repositories ...
    
    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      1         AS remote
	,      0         AS active
	,      R.id      AS rid
	,      R.url     AS url
	FROM store_times            T
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
	,      0             AS active
	,      R.id          AS rid
	,      R.url         AS url
	,      R.fork_origin AS origin
	FROM repository             R
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	,    repository             R
	WHERE T.store   = S.id
	AND   R.active  = 0    -- Flag for disabled
	WHERE R.store     = S.id
	AND   R.is_active = 0    -- Flag for disabled
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   R.mset    = S.mset
	AND   R.vcs     = S.vcs
	AND   R.project   = P.id
	AND   R.vcs       = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow+rid+url series ;# upvar column variables
    }
    return $series
}

proc ::m::store::by-name {} {
proc ::m::store::by-name {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List stores ...
    
    set series {}
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	FROM repository             R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	if {($last ne {}) && ($last ne $mname)} {
	    Sep series
	}
	set saved $mname
	set mname [expr {($last eq $mname) ? "" : "$mname"}]
	Srow series ;# upvar column variables
	set last $saved
    }
    return $series
}

proc ::m::store::by-vcs {} {
proc ::m::store::by-vcs {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List repositories ...
    
    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	SELECT S.id         AS store
	,      P.name       AS mname
	,      V.code       AS vcode
	,      S.changed    AS changed
	,      S.updated    AS updated
	,      S.created    AS created
	,      R.has_issues AS attend
	,      S.size_kb    AS size
	,      1            AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
	,      R.is_active  AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	FROM repository             R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY vcode ASC
	,        mname ASC
	,        size  ASC
    } {
	Srow series
    }
    return $series
}

proc ::m::store::by-size {} {
proc ::m::store::by-size {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List repositories ...
    
    set series {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	SELECT S.id          AS store
	,      P.name        AS mname
	,      V.code        AS vcode
	,      S.changed     AS changed
	,      S.updated     AS updated
	,      S.created     AS created
	,      R.has_issues  AS attend
	,      S.size_kb     AS size
	,      1             AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
	,      R.is_active   AS active
	,      R.fork_origin AS origin
	,      R.url         AS url
	FROM repository             R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	ORDER BY size  DESC
	,        mname ASC
	,        vcode ASC
    } {
	Srow series
    }
    return $series
}

proc ::m::store::updates {} {
proc ::m::store::updates {} {	;# XXX REWORK move to repo package
    debug.m/store {}

    # List repositories ...

    # From the db.tcl notes on store_times
    # From the db.tcl notes on store times
    #
    # 1. created <= changed <= updated
    # 2. (created == changed) -> never changed.

    set series {}

    # Block 1: Changed stores, changed order descending
    # Insert separators when `updated` changes.
    set last {}
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	SELECT S.id               AS store
	,      P.name             AS mname
	,      V.code             AS vcode
	,      S.changed          AS changed
	,      S.updated          AS updated
	,      S.created          AS created
	,      R.has_issues       AS attend
	,      S.size_kb          AS size
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      1                  AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      R.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	,      R.fork_origin      AS origin
	FROM store_times            T
	,      R.url              AS url
	FROM repository             R
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   T.created != T.changed
	ORDER BY T.changed DESC
	WHERE R.store    = S.id
	AND   R.project  = P.id
	AND   R.vcs      = V.id
	AND   S.created != S.changed
	ORDER BY S.changed DESC
    } {
	if {($last ne {}) && ($last != $updated)} {
	    Sep series
	}
	Srow+delta series
	set last $updated
    }

    debug.m/store {f/[llength $series]}
    set first [llength $series]

    # Block 2: All unchanged stores, creation order descending,
    # i.e. last created top/first.
    m db eval {
	SELECT S.id      AS store
	,      M.name    AS mname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	SELECT S.id               AS store
	,      P.name             AS mname
	,      V.code             AS vcode
	,      S.changed          AS changed
	,      S.updated          AS updated
	,      S.created          AS created
	,      R.has_issues       AS attend
	,      S.size_kb          AS size
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      1                  AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      R.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	,      R.fork_origin      AS origin
	FROM store_times            T
	,      R.url              AS url
	FROM repository             R
	,    store                  S
	,    mirror_set             M
	,    project                P
	,    version_control_system V
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	AND   T.created = T.changed
	ORDER BY T.created DESC
	WHERE R.store    = S.id
	AND   R.project  = P.id
	AND   R.vcs      = V.id
	AND   S.created = S.changed
	ORDER BY S.created DESC
    } {
	if {$first} { Sep series }
	set changed {}
	set updated {}
	Srow+delta series
	set first 0
    }

    debug.m/store {r/[llength $series]}
    return $series
}

proc ::m::store::move-location {newpath} {
    debug.m/store {}
    m vcs move $newpath
    return
}

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

proc ::m::store::Srow {sv} {
proc ::m::store::Srow {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend
        size size active active remote remote attend attend \
        origin origin url url			    

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend}
    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, oring=4origin, url=$url}
    
    set row [dict create \
		url     $url \
		origin  $origin \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend \
		]
    lappend series $row
    return
}

proc ::m::store::Srow+origin {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	origin origin url url rid rid

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, origin=$origin, url=$url, rid=$rid}
    
    set row [dict create \
		rid     $rid \
		url     $url \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend]
		attend  $attend \
		origin  $origin
		]
    lappend series $row
    return
}

proc ::m::store::Srow+delta {sv} {
proc ::m::store::Srow+delta {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	sizep sizep commits commits commitp commitp mins mins \
	maxs maxs lastn lastn
	maxs maxs lastn lastn origin origin url url

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend}
    
    set row [dict create \
		url     $url \
	        origin  $origin \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
676
677
678
679
680
681
682
683

684
685
686
687
688
689

690
691

692
693
694
695
696
697
698
699
700
701
702
703
704
705


706
707
708
709
710

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731


732
733

734
735
736
737
738
739
740
741
742


743
744
745
746



















747

748
749
750
751
752
753
754
755
756
757
758

759
760
761
762
763
764
765
766

767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
697
698
699
700
701
702
703

704
705
706
707
708
709

710
711

712
713
714
715
716
717
718
719
720
721
722
723
724
725

726
727
728
729
730
731

732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751


752
753


754
755
756
757
758
759
760
761


762
763

764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785

786
787
788

789
790
791
792
793
794
795

796
797
798
799
800
801
802


803












804
805
806
807
808
809
810







-
+





-
+

-
+













-
+
+




-
+



















-
-
+
+
-
-
+







-
-
+
+
-



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+


-







-
+






-
-
+
-
-
-
-
-
-
-
-
-
-
-
-







		commits $commits \
		commitp $commitp \
		]
    lappend series $row
    return
}

proc ::m::store::Srow+rid+url {sv} {
proc ::m::store::Srow+rid+url {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 \
	$sv series store store mname mname vcode vcode \
	changed changed updated updated created created \
	size size active active remote remote attend attend \
	rid rid url url
	rid rid url url origin origin

    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend}
    debug.m/store {s=$store, m=$mname, v=$vcode, ch=$changed, up=$updated, cr=$created, sz=$size, r=$remote/$active, trouble=$attend, rid=$rid, url=$url, origin=$origin}
    
    set row [dict create \
		store   $store \
		mname   $mname \
		vcode   $vcode \
		changed $changed \
		updated $updated \
		created $created \
		size    $size \
		remote  $remote \
		active  $active \
		attend  $attend \
		rid     $rid \
		url     $url	]
		url     $url	\
		origin  $origin ]
    lappend series $row
    return
}

proc ::m::store::Sep {sv} {
proc ::m::store::Sep {sv} {	;# XXX REWORK move to repo package
    debug.m/store {}
    upvar 1 $sv series
    lappend series {
	store   . mname   . vcode . changed .
	updated . created . size  . active  .
	remote  . attend  . rid   . url     .
	mins    . maxs    . lastn . sizep   .
	commits . commitp .
    }
    return
}

proc ::m::store::Remotes {store {onlyactive 0}} {
    debug.m/store {}
    if {$onlyactive} {
	return [m db eval {
	    SELECT R.url
	    FROM   repository R
	    ,      store      S
	    WHERE S.id   = :store
	    AND   R.vcs  = S.vcs
	    WHERE S.id    = :store
	    AND   R.store = S.id
	    AND   R.mset = S.mset
	    AND   R.active
	    AND   R.is_active
	}]
    }
    
    return [m db eval {
	SELECT R.url
	FROM   repository R
	,      store      S
	WHERE S.id   = :store
	AND   R.vcs  = S.vcs
	WHERE S.id    = :store
	AND   R.store = S.id
	AND   R.mset = S.mset
    }]
}

proc ::m::store::Times {store cycle now haschanged} {
    if {$haschanged} {
	m db eval {
	    UPDATE store
	    SET updated = :cycle
	    ,   changed = :now
	    WHERE store = :store
	}
	return
    }

    m db eval {
	UPDATE store
	SET updated = :cycle
	WHERE store = :store
	}
    return
}

proc ::m::store::Size {store} {
proc ::m::store::Size {store new} {
    debug.m/store {}

    set new     [m vcs size $store]
    set current [m db onecolumn {
	SELECT size_kb
	FROM   store
	WHERE  id = :store
    }]

    if {$new == $current} return

    
    m db eval {
	UPDATE store
	SET    size_previous = size_kb -- Parallel assignment
	,      size_kb       = :new    -- Shift values.
	WHERE  id            = :store
    }
    return
}


proc ::m::store::InitialCommit {store} {
    debug.m/store {}

    set vcs  [VCS $store]
    set revs [m vcs revs $store $vcs]
    m db eval {
	UPDATE store
	SET    commits_current  = :revs
	,      commits_previous = :revs
	WHERE  id               = :store
    }
    return
}

proc ::m::store::Commits {store new} {
    debug.m/store {}

    set current [m db onecolumn {
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849

850
851

852
853
854
855
856
857

858
859
860
861
862
863
864
865
866
867
868
869
870
871
872



873
874
875
876

877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904

905
906
907
908
909


910
911
912








913
914
915
916
917
918
919
920
921
922














923
924
925
926
927
928
929
930






















931
932
933
934
935
936
937
938
939
940
941
942
943
944












945
946
947
948
949
950
951
820
821
822
823
824
825
826
















































827
828

829






830





831
832
833
834
835

836
837
838
839
840
841
842
843
844
845

846
















847
848
849
850
851
852
853
854
855
856
857

858
859
860
861


862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905

906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+

-
+
-
-
-
-
-
-
+
-
-
-
-
-





-




+
+
+



-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-











-
+



-
-
+
+



+
+
+
+
+
+
+
+










+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+














+
+
+
+
+
+
+
+
+
+
+
+







	SET    commits_previous = commits_current -- Parallel assignment
	,      commits_current  = :new            -- Shift values.
	WHERE  id               = :store
    }
    return
}

proc ::m::store::Spent {store new} {
    debug.m/store {}

    m db eval {
	SELECT min_seconds    AS mins
	,      max_seconds    AS maxs
	,      window_seconds AS window
	FROM   store_times
	WHERE  store = :store
    } {}

    debug.m/store {mins  = $mins}
    debug.m/store {maxs  = $maxs}
    debug.m/store {lastn = ($window)}
    
    if {($mins < 0) || ($new < $mins)} { set mins $new }
    if {                $new > $maxs}  { set maxs $new }

    set window [split [string trim $window ,] ,]

    debug.m/store {lastn'= ($window)}
    
    if {[llength $window]} {
	lappend window $new
	set maxlen [m state store-window-size]
	set len    [llength $window]
	if {$len > $maxlen} {
	    set over   [expr {$len - $maxlen}]
	    set window [lreplace $window 0 ${over}-1]
	}
	set new [join $window ,]
    }
    set window ,${new},

    debug.m/store {lastn.= ($window)}

    m db eval {
	UPDATE store_times
	SET    min_seconds    = :mins
	,      max_seconds    = :maxs
	,      window_seconds = :window
	WHERE  store          = :store
    }

    return
}

proc ::m::store::Attend {store} {
proc ::m::store::Add {vcs} {
    debug.m/store {}

    set now [clock seconds]
    set attend [expr {[lindex [m vcs caps $store] 1] ne {}}]
    m db eval {
	UPDATE store_times
	SET    attend = :attend
	WHERE  store  = :store
    }

    return
}

proc ::m::store::Add {vcs mset} {
    debug.m/store {}
    m db eval {
	INSERT
	INTO   store
	VALUES ( NULL  -- id
	,	 :vcs  -- vcs
	,        :mset -- mset
	,	 0     -- size_kb
	,	 0     -- size_previous
	,	 0     -- commits_current
	,	 0     -- commits_previous
	,	 :now  -- created
	,	 :now  -- updated
	,	 :now  -- changed
	)
    }

    set store [m db last_insert_rowid]
    return [m db last_insert_rowid]
    set now   [clock seconds]

    m db eval {
	INSERT
	INTO   store_times
	VALUES ( :store -- ^store
	,	 :now   -- created
	,	 :now   -- updated
	,	 :now   -- changed
	,	 0      -- attend
	,	 -1     -- min_seconds (+Infinity)
	,	 0      -- max_seconds
	,	 ''     -- window_seconds
	)
    }
    return $store
}

proc ::m::store::VCS {store} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT vcs
	FROM   store
	WHERE  id = :store
    }]
}

proc ::m::store::MSName {mset} {
proc ::m::store::MSName {project} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT name
	FROM   mirror_set
	WHERE  id = :mset
	FROM   project
	WHERE  id = :project
    }]
}

##
# # ## ### ##### ######## ############# ######################
## ATTENTION
## These commands are part of the database migration step.
## Their use of old tables and columns is intentional!
## At the point they are called by the migration these are
## the current tables and columns

proc ::m::store::InitialCommits {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	InitialCommit $id
    }
    return
}

proc ::m::store::InitialCommit {store} {
    debug.m/store {}

    set vcs  [VCS $store]
    set revs [m vcs revs $store $vcs]
    m db eval {
	UPDATE store
	SET    commits_current  = :revs
	,      commits_previous = :revs
	WHERE  id               = :store
    }
    return
}

proc ::m::store::InitialSizes {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	Size $id
	InitialSize $id
    }
    return
}

proc ::m::store::InitialSize {store} {
    debug.m/store {}

    set new     [m vcs size $store]
    set current [m db onecolumn {
	SELECT size_kb
	FROM   store
	WHERE  id = :store
    }]

    if {$new == $current} return

    m db eval {
	UPDATE store
	SET    size_previous = size_kb -- Parallel assignment
	,      size_kb       = :new    -- Shift values.
	WHERE  id            = :store
    }
    return
}

proc ::m::store::InitialIssues {} {
    debug.m/store {}
    m db eval {
	SELECT id
	FROM   store
    } {
	Attend $id
    }
    return
}

proc ::m::store::Attend {store} {
    debug.m/store {}

    set attend [expr {[lindex [m vcs caps $store] 1] ne {}}]
    m db eval {
	UPDATE store_times
	SET    attend = :attend
	WHERE  store  = :store
    }
    return
}

proc ::m::store::InitialForks {} {
    debug.m/store {}
    m db eval {
	SELECT S.id AS store
	FROM   store                  S
	,      version_control_system V
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
972
973
974
975
976
977
978























979
980
981
982
983
984







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-






    set forks [llength [lindex [m vcs github remotes [m vcs path $store]] 1]]
    m db eval {
	INSERT
	INTO store_github_forks
	VALUES ( :store
	       , :forks )
    }
    return
}

proc ::m::store::ForksSetNew {store forks} {
    debug.m/store {}
    # assert: vcs == github
    m db eval {
	INSERT
	INTO store_github_forks
	VALUES ( :store
	       , :forks )
    }
    return
}

proc ::m::store::ForksSet {store forks} {
    debug.m/store {}
    # assert: vcs == github
    m db eval {
	UPDATE store_github_forks
	SET    nforks = :forks
	WHERE  store  = :store
    }
    return
}

# # ## ### ##### ######## ############# ######################
package provide m::store 0
return

Deleted lib/logic/vt_mirrorset.tcl.

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





















































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Repositories - Validation

# @@ Meta Begin
# Package m::validate::mset 0 
# Meta author   {Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     mirror set validation
# Meta description mirror set validation
# Meta subject    {mirror set - validation}
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide m::validate::mset 0

# # ## ### ##### ######## ############# #####################
## Requisites

package require Tcl 8.5
package require cmdr::validate::common 1.2
package require try
package require m::mset
package require m::repo
package require m::rolodex
package require m::match
package require debug
package require debug::caller

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

debug level  m/validate/mset
debug prefix m/validate/mset {[debug caller] | }

# # ## ### ##### ######## ############# #####################
## Definition

namespace eval ::m {
    namespace export validate
    namespace ensemble create
}
namespace eval ::m::validate {
    namespace export mset
    namespace ensemble create
}
namespace eval ::m::validate::mset {
    namespace export default validate complete release
    namespace ensemble create

    namespace import ::cmdr::validate::common::fail
    namespace import ::cmdr::validate::common::complete-enum
}
# # ## ### ##### ######## ############# #####################

debug define m/validate/mset
debug level  m/validate/mset
debug prefix m/validate/mset {[debug caller] | }

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

proc ::m::validate::mset::release  {p x} { return }
proc ::m::validate::mset::default  {p}   {
    return [m repo mset [m rolodex top]]
}
proc ::m::validate::mset::complete {p x} {
    debug.m/validate/mset {} 10
    return [complete-enum [dict keys [m mset known]] 0 $x]
}
proc ::m::validate::mset::validate {p x} {
    debug.m/validate/mset {}

    set known [m mset known]
    set match [m match substring id $known nocase $x]

    switch -exact -- $match {
	ok        { return $id }
	fail      { fail $p MSET "a mirror set"              $x }
	ambiguous { fail $p MSET "an unambiguous mirror set" $x }
    }
}

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

Added lib/logic/vt_project.tcl.






















































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Repositories - Validation

# @@ Meta Begin
# Package m::validate::project 0 
# Meta author   {Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     mirror set validation
# Meta description mirror set validation
# Meta subject    {mirror set - validation}
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide m::validate::project 0

# # ## ### ##### ######## ############# #####################
## Requisites

package require Tcl 8.5
package require cmdr::validate::common 1.2
package require try
package require m::project
package require m::repo
package require m::rolodex
package require m::match
package require debug
package require debug::caller

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

debug level  m/validate/project
debug prefix m/validate/project {[debug caller] | }

# # ## ### ##### ######## ############# #####################
## Definition

namespace eval ::m {
    namespace export validate
    namespace ensemble create
}
namespace eval ::m::validate {
    namespace export project
    namespace ensemble create
}
namespace eval ::m::validate::project {
    namespace export default validate complete release
    namespace ensemble create

    namespace import ::cmdr::validate::common::fail
    namespace import ::cmdr::validate::common::complete-enum
}
# # ## ### ##### ######## ############# #####################

debug define m/validate/project
debug level  m/validate/project
debug prefix m/validate/project {[debug caller] | }

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

proc ::m::validate::project::release  {p x} { return }
proc ::m::validate::project::default  {p}   {
    return [m repo mset [m rolodex top]]
}
proc ::m::validate::project::complete {p x} {
    debug.m/validate/project {} 10
    return [complete-enum [dict keys [m project known]] 0 $x]
}
proc ::m::validate::project::validate {p x} {
    debug.m/validate/project {}

    set known [m project known]
    set match [m match substring id $known nocase $x]

    switch -exact -- $match {
	ok        { return $id }
	fail      { fail $p PROJECT "a project"              $x }
	ambiguous { fail $p PROJECT "an unambiguous project" $x }
    }
}

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

Changes to lib/mail/sender.tcl.

53
54
55
56
57
58
59

60
61
62


63
64
65
66
67
68
69
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72







+



+
+








    set token [mime::initialize -string $corpus]

    m msg* "    To: [color name $receiver] ... "

    try {
	set res [smtp::sendmessage $token -header [list To $receiver] {*}[Config]]
	# XXX REVISIT This may exit on issues, instead of throwing an error ?!
	foreach item $res {
	    m msg "    ERR $item"
	}
    } on error {e o} {
	m msg [color bad $e]
    } finally {
    }
    
    mime::finalize $token
    m msg [color good OK]
    return
}

Changes to lib/utils/format.tcl.

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







-
+









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+







debug level  m/format
debug prefix m/format {[debug caller] | }

# # ## ### ##### ######## ############# #####################
## Definition

namespace eval m::format {
    namespace export size epoch epoch/short interval
    namespace export size epoch epoch/short interval win win-trim
    namespace ensemble create
}
namespace eval m {
    namespace export format
    namespace ensemble create
}

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

proc m::format::win {lastn} {
    # CSV to list, remove bubbles (empty elements)
    return [lmap x [split $lastn ,] { if {$x eq {}} continue ; set x }]
}

proc m::format::win-trim {lastn max} {
    set len [llength $lastn]
    # As new entries are added at the end trimming is done from the front.
    # This is a naive trimmer, removing elements one by one.
    # Considered ok because we usually need only remove one element anyway.
    while {$len > $max} {
	set lastn [lrange  $lastn 1 end]
	set len   [llength $lastn]
    }
    return $lastn
}

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

proc m::format::size {x} {
    # x is in [KB].
    debug.m/format {}
                              if {$x < 1024} { return ${x}K }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]M }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]G }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]T }
    set x [expr {$x/1024.}] ; if {$x < 1024} { return [format %.1f $x]P }
    set x [expr {$x/1024.}] ;                  return [format %.1f $x]E

Changes to lib/utils/ops.tcl.

38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
38
39
40
41
42
43
44

45
46
47
48
49
50
51
52







-
+







}
namespace eval m::ops {
    namespace export set client
    namespace ensemble create
}
namespace eval m::ops::client {
    namespace export set main \
	info note warn err fatal \
        info note warn err fatal \
	result ok fail commits fork size \
	ok?
    namespace ensemble create
}

# # ## ### ##### ######## ############# ######################
## API
96
97
98
99
100
101
102













103
104
105
106
107
108
109
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







+
+
+
+
+
+
+
+
+
+
+
+
+







proc ::m::ops::client::Cmdline {v} {
    debug.m/ops/client {}
    global argv
    if {[llength $argv] < 3} {
	Usage "Not enough arguments"
    }
    set argv [lassign $argv vcs logfile operation]

    # All issues, including syntax errors, bad arguments, etc are
    # reported through the log and stdout. This is in an internal
    # support application the user normally will not invoke directly.
    # Thus the log has to be initialized before anything other checks.
    if {[catch {
	LogTo $logfile
    } msg]} {
	err $msg
	fail
	return 0
    }

    set ops {
	setup       {Store Url}
	cleanup     {Store}
	update      {Store Url Bool}
	mergable?   {Store Store}
	merge       {Store Store}
	split       {Store Store}
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
130
131
132
133
134
135
136







137
138
139
140
141
142
143







-
-
-
-
-
-
-







    set types [dict get $ops $operation]
    if {[llength $argv] != [llength $types]} {
	Usage "Wrong # Args for $operation"
    }
    foreach a $argv t $types {
	if {![$t $a]} { Usage "Expected $t, got '$a'" }
    }
    if {[catch {
	LogTo $logfile
    } msg]} {
	err $msg
	fail
	return 0
    }
    upvar 1 $v cmd
    set cmd [linsert $argv 0 $vcs $operation]
    return 1
}

proc ::m::ops::client::Usage {{note {}}} {
    debug.m/ops/client {}

Changes to lib/utils/setup.tcl.

1
2
3
4
5
6
7

8
9
10

11
12
13
14
15
16
17
1
2
3
4
5
6

7
8
9

10
11
12
13
14
15
16
17






-
+


-
+







## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Database utilities - Setup, migration processing, schema management

# @@ Meta Begin
# Package db::setup 0
# Meta author   {Andreas Kupries}
# Meta author	{Andreas Kupries}
# Meta location https://core.tcl.tk/akupries/????
# Meta platform tcl
# Meta summary     Database setup, migration management
# Meta summary	   Database setup, migration management
# Meta description Database setup, migration management
# Meta subject {database setup} {migration processing} {schema management}
# Meta require {Tcl 8.5-}
# @@ Meta End

package provide db::setup 0
package require debug
29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43







-
+







debug prefix db/setup {[debug caller] | }

# # ## ### ##### ######## ############# #####################
## Definition

namespace eval db::setup {
    namespace import ::db::track::it ; rename it track
    namespace export D C U T T^ I I+ > >+ X < <= /
    namespace export D C U T T^ I I+ > >+ X < <= / R
}

namespace eval db {
    namespace export setup
    namespace ensemble create
}

162
163
164
165
166
167
168
169

170
171

172
173
174
175
176
177
178
162
163
164
165
166
167
168

169
170

171
172
173
174
175
176
177
178







-
+

-
+







    return
}

proc db::setup::<= {table select} {
    debug.db/setup {}
    T new_${table}

    # constraint: do no lose rows. count, then count again.
    # constraint: to not lose rows in the change we count before, then count again after
    set old [lindex [R "SELECT count (*) FROM $table"] 0]
    

    lappend map @@ $table
    set select [string map $map $select]
    lappend sql "INSERT INTO new_${table} $select"
    lappend sql "DROP TABLE $table"
    lappend sql "ALTER TABLE new_${table} RENAME TO $table"
    R [join $sql ";\n"]

236
237
238
239
240
241
242
243

244
245
246
247
248
249
250
236
237
238
239
240
241
242

243
244
245
246
247
248
249
250







-
+







    return
}

proc db::setup::InitializeAndGetVersion {db} {
    debug.db/setup {}
    return [$db eval [string map [list \t {}] {
	CREATE TABLE IF NOT EXISTS schema
	( key     TEXT    NOT NULL PRIMARY KEY
	( key	  TEXT	  NOT NULL PRIMARY KEY
	, version INTEGER NOT NULL
	)
	;
	INSERT OR IGNORE
	INTO   schema
	VALUES ('version', 0)
	;

Changes to lib/vcs/github.tcl.

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







+
-
+










-
+






}

# # ## ### ##### ######## ############# #####################
## Helpers

proc ::m::vcs::github::ReportForks {url} {
    debug.m/vcs/github {}
    upvar 1 path path
    upvar 1 path path ;# for `git::Get` - TODO - redesign with proper state in the low-level code.
    # for `git::Get` - TODO - redesign with proper state in the low-level code.
    
    set origin [join [lrange [file split $url] end-1 end] /]
    set forks  [lsort -dict [m::vcs::git::Get hub forks --raw $origin]]

    if {[m exec err-last-get]} {
	m ops client fail ; return
    }

    foreach fork $forks {
	# unverified estimate (saved)
	m ops client fork $fork
	m ops client fork https://github.com/$fork
    }
    return
}

# # ## ### ##### ######## ############# #####################
return

Changes to lib/vcs/vcs.tcl.

52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66







-
+







}

namespace eval ::m::vcs {
    namespace export \
	setup cleanup update check cleave merge \
	rename id supported all code name \
	detect url-norm name-from-url version \
	move size caps remotes export path revs
	move size caps export path revs
    namespace ensemble create

    namespace import ::cmdr::color

    # Operation state: Id counter, and state per operation.
    variable opsid 0
    variable ops   {}
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
115
116

117
118
119
120
121
122
123
100
101
102
103
104
105
106

107
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122
123







-
+








-
+







#     return [$vcode revs $path]
# }

proc ::m::vcs::setup {store vcs name url} {
    debug.m/vcs {}
    # store id -> Using for path.
    # vcs   id -> Decode to plugin name
    # name     -  mset name
    # name     -  project name
    # url      -  repo url
    set path  [Path $store]
    set vcode [code $vcs]

    # Ensure clean new environment
    file delete -force -- $path
    file mkdir            $path

    m futil write $path/%name $name  ;# Mirror set
    m futil write $path/%name $name  ;# Project
    m futil write $path/%vcs  $vcode ;# Manager

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG setup STORE URL`.

    # Ask plugin to fill the store.
    
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
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







-
+




+
+



-
+








-
-
-
-
+
+
+
+


-
+
-
-
-
-
+
+
+

-
-
-
-
-
-
+
+
+







-
+


+
+








-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    # [x] duration

    if {!$ok} {
	# Roll back filesystem changes
	file delete -force -- $path

	# Rethrow as something more distinguished for trapping
	return -code error -errorcode {M VCS CHILD} $msg
	E $msg CHILD
    }

    dict unset state results
    dict unset state msg
    dict unset state ok
    # commits, size, forks, duration
    return $state
}

proc ::m::vcs::update {store vcs urls} {
proc ::m::vcs::update {store vcs url primary} {
    debug.m/vcs {}
    # store id -> Using for path.
    # vcs   id -> Decode to plugin name
    # urls     -  repo urls to use as sources

    set path  [Path $store]
    set vcode [code $vcs]

    # Validate incoming urls to ensure that they are still present. No
    # need to go for the vcs client when we know that it must
    # fail. That said, we store our failure as a pseudo error log for
    # other parts to pick up on.
    # Validate the url to ensure that it is still present. No need to
    # go for the vcs client when we know that it must fail. That said,
    # we store our failure as a pseudo error log for other parts to
    # pick up on.

    m futil write $path/%stderr ""
    m futil write $path/%stdout "Verifying urls ...\n"
    m futil write $path/%stdout "Verifying url ...\n"
    set failed 0
    foreach u $urls {
	debug.m/vcs {Verifying $u ...}
	if {[m url ok $u xr]} continue
    debug.m/vcs {Verifying $url ...}
    set ok [m url ok $url xr]
    if {!$ok} {
	m futil append $path/%stderr "  Bad url: $u\n"
	set failed 1
    }
    if {$failed} {
	m futil append $path/%stderr "Unable to reach remotes\n"
	# Fake 'no changes', and error
	return {-1 -1 {}}
	m futil append $path/%stderr "Unable to reach remote\n"
	# Fake an error state ...
	return {ok 0 commits 0 size 0 forks {} results {} msg {Invalid url} duration 0}
    }

    # Ask plugin to update the store.
    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG setup STORE URL`.
    
    Operation ::m::vcs::OpComplete $vcode update \
	{*}[OpCmd $vcode $path $urls 0]
	{*}[OpCmd $vcode $path $url $primary]
    set state [OpWait]

    return $state
    
    dict with state {}
    # [x] ok
    # [x] commits
    # [x] size
    # [x] forks
    # [ ] results
    # [x] msg
    # [x] duration

    if {!$ok} {
	# Fake 'no changes', and error.
	# Note, CAP already saved the errorInfo into %stderr
	return {-1 -1 {}}
    }




    
    try {	
	CAP $path {
	    set counts [$vcode update $path $urls 0]
	}
    } on error {e o} {
	# Fake 'no changes', and error.
	# Note, CAP already saved the errorInfo into %stderr
	return {-1 -1 {}}
    }
    if {[llength $counts] < 3} { lappend counts {} }
    debug.m/vcs {==> ($counts)}
    return $counts
}

proc ::m::vcs::rename {store name} {
    debug.m/vcs {}
    # store id -> Using for path.
    # name     -  new mset name
    set path [Path $store]
251
252
253
254
255
256
257
258

259
260
261
262
263
264
265
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242







-
+







    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	# Do not perform any filesystem changes.
	# Rethrow as something more distinguished for trapping
	return -code error -errorcode {M VCS CHILD} $msg
	E $msg CHILD
    }

    # ... and the store directory
    file delete -force -- $path
    return 
}

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







-
-
-



















-
+













-
-
-



















-
+








proc ::m::vcs::check {vcs storea storeb} {
    debug.m/vcs {}
    set patha [Path $storea]
    set pathb [Path $storeb]
    set vcode [code $vcs]

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG mergable?`.

    Operation ::m::vcs::OpComplete $vcode mergable? \
	{*}[OpCmd $vcode $patha $pathb]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
	E [join $issues \n] CHILD
    } else {
	set flag [lindex $results 0]
	debug.m/vcs {--> $flag}
	return $flag
    }
}

proc ::m::vcs::merge {vcs target origin} {
    debug.m/vcs {}
    set ptarget [Path $target]
    set porigin [Path $origin]
    set vcode   [code $vcs]

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG merge`.
    
    Operation ::m::vcs::OpComplete $vcode merge \
	{*}[OpCmd $vcode $ptarget $porigin]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
	E [join $issues \n] CHILD
    }
    
    # Destroy the merged store
    cleanup $origin $vcs
    return
}

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







-
-
-



















+









-
-
-
-
-
-
-
-
-













-
+











-
+

-
+











+







    file copy   -force -- $porigin $pdst

    # Inlined rename of origin's new copy
    m futil write $pdst/%name $dstname
    
    # Split/create vcs specific special resources, if any ...

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG split`.
    
    Operation ::m::vcs::OpComplete $vcode split \
	{*}[OpCmd $vcode $porigin $pdst]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	E [join $issues \n] CHILD
    }
    return
}

proc ::m::vcs::path {store} {
    debug.m/vcs {}
    return [Path $store]
}

proc ::m::vcs::remotes {vcs store} {
    debug.m/vcs {}
    set path  [Path $store]
    set vcode [code $vcs]

    # Ask plugin for remotes it may have.
    return [$vcode remotes $path]
}

proc ::m::vcs::export {vcs store} {
    debug.m/vcs {}
    set path  [Path $store]
    set vcode [code $vcs]

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG export STORE`.

    # Ask plugin for CGI script to access the store.
    
    Operation ::m::vcs::OpComplete $vcode export \
	{*}[OpCmd $vcode $path]
    set state [OpWait]

    
    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [ ] msg
    # [ ] duration

    if {!$ok} {
	if {![llength $results]} {
	    lappend results "Failed to retrieve export script for $vcs on $path"
	    lappend results "Failed to retrieve export script for $vcode on $path"
	}
	return -errorcode {MIRROR VCS EXPORT} -code error [join $results \n]
	E [join $results \n] EXPORT
    } else {
	set script [join $results \n]
	debug.m/vcs {--> $script}
	return $script
    }
}

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

proc ::m::vcs::version {vcode iv} {
    debug.m/vcs {}

    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG version`.
    
    Operation ::m::vcs::OpComplete $vcode version \
499
500
501
502
503
504
505
506

507
508
509
510
511
512
513
460
461
462
463
464
465
466

467
468
469
470
471
472
473
474







-
+








    github detect $url
    git    detect $url
    hg     detect $url
    svn    detect $url
    fossil detect $url

    return -code error "Unable to determine vcs for $url"
    E "Unable to determine vcs for $url" DETECT
}

proc ::m::vcs::url-norm {vcode url} {
    debug.m/vcs {}
    # Normalize the incoming url
    # I.e. for a number of known sites, force the use of the https
    # they support. Further strip known irrelevant trailers.
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560

561
562
563
564
565
566
567
492
493
494
495
496
497
498


499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518

519
520
521
522
523
524
525
526







-
-




















-
+







    }
    
    return [string map $map $url]
}

proc ::m::vcs::name-from-url {vcode url} {
    debug.m/vcs {}
    upvar 1 $iv issues
    set issues {}

    # Redirect through an external command. This command is currently
    # always `mirror-vcs VCS LOG url-to-name`.
    
    Operation ::m::vcs::OpComplete $vcode url-to-name \
	{*}[OpCmd $vcode $url]
    set state [OpWait]

    dict with state {}
    # [x] ok
    # [ ] commits
    # [ ] size
    # [ ] forks
    # [x] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	if {[llength $msg]}     { lappend issues {*}$msg     }
	if {[llength $results]} { lappend issues {*}$results }
	return
	E [join $issues \n] CHILD
    } else {
	set name [lindex $results 0]
	debug.m/vcs {--> $name}
	return $name
    }
}

621
622
623
624
625
626
627
628

629
630
631
632
633
634
635
580
581
582
583
584
585
586

587
588
589
590
591
592
593
594







-
+







	    SELECT id
	    FROM   version_control_system
	    WHERE  name = :x
	}]
    }

    if {$id eq {}} {
	return -code error "Invalid vcs code or name"
	E "Invalid vcs code or name" INTERNAL
    }

    return $id
}

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

655
656
657
658
659
660
661




662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679



680
681
682
683
684
685
686
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652







+
+
+
+


















+
+
+








proc ::m::vcs::Path {dir} {
    debug.m/vcs {}
    set path [file normalize [file join [m state store] $dir]]
    debug.m/vcs {=> $path}
    return $path
}

proc ::m::vcs::E {msg args} {
    return -code error -errorcode [linsert $args 0 MIRROR VCS] $msg
}

# # ## ### ##### ######## ############# #####################
## Background operations. Based on jobs.
#
## Caller side
# - Operation DONE VCS OP ...
# - OpCmd VCS ...
#

proc ::m::vcs::OpComplete {state} {
    debug.m/vcs {}
    variable opsresult $state
    return
}

proc ::m::vcs::OpWait {} {
    debug.m/vcs {}
    vwait ::m::vcs::opsresult

    #array set __ $::m::vcs::opsresult ; parray __

    return $::m::vcs::opsresult
}

proc ::m::vcs::OpCmd {vcs args} {
    debug.m/vcs {}
    # Currently only fallback for builtin systems.
    # TODO: Query system configuration first.

Changes to lib/web/site.tcl.

22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36







-
+







package require debug
package require debug::caller
package require m::asset
package require m::db
package require m::exec
package require m::format
package require m::futil
package require m::mset
package require m::project
package require m::site
package require m::state
package require m::store
package require m::vcs

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

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







+
+
+
+
+
+
+
+
+
+
+







-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







	! "= Data dependent content ..."
	Contact
	Export		;# (See `export`)
	Search
	Submit
	Stores

	# Statistics page
	#   - cycle information (start, end, duration, last duration)
	#   - number of projects, repos, stores
	#   - min, max, average, median n.commits, size.kb
	# Project list    - # repos, link to details
	# Project details - repo list, store links!

	# constrained lists --
	# -- just primaries, no forks
	# -- per VCS, just managed by such
	
	set bytime   [m store updates]
	set byname   [m store by-name]
	set bysize   [m store by-size]
	set byvcs    [m store by-vcs]
	set issues   [m store issues] ;# excludes disabled
	set disabled [m store disabled]

	dict set stats issues   [llength $issues]
	dict set stats disabled [llength $disabled]
	dict set stats size     [m store total-size]
	dict set stats nrepos   [m repo count]
	dict set stats nmsets   [m mset count]
	dict set stats nstores  [m store count]
	dict set stats ccycle   [m state start-of-current-cycle]
	dict set stats pcycle   [m state start-of-previous-cycle]
	dict set stats issues    [llength $issues]
	dict set stats disabled  [llength $disabled]
	dict set stats size      [m store total-size]
	dict set stats nrepos    [m repo count]
	dict set stats nprojects [m project count]
	dict set stats nstores   [m store count]
	dict set stats ccycle    [m state start-of-current-cycle]
	dict set stats pcycle    [m state start-of-previous-cycle]

	List "By Last Change"     index.md          $bytime   $stats
	List "By Name, VCS, Size" index_name.md     $byname   $stats
	List "By Size, Name, VCS" index_size.md     $bysize   $stats
	List "By VCS, Name, Size" index_vcs.md      $byvcs    $stats
	List "Issues by Name"     index_issues.md   $issues   $stats
	List "Disabled by Name"   index_disabled.md $disabled $stats
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
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517

518
519
520
521
522
523
524
525







-
-
-
+
+
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+



-
-
-
-
+
+
+
+
-
-















-
-
-
+
-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-




-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
-


-
+

-
+

+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







+












+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+





-
+






-
-
-
-
-
+
+
+
+
+

-
+





-
+









-
-
+
+






-
+
+
+




+

+
+


-
+




















-
+


-
-
-
-
+
+
+
+
+








+
+
+
+
+
+
+
+
+
+



-
+







    uplevel 1 $script
    unset dst silent
    return
}

proc ::m::web::site::Stores {} {
    debug.m/web/site {}
    foreach {mset mname} [m mset all] {
	foreach store [m mset stores $mset] {
	    Store $mset $mname $store

    foreach {project name} [m project all] {
	foreach store [m project stores $project] {
	    Store $project $name $store
	}
    }
    return
}

proc ::m::web::site::RLink {repo {follow 1}} {
    debug.m/web/site {}

    set ri [m repo get $repo]
    dict with ri {}
    # active, issues, url, store

    set active [expr {$active  ? "" : "[I images/off.svg "Offline"]"}]
    set issues [expr {!$issues ? "" : "[I images/bad.svg "Attend"]"}]
    if {!$follow} { set origin {} }
    if {$origin ne {}} {
	set origin " a [ForkLogo] from [OLink $origin]"
    }
    set label $active$issues$url
    
    return [LB $url $label]$origin
}

proc ::m::web::site::OLink {repo} {
    debug.m/web/site {}

    set ri [m repo get $repo]
    dict with ri {}
    # active, issues, url, store

    set active [expr {$active  ? "" : "[I images/off.svg "Offline"]"}]
    set issues [expr {!$issues ? "" : "[I images/bad.svg "Attend"]"}]
    set label  $active$issues$url
    
    return [LB store_${store}.html $label]
}

proc ::m::web::site::StatsTime {min_sec max_sec win_sec} {
    debug.m/web/site {}

    # See also ::m::repo::times, ::m::glue::StatsTime
    
    set min_sec [expr {$min_sec < 0 ? "+Inf" : [m format interval $min_sec]}]
    set max_sec [m format interval $max_sec]
    set spent   "$min_sec ... $max_sec"
    set win_sec [m format win $win_sec]
    set n       [llength $win_sec]
    if {$n} {
	set win_sec [m format win-trim $win_sec [m state store-window-size]]
	set total   [expr [join $win_sec +]]
	set avg     [m format interval [format %.0f [expr {double($total)/$n}]]]
	append spent " \[avg $avg (over $n)]"
    }
    return $spent
}

proc ::m::web::site::Commits {commits commitp} {
    debug.m/web/site {}
    
    if {$commitp != $commits} {
	set delta [expr {$commits - $commitp}]
	if {$delta > 0} {
	    set delta +$delta
	}
	append commits " ($commitp ($delta))"
    }
    return $commits
}

proc ::m::web::site::Size {size sizep} {
    debug.m/web/site {}
    
    set dsize [m format size $size]
    if {$sizep != $size} {
	set dsizep [m format size $sizep]
	if {$size < $sizep} {
	    # shrink
	    set delta -[m format size [expr {$sizep - $size}]]
	} else {
	    # grow
	    set delta +[m format size [expr {$size - $sizep}]]
	}
	append dsize " ($dsizep ($delta))"
    }
    return $dsize
}

proc ::m::web::site::ExportStore {vcs store} {
    debug.m/web/site {}
    
    set export [m vcs export $vcs $store]
    if {$export ne {}} {
	set path external/local_${store}
	WX static/$path $export
	set export [LB $path {Local Site}]
    }

    return $export
}

proc ::m::web::site::StoreForks {pname url store serial forks} {
    debug.m/web/site {}

    set series [m store getx $forks]
    set page   store_${store}_forks_${serial}
    set up     [LB store_${store}.html $url]
    set title  "[llength $forks] [ForkLogo] of $up"

    ListSimple $pname $title $page.md $series
    return ${page}.html
}

proc ::m::web::site::Store {mset mname store} {
proc ::m::web::site::Store {project pname store} {
    debug.m/web/site {}

    # Get page pieces ...

    lassign [m store remotes $store] remotes plugin
    lappend r Remotes $remotes
    if {[llength $plugin]} {
    
    set urls  [m store remotes $store]
    set repos [lmap u $urls  { m repo id $u }]
    set links [lmap r $repos { RLink $r }]
	lappend r {*}$plugin
    }

    set sd  [m store get $store]
    dict with sd {}
    # -> size, sizep
    #    commits, commitp
    #    vcs
    #    vcsname
    #    created
    #    changed
    #    updated
    #    attend
    #    active
    #    remote
    #    min_sec, max_sec, win_sec

    set min_sec [expr {$min_sec < 0 ? "+Inf" : [m format interval $min_sec]}]
    set max_sec [m format interval $max_sec]

    set spent [StatsTime $min_sec $max_sec $win_sec]
    set spent "$min_sec ... $max_sec"

    set win_sec [split [string trim $win_sec ,] ,]
    set n       [llength $win_sec]
    if {$n} {
	set maxn [m state store-window-size]
	if {$n > $maxn} {
	    set over    [expr {$n - $maxn}]
	    set win_sec [lreplace $win_sec 0 ${over}-1]
	    set n       [llength $win_sec]
	}
	set total [expr [join $win_sec +]]
	set avg   [m format interval [format %.0f [expr {double($total)/$n}]]]
	append spent " ($avg * $n)"
    }
    
    set simg [StatusRefs $attend $active $remote]

    lassign [m vcs caps $store] stdout stderr
    set logo [T "Operation" $stdout]
    set loge [T "Notes & Errors" $stderr]

    if {$commitp != $commits} {
	set delta [expr {$commits - $commitp}]
	if {$delta > 0} {
	    set delta +$delta
	}
	append commits " ($commitp ($delta))"
    }

    set dsize [m format size $size]
    set commits [Commits $commits $commitp]
    set dsize   [Size $size $sizep]
    if {$sizep != $size} {
	set dsizep [m format size $sizep]
	if {$size < $sizep} {
	    # shrink
	    set delta -[m format size [expr {$sizep - $size}]]
	} else {
	    # grow
	    set delta +[m format size [expr {$size - $sizep}]]
	}
	append dsize " ($dsizep ($delta))"
    }

    set export [m vcs export $vcs $store]
    set export  [ExportStore $vcs $store]
    set vcslogo [VCSLogo [m vcs code $vcs] $vcsname]
    if {$export ne {}} {
	set f external/local_${store}
	WX static/$f $export
	set export [LB $f {Local Site}]
    }
    

    # Assemble page ...

    append text [H $mname]
    append text [H $pname]
    append text |||| \n
    append text |---|---|---| \n
    append text |---|---:|---| \n

    if {![llength $urls]} {
	R $vcslogo {} {}
    } else {
	set threshold 5
    R $simg   {} "[IH 32 images/logo/[m vcs code $vcs].svg $vcsname] $vcsname"
	
	set left $vcslogo
	foreach r $repos l $links u $urls {
	    R $left {} $l
	    set left {}

	    # For each repo show the forks, up to a threshold. If
	    # there are more than that a separate page is created for
	    # the list and linked.
	    
	    set forks [m repo forks $r]
	    set nforks [llength $forks]
	    if {$nforks} {
		incr m
		set links [lmap f $forks { OLink $f }]
		foreach link $links {
		    R {} [ForkLogo] $link
		    incr k
		    if {$k < $threshold} continue
		    set more [expr {$nforks - $threshold}]
		    R {} {} [LB [StoreForks $pname $u $store $m $forks] "+ $more more"]
		    break
		}
		unset k
	    }
	}
    }
    
    R Size    {} $dsize
    R Commits {} $commits
    if {$export ne {}} {
	R {} {} $export
    }
    R {Update Stats} {} $spent
    R {Last Change}  {} [m format epoch $changed]
    R {Last Check}   {} [set lc [m format epoch $updated]]
    R Created        {} [m format epoch $created]

    set active 1
    foreach {label urls} $r {
	R $label {}
	foreach url [lsort -dict $urls] {
	    incr id
	    set u [LB $url $url]
	    set a {}
	    if {$active} {
		set a [dict get	[m repo get [m repo id $url]] active]
		if {$a} {
		    set a "" ;#[I images/ok.svg "&nbsp;"]
		} else {
		    set a [I images/off.svg "-"]
		}
	    }
	    R ${id}. $a $u
	}
	unset -nocomplain id
	incr active -1
    }
    append text \n

    append text "## Messages as of last check on $lc" \n\n
    append text $logo \n
    append text $loge \n
    append text \n
    append text [F]

    W pages/store_${store}.md $text
    return
}

proc ::m::web::site::Contact {} {
    debug.m/web/site {}
    append text [H Contact]
    append text [F]
    W pages/contact.md $text
    return
}

proc ::m::web::site::ListSimple {title subtitle page series} {
    # A cut down form of `List`. No sorting. No other stats.
    
    debug.m/web/site {}

    set hvcs     VCS
    set hsize    Size
    set hname    Project
    set hchan    Changed

    append text [H $title]
    append text $subtitle \n
    append text \n

    append text "||$hname|Repository||$hvcs|$hsize|$hchan|Updated|Created|" \n
    append text "|---|---|---|---|---|---:|---|---|---|" \n

    set fork [ForkLogo]

    set mname {}
    set last {}
    foreach row $series {
	dict with row {}
	
	# store mname vcode changed updated created size active remote attend
	# -- origin url
	set tag {}

	if {$created eq "."} {
	    append text "||||||||||" \n
	    continue
	}

	set img     [StatusRefs $attend $active $remote]
	set size    [m format size $size]
	set changed [m format epoch $changed]
	set updated [m format epoch $updated]
	set created [m format epoch $created]

	set vcode   [VCSLogo $vcode $vcode]
       	set vcode   [LB store_${store}.html $vcode]
	
	if {$mname  ne {}} { set mname [LB store_${store}.html $mname] }
	set url                        [LB store_${store}.html $url]
	if {$origin ne {}} { append tag $fork }
	
	append text "|$img|$mname|$url|$tag|$vcode|$size|$changed|$updated|$created|" \n
	set last $mname
    }
    append text \n\n

    append text [F]
    W pages/$page $text
    return
}

proc ::m::web::site::List {suffix page series stats} {
    debug.m/web/site {}

    
    dict with stats {}
    # issues
    # disabled
    # size
    # nrepos
    # nmsets
    # nprojects
    # nstores
    # ccycle
    # pcycle

    append text [H "Index ($suffix)"]

    set hvcs     [L index_vcs.html      VCS          ]
    set hsize    [L index_size.html     Size         ]
    set hname    [L index_name.html     {Mirror Set} ]
    set hchan    [L index.html          Changed      ]
    set issues   [L index_issues.html   "Issues: $issues" ]
    set hvcs     [L index_vcs.html      VCS                   ]
    set hsize    [L index_size.html     Size                  ]
    set hname    [L index_name.html     Project               ]
    set hchan    [L index.html          Changed               ]
    set issues   [L index_issues.html   "Issues: $issues"     ]
    set disabled [L index_disabled.html "Disabled: $disabled" ]

    
    set ccf [m format epoch $ccycle]
    set pcf [m format epoch $pcycle]
    set dt  [expr {$ccycle - $pcycle}]
    set dtf [m format interval $dt]

    append text  "Sets: " $nmsets ,
    append text  "Projects: " $nprojects ,
    append text " Repos: " $nrepos ,
    append text " Stores: " $nstores ,
    append text " Size: " [m format size $size] ,
    append text " " $issues , \n
    append text " " $disabled \n
    append text \n
    append text "Cycles: Current began __" $ccf "__, "
    append text            "Last began __" $pcf "__, taking __" $dtf "__" \n
    append text \n
    append text "||$hname|$hvcs|$hsize|$hchan|Updated|Created|" \n
    append text "|---|---|---|---:|---|---|---|" \n
    append text "||$hname|Repository||$hvcs|$hsize|$hchan|Updated|Created|" \n
    append text "|---|---|---|---|---|---:|---|---|---|" \n

    # Disable insertion of cycle flags for all tables but sorted by change.
    if {$page ne "index.md"} {
	set pcycle -1
	set ccycle -1
    }
    

    set fork [ForkLogo]

    set mname {}
    set last {}
    foreach row $series {
	dict with row {}

	# store mname vcode changed updated created size active remote attend
	# -- origin url
	set tag {}

	if {$created eq "."} {
	    append text "||||||||" \n
	    append text "||||||||||" \n
	    continue
	}

	if {$changed ne {}} {
	    if {$changed < $ccycle} {
		append text "||__$ccf Start Of Current Cycle__||||||" \n
		set ccycle -1 ;# Prevent further triggers
	    }
	    if {$changed < $pcycle} {
		append text "||__$pcf Start Of Last Cycle__||||||" \n
		set pcycle -1 ;# Prevent further triggers
	    }
	}

	set img     [StatusRefs $attend $active $remote]
	set size    [m format size $size]
	set changed [m format epoch $changed]
	set updated [m format epoch $updated]
	set created [m format epoch $created]

	set vcode   "[IH 32 images/logo/${vcode}.svg $vcode] $vcode"
	set vcode   [VCSLogo $vcode $vcode]
       	set vcode   [LB store_${store}.html $vcode]
	
	if {$mname ne {}} {
	    set mname [LB store_${store}.html $mname]
	}
	append text "|$img|$mname|$vcode|$size|$changed|$updated|$created|" \n
	if {$mname  ne {}} { set mname [LB store_${store}.html $mname] }
	set url                        [LB store_${store}.html $url]
	if {$origin ne {}} { append tag $fork }
	
	append text "|$img|$mname|$url|$tag|$vcode|$size|$changed|$updated|$created|" \n
	set last $mname
    }
    append text \n\n

    append text [F]
    W pages/$page $text
    return
}

proc ::m::web::site::VCSLogo {vcode vcsname} {
    debug.m/web/site {}
    return "[IH 32 images/logo/$vcode.svg $vcsname] $vcsname"
}

proc ::m::web::site::ForkLogo {} {
    debug.m/web/site {}
    IH 32 images/fork.svg Fork
}

proc ::m::web::site::Export {} {
    debug.m/web/site {}
    W static/spec.txt [m mset spec]
    W static/spec.txt [m project spec]
    return
}

proc ::m::web::site::Search {} {
    debug.m/web/site {}
    WX static/search [CGI mirror-search]
    return
445
446
447
448
449
450
451
452

453
454
455
456
457

458
459

460
461
462
463
464
465
466
467
468
604
605
606
607
608
609
610

611
612
613
614
615

616


617
618

619
620
621
622
623
624
625







-
+




-
+
-
-
+

-







}

proc ::m::web::site::Sync {} {
    debug.m/web/site {}

    # Data flows
    # - Main
    #   - mset_pending			local, no sync
    #   - repo_pending			local, no sync
    #   - reply				local, no sync
    #   - rolodex			local, no sync
    #   - schema			local, no sync
    #
    #   - mirror_set			[1] join/view pushed to site
    #   - project			[1] join/view pushed to site
    #   - name				[1] store_index, total replacement
    #   - repository			[1]
    #   - repository			[1] store_index, total replacement
    #   - store				[1]
    #   - store_times			[1]
    #   - version_control_system	[1], plus copy to vcs
    #
    #   - rejected			push to site rejected, total replacement
    #   - submission			pull from site (insert or update)
    #   - submission_handled		push to site, deletions in submission
    #
    # - Site
619
620
621
622
623
624
625
626

627
628
629
630
631




632
633

634
635
636
637

638
639
640
641
642
643

644
645
646
647
648
649

650
651
652

653
654
655
656
657
658
659
660
661

662
663
664
665

666
667
668
669
670
671
672
673
674
675

676
677
678
679
680
681


682
683











684
685
686
687
688
689
690
776
777
778
779
780
781
782

783
784




785
786
787
788
789

790




791






792

793




794

795

796
797
798
799
800
801
802
803
804

805

806
807

808
809
810
811
812
813
814
815
816
817

818
819
820
821
822
823
824
825
826


827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844







-
+

-
-
-
-
+
+
+
+

-
+
-
-
-
-
+
-
-
-
-
-
-
+
-

-
-
-
-
+
-

-
+








-
+
-


-
+









-
+






+
+
-
-
+
+
+
+
+
+
+
+
+
+
+







    # database. Implemented as `delete all old ; insert all new`.

    m site eval { DELETE FROM store_index }

    # m store search '' (inlined, simply all)
    m db eval {
	SELECT S.id      AS store
	,      N.name    AS mname
	,      (SELECT max (P.name) FROM project P, repository R WHERE P.id = R.project AND R.store = S.id) AS pname
	,      V.code    AS vcode
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.changed AS changed
	,      S.updated AS updated
	,      S.created AS created
	,      (SELECT sum (has_issues) FROM repository R WHERE R.store = S.id) AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)
	,      (SELECT count (*)         FROM repository R WHERE R.store = S.id) AS remote
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
	,      (SELECT sum   (is_active) FROM repository R WHERE R.store = S.id) AS active
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	FROM store                  S
	,    mirror_set             M
	,    version_control_system V
	,    name                   N
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	WHERE S.vcs = V.id
	AND   M.name    = N.id
    } {
	# store, mname, vcode, changed, updated, created, size, remote, active, attend
	# store, pname, vcode, changed, updated, created, size, remote, active, attend

	set page    store_${store}.html
	set status  [StatusIcons $attend $active $remote]
	set remotes [m db eval {
	    SELECT R.url
	    FROM repository R
	    ,    store      S
	    WHERE S.id   = :store
	    AND   S.vcs  = R.vcs
	    AND   S.id   = R.store
	    AND   S.mset = R.mset
	}]
	
	lappend remotes $mname
	lappend remotes $pname
	set remotes [string tolower [join $remotes { }]]
	# We are using the remotes field for the entire text we can
	# search over.  Should rename the field, not bothering until
	# we need a larger schema change it can be folded into.

	m site eval {
	    INSERT
	    INTO store_index
	    VALUES ( NULL,
		     :mname, :vcode, :page, :remotes, :status,
		     :pname, :vcode, :page, :remotes, :status,
		     :size, :changed, :updated, :created )
	}
    }

    # Copy the VCS information

    # Logically this ...
    if 0 {m site eval {
    m site eval { DELETE FROM vcs }

	DELETE FROM vcs
	;
	INSERT INTO VCS
	SELECT id, code, name
	FROM   version_control_system
    }}

    # It is done like below because we are operating on two databases
    # here. And it is simpler than to attach/detach one of the
    # databases into the other connection.
    m site eval { DELETE FROM vcs }
    m db eval {
	SELECT id
	,      code
	,      name
	FROM version_control_system
    } {
	m site eval {
1144
1145
1146
1147
1148
1149
1150




1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308







+
+
+
+
</svg>
static/images/logo/fossil.svg<svg width="320" height="440" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(-223.69964,-322.98867)" style="fill:#808080;stroke:none"><g transform="matrix(3.4464775e-2,0,0,3.4464775e-2,64.3835,244.04121)" visibility="visible" style="visibility:visible"><path d="M 7207,8311 L 7191,8307 L 7176,8299 L 7162,8289 L 7149,8276 L 7136,8260 L 7124,8242 L 7103,8197 L 7084,8142 L 7069,8078 L 7057,8006 L 7048,7926 L 7041,7744 L 7048,7538 L 7068,7312 L 7104,7073 L 7153,6836 L 7210,6617 L 7274,6421 L 7343,6252 L 7379,6179 L 7415,6115 L 7451,6061 L 7487,6016 L 7522,5981 L 7540,5967 L 7557,5957 L 7574,5949 L 7591,5944 L 7608,5943 L 7624,5944 L 7640,5948 L 7655,5956 L 7669,5966 L 7683,5979 L 7695,5995 L 7707,6013 L 7729,6058 L 7747,6113 L 7762,6177 L 7774,6249 L 7783,6329 L 7790,6511 L 7784,6717 L 7763,6943 L 7727,7182 L 7679,7419 L 7621,7638 L 7557,7834 L 7488,8003 L 7452,8075 L 7416,8139 L 7380,8194 L 7344,8239 L 7309,8274 L 7291,8287 L 7274,8298 L 7257,8306 L 7240,8310 L 7223,8312 L 7207,8311 z"/><path d="M 7607,10301 L 7592,10303 L 7576,10302 L 7560,10299 L 7544,10294 L 7527,10286 L 7511,10275 L 7477,10248 L 7442,10212 L 7408,10169 L 7373,10117 L 7339,10058 L 7272,9921 L 7210,9761 L 7154,9581 L 7106,9386 L 7070,9188 L 7048,9001 L 7040,8829 L 7045,8677 L 7052,8610 L 7063,8549 L 7077,8494 L 7094,8448 L 7114,8409 L 7125,8393 L 7136,8379 L 7149,8368 L 7162,8358 L 7176,8351 L 7191,8347 L 7206,8345 L 7222,8346 L 7238,8349 L 7254,8354 L 7271,8362 L 7288,8372 L 7322,8399 L 7356,8435 L 7391,8479 L 7425,8531 L 7460,8589 L 7526,8726 L 7589,8887 L 7645,9066 L 7693,9262 L 7729,9460 L 7750,9647 L 7758,9818 L 7753,9971 L 7746,10038 L 7735,10099 L 7721,10153 L 7704,10200 L 7684,10239 L 7673,10255 L 7662,10269 L 7649,10280 L 7636,10290 L 7622,10297 L 7607,10301 z"/><path d="M 8749,11736 L 8737,11743 L 8724,11749 L 8709,11752 L 8694,11754 L 8677,11754 L 8659,11751 L 8621,11742 L 8579,11726 L 8534,11703 L 8486,11673 L 8436,11638 L 8330,11551 L 8219,11443 L 8107,11316 L 7995,11173 L 7892,11023 L 7805,10878 L 7735,10739 L 7684,10612 L 7665,10553 L 7652,10499 L 7643,10449 L 7640,10404 L 7643,10365 L 7646,10348 L 7651,10332 L 7657,10317 L 7665,10304 L 7674,10293 L 7685,10284 L 7697,10277 L 7711,10271 L 7725,10268 L 7741,10266 L 7757,10266 L 7775,10268 L 7814,10278 L 7855,10294 L 7900,10317 L 7948,10346 L 7998,10381 L 8104,10469 L 8215,10577 L 8328,10704 L 8440,10847 L 8543,10996 L 8630,11142 L 8699,11280 L 8751,11408 L 8769,11466 L 8783,11521 L 8791,11570 L 8794,11615 L 8791,11655 L 8788,11672 L 8783,11688 L 8777,11703 L 8769,11716 L 8760,11727 L 8749,11736 z"/><path d="M 10683,12127 L 10680,12139 L 10674,12151 L 10666,12162 L 10656,12173 L 10643,12183 L 10628,12192 L 10592,12209 L 10547,12224 L 10494,12237 L 10434,12247 L 10368,12255 L 10217,12263 L 10045,12261 L 9857,12248 L 9657,12224 L 9458,12190 L 9275,12149 L 9110,12103 L 8968,12052 L 8906,12025 L 8852,11999 L 8805,11972 L 8766,11945 L 8736,11918 L 8725,11904 L 8715,11891 L 8708,11878 L 8704,11865 L 8702,11852 L 8702,11840 L 8705,11828 L 8711,11816 L 8719,11805 L 8729,11794 L 8742,11784 L 8757,11775 L 8793,11758 L 8838,11743 L 8891,11730 L 8950,11720 L 9017,11712 L 9168,11704 L 9339,11706 L 9527,11719 L 9727,11743 L 9926,11777 L 10110,11818 L 10275,11864 L 10417,11915 L 10479,11942 L 10533,11968 L 10580,11995 L 10619,12022 L 10649,12049 L 10660,12063 L 10670,12076 L 10677,12089 L 10681,12102 L 10683,12115 L 10683,12127 z"/><path d="M 10761,12053 L 10758,12043 L 10758,12032 L 10760,12021 L 10763,12009 L 10769,11996 L 10777,11983 L 10799,11955 L 10828,11925 L 10864,11894 L 10955,11826 L 11070,11755 L 11206,11682 L 11359,11610 L 11526,11540 L 11696,11478 L 11858,11427 L 12007,11389 L 12140,11363 L 12253,11350 L 12301,11349 L 12343,11351 L 12378,11357 L 12392,11361 L 12405,11367 L 12416,11373 L 12425,11380 L 12432,11388 L 12437,11397 L 12440,11407 L 12440,11418 L 12438,11429 L 12435,11441 L 12429,11454 L 12421,11467 L 12399,11495 L 12370,11525 L 12334,11556 L 12243,11624 L 12127,11695 L 11992,11768 L 11838,11840 L 11671,11910 L 11501,11972 L 11340,12023 L 11191,12061 L 11058,12087 L 10945,12100 L 10897,12101 L 10855,12099 L 10820,12093 L 10806,12089 L 10793,12083 L 10782,12077 L 10773,12070 L 10766,12062 L 10761,12053 z"/><path d="M 12410,11353 L 12408,11351 L 12406,11349 L 12404,11344 L 12402,11337 L 12402,11330 L 12402,11322 L 12403,11312 L 12409,11291 L 12418,11267 L 12430,11239 L 12465,11175 L 12511,11102 L 12568,11022 L 12635,10936 L 12710,10847 L 12788,10761 L 12864,10683 L 12937,10616 L 13003,10560 L 13061,10518 L 13087,10502 L 13110,10490 L 13130,10482 L 13139,10479 L 13147,10478 L 13154,10477 L 13161,10478 L 13166,10480 L 13169,10481 L 13171,10483 L 13173,10485 L 13175,10487 L 13177,10492 L 13179,10499 L 13180,10506 L 13179,10514 L 13178,10524 L 13173,10545 L 13164,10569 L 13152,10597 L 13117,10661 L 13071,10734 L 13014,10814 L 12947,10900 L 12872,10989 L 12794,11075 L 12718,11153 L 12645,11220 L 12579,11276 L 12521,11318 L 12495,11334 L 12472,11346 L 12451,11354 L 12442,11357 L 12434,11358 L 12427,11359 L 12420,11358 L 12415,11356 L 12412,11355 L 12410,11353 z"/><path d="M 8102,11826 L 8102,11791 L 8101,11755 L 8100,11720 L 8098,11685 L 8096,11651 L 8093,11617 L 8089,11583 L 8086,11550 L 8081,11518 L 8077,11487 L 8072,11456 L 8066,11427 L 8060,11398 L 8054,11371 L 8047,11344 L 8039,11319 L 8032,11296 L 8024,11273 L 8016,11252 L 8007,11233 L 7999,11215 L 7990,11198 L 7980,11184 L 7971,11171 L 7961,11159 L 7951,11149 L 7941,11141 L 7931,11135 L 7921,11131 L 7911,11128 L 7901,11127 L 7901,11127 L 7891,11128 L 7881,11131 L 7871,11135 L 7861,11141 L 7851,11149 L 7841,11159 L 7831,11171 L 7822,11184 L 7812,11198 L 7803,11215 L 7795,11233 L 7786,11252 L 7778,11273 L 7770,11296 L 7763,11319 L 7755,11344 L 7748,11371 L 7742,11398 L 7736,11427 L 7730,11456 L 7725,11487 L 7721,11518 L 7716,11550 L 7713,11583 L 7709,11617 L 7706,11651 L 7704,11685 L 7702,11720 L 7701,11755 L 7700,11791 L 7700,11826 L 7700,11826 L 7700,11861 L 7701,11897 L 7702,11932 L 7704,11967 L 7706,12001 L 7709,12035 L 7713,12069 L 7716,12102 L 7721,12134 L 7725,12165 L 7730,12196 L 7736,12225 L 7742,12254 L 7748,12281 L 7755,12308 L 7763,12333 L 7770,12356 L 7778,12379 L 7786,12400 L 7795,12419 L 7803,12437 L 7812,12454 L 7822,12468 L 7831,12481 L 7841,12493 L 7851,12503 L 7861,12511 L 7871,12517 L 7881,12521 L 7891,12524 L 7901,12525 L 7901,12525 L 7911,12524 L 7921,12521 L 7931,12517 L 7941,12511 L 7951,12503 L 7961,12493 L 7971,12481 L 7980,12468 L 7990,12454 L 7999,12437 L 8007,12419 L 8016,12400 L 8024,12379 L 8032,12356 L 8039,12333 L 8047,12308 L 8054,12281 L 8060,12254 L 8066,12225 L 8072,12196 L 8077,12165 L 8081,12134 L 8086,12102 L 8089,12069 L 8093,12035 L 8096,12001 L 8098,11967 L 8100,11932 L 8101,11897 L 8102,11861 L 8102,11826 z"/><path d="M 7825,12576 L 7819,12584 L 7810,12591 L 7801,12597 L 7789,12601 L 7777,12604 L 7762,12606 L 7730,12607 L 7692,12603 L 7649,12595 L 7602,12583 L 7551,12567 L 7438,12522 L 7313,12463 L 7181,12391 L 7043,12306 L 6910,12215 L 6790,12123 L 6685,12033 L 6599,11948 L 6563,11907 L 6533,11869 L 6508,11833 L 6490,11800 L 6477,11770 L 6473,11756 L 6471,11743 L 6470,11731 L 6471,11719 L 6474,11709 L 6479,11700 L 6486,11692 L 6494,11685 L 6504,11679 L 6515,11675 L 6528,11672 L 6542,11670 L 6575,11669 L 6613,11673 L 6656,11681 L 6703,11693 L 6754,11709 L 6867,11753 L 6992,11812 L 7124,11884 L 7262,11969 L 7395,12061 L 7515,12153 L 7619,12243 L 7706,12328 L 7741,12369 L 7771,12407 L 7796,12443 L 7815,12476 L 7827,12506 L 7831,12520 L 7834,12533 L 7834,12545 L 7833,12557 L 7830,12567 L 7825,12576 z"/><path d="M 6460,11695 L 6457,11697 L 6454,11699 L 6451,11701 L 6447,11702 L 6443,11703 L 6438,11703 L 6428,11702 L 6416,11700 L 6403,11696 L 6389,11691 L 6374,11684 L 6342,11666 L 6307,11643 L 6270,11616 L 6233,11584 L 6197,11550 L 6166,11517 L 6139,11485 L 6118,11455 L 6110,11441 L 6103,11427 L 6098,11415 L 6094,11404 L 6092,11393 L 6092,11389 L 6092,11385 L 6093,11381 L 6094,11377 L 6096,11374 L 6098,11371 L 6101,11369 L 6104,11366 L 6107,11365 L 6111,11364 L 6115,11363 L 6120,11363 L 6130,11363 L 6142,11366 L 6154,11370 L 6168,11375 L 6183,11382 L 6216,11399 L 6251,11422 L 6288,11450 L 6325,11481 L 6361,11515 L 6392,11548 L 6418,11581 L 6439,11611 L 6448,11625 L 6455,11638 L 6460,11651 L 6464,11662 L 6465,11672 L 6466,11677 L 6465,11681 L 6465,11685 L 6464,11689 L 6462,11692 L 6460,11695 z"/><path d="M 13184,10437 L 13182,10436 L 13179,10434 L 13175,10430 L 13171,10424 L 13168,10418 L 13166,10410 L 13164,10401 L 13163,10379 L 13164,10353 L 13167,10322 L 13179,10251 L 13200,10167 L 13229,10073 L 13266,9970 L 13309,9862 L 13357,9756 L 13405,9659 L 13453,9572 L 13498,9499 L 13540,9440 L 13560,9416 L 13578,9398 L 13595,9384 L 13602,9378 L 13610,9374 L 13616,9372 L 13623,9370 L 13629,9371 L 13631,9371 L 13634,9372 L 13636,9373 L 13639,9375 L 13643,9379 L 13646,9385 L 13649,9391 L 13651,9399 L 13653,9408 L 13655,9430 L 13654,9456 L 13651,9487 L 13638,9558 L 13617,9642 L 13588,9736 L 13551,9839 L 13508,9947 L 13461,10053 L 13413,10150 L 13365,10237 L 13320,10311 L 13278,10369 L 13258,10393 L 13240,10411 L 13223,10425 L 13216,10431 L 13208,10435 L 13202,10437 L 13195,10439 L 13189,10438 L 13187,10438 L 13184,10437 z"/><path d="M 10098,10825 L 10098,10790 L 10097,10754 L 10096,10719 L 10094,10684 L 10092,10650 L 10089,10616 L 10086,10582 L 10082,10549 L 10078,10517 L 10073,10486 L 10068,10455 L 10062,10426 L 10056,10397 L 10050,10370 L 10043,10343 L 10036,10318 L 10029,10295 L 10021,10272 L 10013,10251 L 10004,10232 L 9996,10214 L 9987,10197 L 9977,10183 L 9968,10170 L 9959,10158 L 9949,10148 L 9939,10140 L 9929,10134 L 9919,10130 L 9909,10127 L 9899,10126 L 9899,10126 L 9889,10127 L 9879,10130 L 9869,10134 L 9859,10140 L 9849,10148 L 9839,10158 L 9830,10170 L 9821,10183 L 9811,10197 L 9802,10214 L 9794,10232 L 9785,10251 L 9777,10272 L 9769,10295 L 9762,10318 L 9755,10343 L 9748,10370 L 9742,10397 L 9736,10426 L 9730,10455 L 9725,10486 L 9720,10517 L 9716,10549 L 9712,10582 L 9709,10616 L 9706,10650 L 9704,10684 L 9702,10719 L 9701,10754 L 9700,10790 L 9700,10825 L 9700,10825 L 9700,10860 L 9701,10896 L 9702,10931 L 9704,10966 L 9706,11000 L 9709,11034 L 9712,11068 L 9716,11101 L 9720,11133 L 9725,11164 L 9730,11195 L 9736,11224 L 9742,11253 L 9748,11280 L 9755,11307 L 9762,11332 L 9769,11355 L 9777,11378 L 9785,11399 L 9794,11418 L 9802,11436 L 9811,11453 L 9821,11467 L 9830,11480 L 9839,11492 L 9849,11502 L 9859,11510 L 9869,11516 L 9879,11520 L 9889,11523 L 9899,11524 L 9899,11524 L 9909,11523 L 9919,11520 L 9929,11516 L 9939,11510 L 9949,11502 L 9959,11492 L 9968,11480 L 9977,11467 L 9987,11453 L 9996,11436 L 10004,11418 L 10013,11399 L 10021,11378 L 10029,11355 L 10036,11332 L 10043,11307 L 10050,11280 L 10056,11253 L 10062,11224 L 10068,11195 L 10073,11164 L 10078,11133 L 10082,11101 L 10086,11068 L 10089,11034 L 10092,11000 L 10094,10966 L 10096,10931 L 10097,10896 L 10098,10860 L 10098,10825 z"/><path d="M 9827,11575 L 9821,11583 L 9812,11590 L 9803,11596 L 9791,11600 L 9779,11603 L 9764,11605 L 9732,11606 L 9694,11602 L 9651,11594 L 9604,11582 L 9553,11566 L 9440,11521 L 9315,11462 L 9183,11390 L 9045,11305 L 8912,11214 L 8792,11122 L 8687,11032 L 8601,10947 L 8565,10906 L 8535,10868 L 8510,10832 L 8492,10799 L 8479,10769 L 8475,10755 L 8473,10742 L 8472,10730 L 8473,10718 L 8476,10708 L 8481,10699 L 8488,10691 L 8496,10684 L 8506,10678 L 8517,10674 L 8530,10671 L 8544,10669 L 8577,10668 L 8615,10672 L 8658,10680 L 8705,10692 L 8756,10708 L 8869,10752 L 8994,10811 L 9126,10883 L 9264,10968 L 9397,11060 L 9517,11152 L 9621,11242 L 9708,11327 L 9743,11368 L 9773,11406 L 9798,11442 L 9817,11475 L 9829,11505 L 9833,11519 L 9836,11532 L 9836,11544 L 9835,11556 L 9832,11566 L 9827,11575 z"/><path d="M 6085,9230 L 6075,9220 L 6067,9209 L 6060,9197 L 6055,9184 L 6050,9169 L 6047,9154 L 6045,9120 L 6048,9083 L 6055,9042 L 6067,8998 L 6083,8952 L 6104,8904 L 6128,8853 L 6190,8749 L 6266,8641 L 6357,8533 L 6456,8432 L 6555,8346 L 6653,8274 L 6701,8245 L 6747,8220 L 6791,8199 L 6833,8183 L 6873,8172 L 6910,8165 L 6944,8164 L 6960,8166 L 6975,8169 L 6989,8173 L 7001,8179 L 7013,8186 L 7024,8194 L 7034,8204 L 7042,8215 L 7049,8227 L 7054,8241 L 7059,8255 L 7062,8271 L 7064,8304 L 7062,8342 L 7054,8383 L 7043,8426 L 7027,8473 L 7006,8521 L 6981,8571 L 6920,8676 L 6843,8784 L 6753,8892 L 6654,8993 L 6554,9079 L 6456,9151 L 6409,9180 L 6362,9205 L 6318,9226 L 6276,9242 L 6236,9253 L 6199,9259 L 6165,9260 L 6149,9259 L 6134,9256 L 6120,9251 L 6108,9246 L 6096,9239 L 6085,9230 z"/><path d="M 5910,9183 L 5900,9185 L 5890,9184 L 5879,9182 L 5868,9177 L 5856,9171 L 5845,9163 L 5820,9141 L 5795,9113 L 5769,9078 L 5743,9037 L 5716,8991 L 5663,8882 L 5611,8754 L 5561,8612 L 5516,8456 L 5479,8299 L 5451,8150 L 5434,8014 L 5427,7893 L 5427,7839 L 5430,7791 L 5435,7748 L 5443,7711 L 5454,7680 L 5460,7667 L 5467,7656 L 5474,7647 L 5483,7639 L 5491,7634 L 5501,7630 L 5511,7628 L 5521,7629 L 5532,7631 L 5543,7636 L 5555,7642 L 5566,7650 L 5591,7671 L 5616,7700 L 5642,7735 L 5668,7776 L 5695,7822 L 5748,7931 L 5800,8058 L 5850,8201 L 5895,8357 L 5932,8514 L 5960,8663 L 5977,8799 L 5984,8920 L 5984,8974 L 5981,9022 L 5975,9065 L 5967,9102 L 5957,9133 L 5951,9146 L 5944,9157 L 5936,9166 L 5928,9174 L 5919,9179 L 5910,9183 z"/><path d="M 8630,9344 L 8623,9336 L 8617,9328 L 8613,9318 L 8609,9306 L 8607,9294 L 8607,9281 L 8609,9251 L 8615,9217 L 8626,9180 L 8642,9140 L 8662,9097 L 8713,9004 L 8779,8903 L 8858,8798 L 8950,8691 L 9048,8589 L 9144,8500 L 9238,8425 L 9325,8365 L 9366,8341 L 9405,8321 L 9440,8306 L 9473,8296 L 9503,8291 L 9516,8291 L 9529,8291 L 9540,8294 L 9550,8297 L 9560,8302 L 9568,8308 L 9575,8316 L 9581,8325 L 9585,8335 L 9588,8346 L 9590,8358 L 9591,8372 L 9589,8401 L 9582,8435 L 9571,8472 L 9556,8512 L 9536,8555 L 9485,8648 L 9419,8749 L 9339,8854 L 9248,8961 L 9150,9063 L 9054,9152 L 8960,9228 L 8873,9288 L 8832,9312 L 8793,9331 L 8758,9346 L 8725,9356 L 8695,9361 L 8682,9362 L 8669,9361 L 8658,9359 L 8648,9355 L 8638,9350 L 8630,9344 z"/><path d="M 8566,9557 L 8557,9563 L 8547,9566 L 8536,9568 L 8524,9569 L 8511,9568 L 8497,9565 L 8465,9555 L 8431,9539 L 8393,9517 L 8353,9490 L 8310,9458 L 8218,9378 L 8120,9282 L 8019,9170 L 7917,9044 L 7821,8913 L 7739,8787 L 7670,8668 L 7617,8558 L 7597,8509 L 7581,8462 L 7569,8420 L 7562,8383 L 7561,8350 L 7562,8336 L 7564,8323 L 7567,8311 L 7572,8301 L 7578,8292 L 7586,8285 L 7595,8279 L 7605,8276 L 7616,8274 L 7628,8273 L 7641,8274 L 7655,8277 L 7687,8287 L 7721,8303 L 7759,8325 L 7799,8352 L 7842,8385 L 7934,8464 L 8032,8561 L 8133,8673 L 8235,8799 L 8330,8930 L 8413,9056 L 8482,9175 L 8535,9285 L 8555,9334 L 8571,9380 L 8583,9422 L 8589,9460 L 8591,9492 L 8590,9507 L 8588,9520 L 8585,9531 L 8580,9541 L 8574,9550 L 8566,9557 z"/><path d="M 6578,11626 L 6575,11627 L 6572,11627 L 6569,11627 L 6565,11626 L 6561,11624 L 6557,11622 L 6549,11616 L 6540,11608 L 6531,11598 L 6521,11586 L 6510,11573 L 6489,11541 L 6467,11503 L 6445,11460 L 6424,11413 L 6405,11365 L 6390,11319 L 6379,11277 L 6371,11239 L 6369,11222 L 6367,11207 L 6367,11193 L 6367,11181 L 6369,11171 L 6370,11166 L 6372,11163 L 6374,11159 L 6376,11157 L 6378,11155 L 6381,11153 L 6384,11152 L 6387,11152 L 6391,11152 L 6394,11153 L 6398,11155 L 6402,11157 L 6411,11163 L 6420,11171 L 6429,11181 L 6439,11193 L 6449,11206 L 6471,11238 L 6493,11276 L 6514,11319 L 6535,11366 L 6554,11414 L 6569,11460 L 6581,11502 L 6588,11540 L 6591,11557 L 6592,11572 L 6593,11586 L 6592,11598 L 6590,11608 L 6589,11613 L 6587,11616 L 6585,11620 L 6583,11622 L 6581,11624 L 6578,11626 z"/><path d="M 5952,11673 L 5953,11670 L 5955,11667 L 5957,11665 L 5960,11663 L 5963,11660 L 5967,11658 L 5977,11655 L 5989,11652 L 6003,11651 L 6037,11649 L 6077,11651 L 6122,11655 L 6172,11663 L 6224,11674 L 6276,11687 L 6323,11701 L 6366,11717 L 6402,11733 L 6432,11748 L 6444,11756 L 6454,11764 L 6461,11771 L 6464,11775 L 6466,11778 L 6468,11781 L 6469,11785 L 6469,11788 L 6469,11791 L 6468,11794 L 6466,11797 L 6464,11799 L 6461,11802 L 6458,11804 L 6454,11806 L 6444,11809 L 6432,11812 L 6418,11814 L 6384,11815 L 6344,11814 L 6299,11809 L 6249,11802 L 6197,11791 L 6145,11778 L 6098,11763 L 6055,11748 L 6019,11732 L 5989,11716 L 5977,11708 L 5967,11701 L 5960,11693 L 5957,11690 L 5955,11686 L 5953,11683 L 5952,11679 L 5952,11676 L 5952,11673 z"/><path d="M 5384,7616 L 5381,7618 L 5378,7620 L 5375,7622 L 5371,7623 L 5367,7624 L 5362,7624 L 5352,7623 L 5340,7621 L 5327,7617 L 5313,7612 L 5298,7605 L 5266,7587 L 5231,7564 L 5194,7537 L 5157,7505 L 5121,7471 L 5090,7438 L 5063,7406 L 5042,7376 L 5034,7362 L 5027,7348 L 5022,7336 L 5018,7325 L 5016,7314 L 5016,7310 L 5016,7306 L 5017,7302 L 5018,7298 L 5020,7295 L 5022,7292 L 5025,7290 L 5028,7287 L 5031,7286 L 5035,7285 L 5039,7284 L 5044,7284 L 5054,7284 L 5066,7287 L 5078,7291 L 5092,7296 L 5107,7303 L 5140,7320 L 5175,7343 L 5212,7371 L 5249,7402 L 5285,7436 L 5316,7469 L 5342,7502 L 5363,7532 L 5372,7546 L 5379,7559 L 5384,7572 L 5388,7583 L 5389,7593 L 5390,7598 L 5389,7602 L 5389,7606 L 5388,7610 L 5386,7613 L 5384,7616 z"/><path d="M 5502,7547 L 5499,7548 L 5496,7548 L 5493,7548 L 5489,7547 L 5485,7545 L 5481,7543 L 5473,7537 L 5464,7529 L 5455,7519 L 5445,7507 L 5434,7494 L 5413,7462 L 5391,7424 L 5369,7381 L 5348,7334 L 5329,7286 L 5314,7240 L 5303,7198 L 5295,7160 L 5293,7143 L 5291,7128 L 5291,7114 L 5291,7102 L 5293,7092 L 5294,7087 L 5296,7084 L 5298,7080 L 5300,7078 L 5302,7076 L 5305,7074 L 5308,7073 L 5311,7073 L 5315,7073 L 5318,7074 L 5322,7076 L 5326,7078 L 5335,7084 L 5344,7092 L 5353,7102 L 5363,7114 L 5373,7127 L 5395,7159 L 5417,7197 L 5438,7240 L 5459,7287 L 5478,7335 L 5493,7381 L 5505,7423 L 5512,7461 L 5515,7478 L 5516,7493 L 5517,7507 L 5516,7519 L 5514,7529 L 5513,7534 L 5511,7537 L 5509,7541 L 5507,7543 L 5505,7545 L 5502,7547 z"/><path d="M 4875,7594 L 4876,7591 L 4878,7588 L 4880,7586 L 4883,7584 L 4886,7581 L 4890,7579 L 4900,7576 L 4912,7573 L 4926,7572 L 4960,7570 L 5000,7572 L 5045,7576 L 5095,7584 L 5147,7594 L 5199,7607 L 5246,7622 L 5289,7637 L 5325,7653 L 5355,7669 L 5367,7677 L 5377,7684 L 5384,7692 L 5387,7695 L 5389,7699 L 5391,7702 L 5392,7706 L 5392,7709 L 5392,7712 L 5391,7715 L 5389,7718 L 5387,7720 L 5384,7722 L 5381,7725 L 5377,7727 L 5367,7730 L 5355,7733 L 5341,7734 L 5307,7736 L 5267,7734 L 5222,7730 L 5172,7722 L 5120,7711 L 5068,7698 L 5021,7684 L 4978,7668 L 4942,7653 L 4912,7637 L 4900,7629 L 4890,7621 L 4883,7614 L 4880,7610 L 4878,7607 L 4876,7604 L 4875,7600 L 4875,7597 L 4875,7594 z"/><path d="M 9763,8248 L 9761,8245 L 9759,8242 L 9758,8238 L 9758,8234 L 9757,8230 L 9758,8226 L 9759,8215 L 9763,8204 L 9768,8192 L 9775,8178 L 9784,8164 L 9805,8134 L 9831,8102 L 9862,8069 L 9897,8035 L 9934,8004 L 9970,7976 L 10005,7953 L 10037,7936 L 10052,7929 L 10066,7923 L 10079,7919 L 10090,7917 L 10100,7916 L 10105,7916 L 10109,7917 L 10113,7918 L 10116,7920 L 10119,7922 L 10122,7924 L 10124,7927 L 10126,7930 L 10127,7933 L 10127,7937 L 10128,7941 L 10127,7946 L 10126,7956 L 10122,7967 L 10117,7979 L 10110,7993 L 10102,8007 L 10080,8037 L 10054,8069 L 10023,8102 L 9988,8136 L 9951,8167 L 9914,8195 L 9880,8218 L 9847,8236 L 9833,8242 L 9819,8248 L 9806,8252 L 9794,8254 L 9784,8255 L 9780,8255 L 9775,8255 L 9772,8254 L 9768,8252 L 9765,8250 L 9763,8248 z"/><path d="M 9639,8179 L 9636,8177 L 9634,8175 L 9632,8173 L 9630,8169 L 9628,8166 L 9627,8161 L 9625,8151 L 9624,8139 L 9625,8126 L 9626,8110 L 9629,8093 L 9636,8056 L 9648,8013 L 9663,7968 L 9682,7920 L 9703,7873 L 9725,7830 L 9747,7792 L 9768,7760 L 9779,7746 L 9789,7734 L 9798,7724 L 9807,7717 L 9815,7711 L 9819,7709 L 9823,7707 L 9827,7706 L 9830,7706 L 9833,7706 L 9836,7707 L 9839,7708 L 9841,7710 L 9843,7713 L 9845,7716 L 9847,7720 L 9848,7724 L 9850,7734 L 9851,7746 L 9850,7760 L 9849,7775 L 9846,7792 L 9839,7830 L 9827,7872 L 9812,7918 L 9793,7966 L 9772,8013 L 9750,8056 L 9728,8094 L 9707,8126 L 9697,8140 L 9687,8152 L 9677,8162 L 9668,8169 L 9660,8175 L 9656,8177 L 9652,8179 L 9648,8180 L 9645,8180 L 9642,8180 L 9639,8179 z"/><path d="M 10269,8228 L 10269,8231 L 10269,8234 L 10268,8237 L 10267,8241 L 10264,8244 L 10262,8248 L 10254,8255 L 10244,8263 L 10232,8270 L 10203,8286 L 10166,8302 L 10123,8317 L 10076,8331 L 10024,8344 L 9972,8355 L 9922,8362 L 9877,8367 L 9837,8368 L 9803,8367 L 9789,8365 L 9777,8362 L 9767,8359 L 9763,8357 L 9760,8355 L 9757,8352 L 9755,8350 L 9753,8347 L 9752,8344 L 9752,8341 L 9752,8338 L 9753,8334 L 9755,8331 L 9757,8327 L 9760,8324 L 9767,8316 L 9777,8309 L 9789,8301 L 9803,8293 L 9819,8285 L 9855,8269 L 9898,8254 L 9946,8240 L 9998,8227 L 10050,8217 L 10099,8209 L 10144,8205 L 10184,8204 L 10218,8205 L 10232,8207 L 10244,8210 L 10254,8213 L 10258,8215 L 10261,8217 L 10264,8220 L 10266,8222 L 10268,8225 L 10269,8228 z"/><path d="M 9749,10085 L 9746,10087 L 9743,10089 L 9740,10091 L 9736,10092 L 9732,10093 L 9727,10093 L 9717,10092 L 9705,10090 L 9693,10086 L 9679,10081 L 9664,10074 L 9631,10056 L 9597,10034 L 9560,10006 L 9523,9975 L 9488,9941 L 9457,9908 L 9430,9876 L 9409,9846 L 9401,9832 L 9394,9819 L 9388,9806 L 9385,9795 L 9383,9785 L 9382,9780 L 9383,9776 L 9383,9772 L 9384,9768 L 9386,9765 L 9388,9762 L 9391,9760 L 9394,9758 L 9397,9756 L 9401,9755 L 9405,9754 L 9410,9754 L 9421,9755 L 9432,9757 L 9445,9761 L 9459,9767 L 9474,9774 L 9507,9791 L 9541,9814 L 9578,9841 L 9615,9872 L 9650,9906 L 9681,9939 L 9707,9971 L 9729,10001 L 9737,10015 L 9744,10029 L 9749,10041 L 9753,10052 L 9754,10063 L 9755,10067 L 9754,10071 L 9754,10075 L 9753,10079 L 9751,10082 L 9749,10085 z"/><path d="M 9868,10017 L 9865,10018 L 9862,10018 L 9859,10018 L 9855,10017 L 9852,10015 L 9848,10013 L 9840,10007 L 9831,9999 L 9821,9989 L 9801,9963 L 9780,9931 L 9758,9893 L 9736,9850 L 9715,9803 L 9696,9755 L 9681,9710 L 9670,9667 L 9662,9630 L 9658,9597 L 9657,9584 L 9658,9572 L 9660,9562 L 9661,9557 L 9662,9554 L 9664,9550 L 9666,9548 L 9668,9546 L 9671,9544 L 9674,9543 L 9677,9543 L 9680,9543 L 9684,9544 L 9688,9546 L 9692,9548 L 9700,9554 L 9709,9562 L 9718,9572 L 9728,9584 L 9739,9598 L 9760,9630 L 9782,9668 L 9803,9711 L 9824,9758 L 9843,9806 L 9858,9851 L 9870,9894 L 9878,9931 L 9880,9948 L 9882,9964 L 9882,9977 L 9882,9989 L 9880,9999 L 9879,10004 L 9877,10007 L 9875,10011 L 9873,10013 L 9871,10015 L 9868,10017 z"/><path d="M 9242,10063 L 9243,10060 L 9245,10057 L 9247,10055 L 9250,10053 L 9253,10050 L 9257,10048 L 9267,10045 L 9279,10042 L 9293,10041 L 9327,10039 L 9367,10041 L 9412,10045 L 9462,10053 L 9514,10064 L 9566,10077 L 9613,10091 L 9656,10107 L 9692,10123 L 9722,10138 L 9734,10146 L 9744,10154 L 9751,10161 L 9754,10165 L 9756,10168 L 9758,10171 L 9759,10175 L 9759,10178 L 9759,10181 L 9758,10184 L 9756,10187 L 9754,10189 L 9751,10192 L 9748,10194 L 9744,10196 L 9734,10199 L 9722,10202 L 9708,10204 L 9674,10205 L 9634,10204 L 9589,10199 L 9539,10192 L 9487,10181 L 9435,10168 L 9388,10153 L 9345,10138 L 9309,10122 L 9279,10106 L 9267,10098 L 9257,10091 L 9250,10083 L 9247,10080 L 9245,10076 L 9243,10073 L 9242,10069 L 9242,10066 L 9242,10063 z"/><path d="M 6841,4401 L 6832,4382 L 6827,4362 L 6826,4339 L 6828,4314 L 6834,4288 L 6843,4260 L 6872,4199 L 6914,4133 L 6968,4061 L 7035,3985 L 7112,3904 L 7298,3734 L 7521,3555 L 7777,3372 L 8060,3190 L 8352,3022 L 8632,2879 L 8894,2763 L 9130,2676 L 9237,2644 L 9336,2621 L 9424,2606 L 9503,2599 L 9570,2601 L 9599,2606 L 9625,2613 L 9648,2622 L 9667,2633 L 9683,2648 L 9696,2664 L 9705,2683 L 9710,2703 L 9711,2726 L 9709,2751 L 9703,2777 L 9694,2805 L 9665,2866 L 9623,2932 L 9569,3004 L 9502,3080 L 9425,3161 L 9239,3331 L 9016,3510 L 8760,3693 L 8477,3875 L 8185,4043 L 7905,4186 L 7643,4302 L 7407,4389 L 7300,4420 L 7201,4444 L 7113,4459 L 7034,4466 L 6967,4464 L 6938,4459 L 6912,4452 L 6889,4443 L 6870,4432 L 6854,4417 L 6841,4401 z"/><path d="M 9098,6041 L 9075,6069 L 9049,6094 L 9020,6117 L 8989,6137 L 8956,6155 L 8920,6170 L 8843,6193 L 8757,6206 L 8665,6209 L 8567,6203 L 8463,6188 L 8354,6163 L 8242,6129 L 8127,6087 L 8009,6035 L 7890,5975 L 7770,5907 L 7651,5830 L 7532,5745 L 7418,5654 L 7311,5560 L 7212,5464 L 7122,5366 L 7040,5267 L 6968,5168 L 6904,5069 L 6851,4972 L 6808,4876 L 6775,4783 L 6753,4694 L 6742,4608 L 6743,4527 L 6748,4489 L 6755,4452 L 6766,4417 L 6780,4383 L 6798,4351 L 6818,4321 L 6841,4293 L 6867,4268 L 6896,4245 L 6927,4225 L 6960,4207 L 6996,4192 L 7073,4169 L 7159,4156 L 7251,4152 L 7349,4158 L 7453,4174 L 7562,4199 L 7674,4232 L 7789,4275 L 7907,4326 L 8026,4387 L 8146,4455 L 8265,4532 L 8384,4617 L 8498,4708 L 8605,4802 L 8704,4898 L 8794,4996 L 8876,5095 L 8948,5194 L 9012,5293 L 9065,5390 L 9108,5486 L 9141,5579 L 9163,5668 L 9174,5754 L 9173,5835 L 9168,5873 L 9161,5910 L 9150,5945 L 9136,5979 L 9118,6011 L 9098,6041 z"/><path d="M 7879,5222 L 7868,5212 L 7858,5199 L 7851,5184 L 7845,5167 L 7841,5147 L 7839,5126 L 7841,5077 L 7850,5020 L 7866,4957 L 7888,4888 L 7917,4812 L 7992,4647 L 8091,4466 L 8210,4274 L 8348,4075 L 8496,3883 L 8643,3712 L 8786,3563 L 8921,3442 L 8984,3392 L 9044,3349 L 9099,3316 L 9150,3290 L 9196,3274 L 9217,3269 L 9237,3267 L 9255,3267 L 9272,3270 L 9287,3275 L 9300,3283 L 9311,3293 L 9321,3306 L 9329,3321 L 9334,3338 L 9338,3358 L 9340,3379 L 9338,3428 L 9329,3484 L 9314,3547 L 9291,3617 L 9263,3692 L 9187,3858 L 9089,4039 L 8970,4231 L 8832,4430 L 8684,4621 L 8536,4793 L 8393,4941 L 8258,5063 L 8195,5113 L 8136,5155 L 8080,5189 L 8029,5214 L 7983,5231 L 7962,5235 L 7942,5238 L 7924,5238 L 7907,5235 L 7892,5230 L 7879,5222 z"/><path d="M 9004,6100 L 8984,6094 L 8965,6084 L 8948,6069 L 8931,6050 L 8915,6027 L 8901,6001 L 8875,5936 L 8854,5857 L 8837,5764 L 8825,5660 L 8818,5543 L 8817,5279 L 8834,4980 L 8871,4652 L 8927,4303 L 8999,3957 L 9082,3638 L 9173,3352 L 9269,3106 L 9317,2999 L 9366,2906 L 9415,2825 L 9463,2759 L 9510,2708 L 9533,2688 L 9556,2672 L 9578,2661 L 9600,2653 L 9621,2651 L 9642,2652 L 9662,2658 L 9681,2668 L 9699,2683 L 9715,2702 L 9731,2725 L 9746,2752 L 9771,2816 L 9793,2895 L 9809,2988 L 9821,3093 L 9829,3209 L 9830,3473 L 9812,3773 L 9775,4100 L 9719,4449 L 9647,4795 L 9564,5114 L 9473,5400 L 9377,5647 L 9329,5753 L 9280,5846 L 9231,5927 L 9183,5993 L 9136,6044 L 9113,6064 L 9090,6080 L 9068,6091 L 9046,6099 L 9025,6101 L 9004,6100 z"/></g></g></svg>

static/images/ok.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='green'/></svg>
static/images/bad.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='red'/></svg>
static/images/off.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='black'/></svg>
static/images/yellow.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width='32' height='32' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='yellow'/></svg>
static/images/fork.svg<?xml version="1.0" encoding="utf-8"?>
<!-- Origin: https://seekicon.com/free-icon/fork_4 2022-07-01 License: SIL (OFL) -->
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><path d="M18,399.3h79.4V326c0-23.2,11.3-48.8,33-70.3C152,234.2,184.2,210.9,237,174c53.1-37.2,85.1-57.8,99.6-70.7 c14.4-12.9,13.2-10.8,13.2-29.6V0.9V0h63.1v0.9v72.7c0,28.2-11.8,56.6-34.2,76.6c-22.4,20-53.4,39-105.5,75.4 c-52.3,36.6-83.3,59.9-98.4,74.9c-15.1,15-14.3,15.6-14.3,25.5v73.4h82.8L130.7,512L18,399.3z M268.7,399.3h81.1V326 c0-9.8,0.8-10.5-14.3-25.5c-10-9.9-27.8-24-53.2-42.6c3.2-2.3,5.7-4.1,9.1-6.4c18.6-13,32.1-22,46-31.5 c17.7,13.4,31.5,24.8,42.5,35.7c21.7,21.4,33,47.1,33,70.3v73.4H494L381.4,512L268.7,399.3z"/></g></svg>