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 5c2d6c24ac6f1a49 To 00532db4911111ae

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

Deleted 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
11
1
2
3

4
5
6
7
8
9
10



-







#!/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
11
1
2
3

4
5
6
7
8
9
10



-







#!/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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41







+








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

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

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

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

Added 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-project {
	input project {
	    The project to operate on.
    common .optional-mirror-set {
	input mirror-set {
	    The mirror set to operate on.
	} { optional
	    validate [m::cmdr::vt project]
	    generate [m::cmdr::call glue gen_current_project]
	    validate [m::cmdr::vt mset]
	    generate [m::cmdr::call glue gen_current_mset]
	}
    }

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

    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 repositories processed per
		Query/change the number of mirror sets processed per
		update cycle.
	    }
	    input take {
		New number of projects to process in one update.
		New number of mirror sets 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
496
497
498
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







-
+



-
-
-
















-
+










-
-
+
+



-
+

-
+






-
-
+
+

-
+

-
+

-
+




-
+





-
-
-
-
+
+
+
+


















-
+







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


-
+







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

    private add {
	use .cms
	description {
	    Add repository. The new repository is placed into its own
	    project. Command tries to auto-detect vcs type if not
	    mirror set. 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 project to hold the repository.
	    Name for the mirror set 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 project, or
	    the project indicated by the current repository.
	    Change the name of the specified mirror set, or the mirror
	    set indicated by the current repository.

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

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

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

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

    private split {
	use .cms
	description {
	    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.
	    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.

	    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 projects to
	    Write the known set of repositories and mirror sets 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 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.
	    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.
	}
	option dated {
	    Add datestamp to the generated projects.
	    Add datestamp to the generated mirror sets.
	} { 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]
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
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







-
-
-
-
-
+
+
+
+
+

-
+














-
-
-
+
+
+







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

    private update {
	use .cms
	description {
	    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.
	    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.
	}
	use .list-optional-repository
	use .list-optional-mirror-set
    } [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 repositories. I.e repositories
	    waiting for an update.  Order shown is the order they are taken,
	    from the top down.
	    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.
	}
    } [m::cmdr::call glue cmd_pending]

    private issues {
	use .cms.in
	description {
	    Show list of active stores with issues.
577
578
579
580
581
582
583
584

585
586
587
588
589
590
591
574
575
576
577
578
579
580

581
582
583
584
585
586
587
588







-
+







	    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 project names. A search overrides
	    repository urls and mirror set 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 {
642
643
644
645
646
647
648
649

650
651
652
653
654
655
656
639
640
641
642
643
644
645

646
647
648
649
650
651
652
653







-
+







	    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 project to hold the submitted repository.
		Name for the future mirror set to hold the submitted repository.
	    } {
		alias N
		validate str
		generate [m::cmdr::call glue gen_name]
	    }
	} [m::cmdr::call glue cmd_submit]

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







-
-
-















-
+







	    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 project to hold the repository.
		Name for the mirror set 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]
956
957
958
959
960
961
962
963

964
965

966
967

968
969
970
971
972
973
974
950
951
952
953
954
955
956

957
958

959
960

961
962
963
964
965
966
967
968







-
+

-
+

-
+








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

	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::project
    package require m::mset
    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_project {p} {
proc ::m::glue::gen_current_mset {p} {
    debug.m/glue {[debug caller] | }
    # Provide current as project for operation when not specified
    # Provide current as mirror set 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 project [m repo project $r]
	if {$project ne {}} {
	    debug.m/glue {[debug caller] | --> $project }
	    return $project
	set m [m repo mset $r]
	if {$m ne {}} {
	    debug.m/glue {[debug caller] | --> $m }
	    return $m
	}
    }

    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::project
    package require m::mset
    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::project
    package require m::mset
    package require m::repo

    m msg [m project spec]
    m msg [m mset 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::repo
    package require m::mset
    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 repo count-pending] pending/[m repo count] total)"
	    $t add Take         "[m state take] ([m mset count-pending] pending/[m mset 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 ? "project" : "projects"}]
    set g [expr {$n == 1 ? "mirror set" : "mirror sets"}]
    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
645
646
647
648
649
650
651
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







-
+














-
+






+

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

-
+
-
-
-
+
+
+

-
+
-
-
-
+
+
+

-
-
-


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







	}
    }] show
    OK
}

proc ::m::glue::cmd_add {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    package require m::mset
    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::project
    package require m::mset
    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
        # -> project: project id
        # -> name   : project name
        # -> store  : id of backing store for repo
	#    mset	: mirror set id
	#    name	: mirror set name
	#    store      : store id, of backing store for the repo

	m msg "Removing $vcode repository [color note $url] ..."
	m repo remove $repo
	m msg "from Project [color note $name]"
	
	m repo remove $repo

	# TODO MAYBE: stuff how much of the cascading remove logic
	# TODO MAYBE: into `repo remove` ?

	set siblings [m store remotes $store]
	# Remove store for the repo's vcs if no repositories for that
	set nsiblings [llength $siblings]
	if {!$nsiblings} {
	    m msg "- Removing unshared $vcode store $store ..."
	# vcs remain in the mirror set.
	if {![m mset has-vcs $mset $vcs]} {
	    m msg "- Removing $vcode 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 project if no repositories remain at all.
	# Remove mirror set if no repositories remain at all.
	set nsiblings [m project size $project]
	
	if {![m mset size $mset]} {
	if {!$nsiblings} {
	    m msg "- Removing now empty project ..."
	    m project remove $project
	    m msg "- Removing mirror set [color note $name] ..."
	    m mset remove $mset
	} 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
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
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







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

-
+






-
+


-
+



+

-


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


-
+
-
-
-
-

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



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








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

-
+

-
-
-
-
+
+
+
+

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


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






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




-
+















-
-
-
-
+
+
+
+


-
-
-
-
-
+
+
+
+
+
+










-
+



-
-
-
+
+
+







-
+





-
+









-
+






-
+







-
+




-
+


-
+











-
+









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





-
+

-
+

-
+



-
-
+
+


-
+

-
+

-
+

-
+




-
+






-
+







	    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} { ;# XXX REWORK due the project/repo/store relation changes
proc ::m::glue::cmd_details {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    package require m::mset
    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
	# -> 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
	#    vcs	: vcs id
	#    vcode	: vcs code
	# *  mset	: mirror set id
	# *  name	: mirror set name
	# *  store      : store id, of backing store for the repo

	#    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

	# Get pieces ...
	
	set spent [StatsTime $min_sec $max_sec $win_sec]
	
	lassign [m store remotes $store] remotes plugin
	lappend r Remotes $remotes
	if {[llength $plugin]} {
	    lappend r {*}$plugin
	}
	# 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
	#  vcsname
	#  created
	#  changed
	#  updated
	# -> size, sizep
	#    commits, commitp
	#    vcs
	#    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]

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

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

	    if {!$full} {
		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}] }
		
		$s add Operation $nllines
		if {$stderr ne {}} {
		    $s add "Notes & Errors" [color bad $nelines]
		} else {
		    $s add "Notes & Errors" $nelines
	    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 {
			} else {
		if {$stdout ne {}} { $s add Operation        [L $stdout] }
		if {$stderr ne {}} { $s add "Notes & Errors" [L $stderr] }
	    }
	}] show return]
	
			    set a "off "
			}
	[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 $id [L "$a$url"]
	    $t add VCS           $vcsname

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

		unset -nocomplain id
	    # 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
		incr active -1
		$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 {} }
	    if {!$full} {
		incr sibs

		# 
		if {$sibs > $threshold} continue
		$t add ${sibs}. [Short $sibling]
	    }
		set nelines #[llength [split $stderr \n]]
		set nllines #[llength [split $stdout \n]]

		$t add Operation $nllines
		if {$stderr ne {}} {
		    $t add "Notes & Errors" [color bad $nelines]
		} else {
	    if {$sibs > $threshold} {
		$t add {} "(+[expr {$sibs - $threshold}] more)"
	    }

		    $t add "Notes & Errors" $nelines
		}
	    } else {
		if {$stdout ne {}} { $t add Operation        [L $stdout] }
		if {$stderr ne {}} { $t add "Notes & Errors" [L $stderr] }
	    }
	}] show
    }
    OK
}

proc ::m::glue::SI {stderr} {
    SIB [expr {$stderr eq {}}]
}

    if {$stderr eq {}} {
	return [color good OK]
    } else {
	set status images/bad.svg
	return [color bad ATTEND]
    }
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::project
    package require m::mset
    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
	    #    project: project id
	    #    name	: project name
	    #    store  : id of backing store for repo

	    #    mset	: mirror set id
	    #    name	: mirror set name
	    #    store  : store id, of backing store for the repo
	    
	    m repo enable $repo $flag

	    # 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.
	    # 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
	    # remotes.
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}

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

    m db transaction {
	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]
	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]

	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 project has $newname]} {
	if {[m mset has $newname]} {
	    m::cmdr::error \
		"New name [color note $newname] already present" \
		HAVE_ALREADY NAME
	}

	Rename $project $newname
	Rename $mset $newname
    }

    ShowCurrent $config
    SiteRegen
    OK
}

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

    m db transaction {
	set msets [Dedup [MergeFill [$config @projects]]]
	set msets [Dedup [MergeFill [$config @mirror-sets]]]
	# __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 project." \
		"All repositories are already in the same mirror set." \
		NOP
	}

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

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

    ShowCurrent $config
    SiteRegen
    OK
}

proc ::m::glue::cmd_split {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    package require m::mset
    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
	#    project: project id
	#    name   : project name
	#    store  : id of backing store for repo
	# -> 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

	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 "  Project    [color note $name]"
	m msg "  Mirror set [color note $name]"

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

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

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

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

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

	    m msg "  Move store ..."

	    m store move $store $projectnew
	    m store move $store $msetnew
	} 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 $projectnew
	    m store cleave $store $msetnew
	}
    }

    ShowCurrent $config
    SiteRegen
    OK
}
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
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







-
+


-



-
+


-
+
-
-
+

-
-
+
+
-
-
-
+

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

-
-
-
-
+
+
+
+
+
+

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













-
-
-



-
-
-




-
-
+
+
-





-
+







-
-
-
+



-
-
+
+













-
+





-
-
-
+
+
+



-
+
-
-
-

-
+


-
+

-
-




-
-
+
+



-
+










-
+






-
+

-
+


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















-
-
+
+












-
+






-
+

-
+


-
-


















-
-
+
+








    ShowCurrent $config
    OK
}

proc ::m::glue::cmd_update {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    package require m::mset
    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 repos    [UpdateRepos $startcycle $nowcycle [$config @repositories]]
	set msets    [UpdateSets $startcycle $nowcycle [$config @mirror-sets]]

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

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

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

	    debug.m/glue {stores = ($stores)}
	    set durl [color note $url]
	    if {$origin eq {}} { set durl [color bg-cyan $durl] }
	    
	    m msg "Updating repository $durl ..."
	    m msg "In project          [color note $name]"
	    if {$verbose} {
		m msg  "  [color note [string totitle $vcode]] store ... "
	    } else {

	    foreach store $stores {
		set vname [m store vcs-name $store]
		if {$verbose} {
		    m msg "  [color note $vname] store ... "
		} else {
		    m msg* "  $vname store ... "
		}
		m msg* "  [string totitle $vcode] store ... "
	    }
	    set primary [expr {$origin eq {}}]

	    # -- has_issues, is_active/enable -- fork handling

	    set now [clock seconds]

		# 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
		debug.m/glue {update = ($counts)}
		
		set    suffix ""
	    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 ", in " [color note [m format interval $spent]]
		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 {!$ok} {
		lassign [m vcs caps $store] _ e
		m msg "[color bad Fail]$suffix"
		m msg $e
		if {$before < 0} {
		    # 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

		continue		
	    } 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"
	    }
		} 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"
		}

	    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 url origin
	    # store mname vcode changed updated created size active remote
	    # sizep commits commitp mins maxs lastn

	    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 $url $vcode $dsize $dcommit $lastn $changed $updated $created]
	    lappend series [list $mname $vcode $dsize $dcommit $lastn $changed $updated $created]
	}
    }
    lassign [TruncW \
		 {Project VCS Size Commits Time Changed Updated Created} \
		 {1       0   0    0       0    0       0       0} \
		 {{Mirror Set} 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::project
    package require m::mset
    package require m::state

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

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

    set nmset    [m mset count]
    set npending [m mset 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 row *
		lappend series [list * $mname $numrepo]
		incr take -1
	    } else {
		lappend row {}
		lappend series [list {} $mname $numrepo]
	    }
	    lappend row $url $nforks $pname
	    lappend series $row
	}
    }

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

    puts @[color note $npending]/$nrepo
    puts @[color note $npending]/$nmset
    [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::project
    package require m::mset
    package require m::repo
    package require m::rolodex
    package require m::store

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

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

	    lappend series [list $rid $url $mname $vcode $size]
	    m rolodex push $rid
	}

	    set urls [lindex [m store remotes $store] 0]
		
	    foreach url $urls {
		set rid [m repo id $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 Project VCS Size} \
		 {0   0          1       0   0} \
		 {Tag Repository Set VCS Size} \
		 {0 1 3 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::project
    package require m::mset
    package require m::repo
    package require m::rolodex
    package require m::store

    m db transaction {
	set series {}
	foreach row [m store disabled] {	# XXX REWORK actually repo state
	foreach row [m store disabled] {
	    dict with row {}
	    # store mname vcode changed updated created size active remote attend rid url origin
	    # store mname vcode changed updated created size active remote attend rid url
	    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 Project VCS Size} \
		 {0   0          1       0   0} \
		 {Tag Repository Set VCS Size} \
		 {0 1 3 0 0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 1 note] ;# 1 => url
	}
    }] show
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
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







-
-
-
-
-
-


-
+














-
+








-
+




-
+









-
-



-
+








-
-
+
+







	    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 project
		unset name url vcs vcode store ri
	    } 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 (primary name url rid vcode sizekb active sizep commits commitp mins maxs lastn))
	# series = list (dict (mset 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 {}
	    # primary name url id vcode sizekb active sizep commits commitp mins maxs lastn
	    # 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 Project VCS Size Commits Time} \
		 {0   0  0          1       0   -1   -1      0} \
		 {Tag {} Repository Set VCS Size Commits Time} \
		 {0 0 1 2 0 -1 -1 0} \
		 $table [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
1504
1505
1506
1507
1508
1509
1510
1511

1512
1513
1514
1515
1516
1517
1518
1367
1368
1369
1370
1371
1372
1373

1374
1375
1376
1377
1378
1379
1380
1381







-
+







	    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    0   0   3           0     1} \
		 {0 0 2 0 3 0 1} \
		 $series [$config @tw]] \
	titles series
    [table t $titles {
	foreach row $series {
	    $t add {*}[C $row 2 note] ;# 2 => url
	}
    }] show
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
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







-
+

















-
+







	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]
1587
1588
1589
1590
1591
1592
1593
1594

1595
1596
1597
1598
1599
1600
1601
1450
1451
1452
1453
1454
1455
1456

1457
1458
1459
1460
1461
1462
1463
1464







-
+







    }
    SiteRegen
    OK
}

proc ::m::glue::cmd_accept {config} {
    debug.m/glue {[debug caller] | }
    package require m::project
    package require m::mset
    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

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







-
+







-
-
+
-
-
-
-
-
+










-
+





-
+

-
+

-
+



-
+













-
+













-
+







	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]
    m mailer to [$config @destination] [m mail generator test]
    } on error {e o} {
	m msg [color bad $e]
	exit
    }
    OK
    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 $v $k
	    $t add $k $v
	}
    }] show
    OK
}

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

    set map [m project known]
    set map [m mset known]
    [table/d t {
	foreach k [lsort -dict [dict keys $map]] {
	    set v [dict get $map $k]
	    $t add $v $k
	    $t add $k $v
	}
    }] 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 $v $k
	    $t add $k $v
	}
    }] 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 $v $k
	    $t add $k $v
	}
    }] show
    OK
}

proc ::m::glue::cmd_debug_levels {config} {
    debug.m/glue {[debug caller] | }
1883
1884
1885
1886
1887
1888
1889
1890

1891
1892
1893
1894
1895
1896
1897
1898

1899
1900
1901
1902
1903
1904
1905
1906
1741
1742
1743
1744
1745
1746
1747

1748

1749
1750
1751
1752
1753
1754

1755

1756
1757
1758
1759
1760
1761
1762







-
+
-






-
+
-







}

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)	- old: 'M'irrorset
    # command :: list ('M' name)
    #          | 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)	- old: 'M'irrorset
    # command  :: list ('M' name)
    #           | 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 .
1920
1921
1922
1923
1924
1925
1926
1927

1928
1929
1930
1931
1932
1933
1934
1935
1776
1777
1778
1779
1780
1781
1782

1783

1784
1785
1786
1787
1788
1789
1790







-
+
-







	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 {
2016
2017
2018
2019
2020
2021
2022
2023

2024
2025
2026
2027
2028
2029
2030
1871
1872
1873
1874
1875
1876
1877

1878
1879
1880
1881
1882
1883
1884
1885







-
+








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

		lappend command $repo
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
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







-
+



-
+

-
-
+
+









-
+










-
+




-
-
-
+
+
+




-
+

-
-
-
+
+
+














-
+














-
-
-
+
+
+







    return
}

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

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

    if {[llength $repos] == 2} {
	lassign $repos vcode url
	# The project contains only a single repository.
	# The mirror set contains only a single repository.
	# We might be able to skip the merge
	if {![m project has $mname]} {
	    # No project of the given name exists.
	if {![m mset has $mname]} {
	    # No mirror set 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 project remove  $mset
		m mset 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
    # project exists. Merging is needed. And the untrusted nature
    # mirror set 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 project, like for
    #   `add`.  Project names are of the form `import_<date>`, plus a
    #   serial number.  Comes with associated store.
    # - 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.
    #
    # - 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 project and its
    #   because each round finalizes at least one mirror set and its
    #   repositories of the finite supply. At the end of this phase we
    #   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.
    #   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.

    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 project remove  $mset
	    m mset 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 project has $mname]} {
	# Targeted project exists. Make it first in the merge list.
	set mset [m project id $mname]
    if {[m mset has $mname]} {
	# Targeted mirror set exists. Make it first in the merge list.
	set mset [m mset 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]
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
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







-
-
+
+












-
+

-

-
-
+
-
-

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

-
+



















-
+






-
+



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

-
-
-
-
+
-
-
-
-
+
+

+
-
-
+
+
-
+
+

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

-
+
-
-

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







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

	if {![llength $unmatched]} break

	# Retry to merge the leftovers.  Note, each iteration
	# finalizes at least one project, ensuring termination of the
	# loop.
	# finalizes at least one mirror set, 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 project [m project add $tmpname]
    set mset    [m mset 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

    lassign [AddStoreRepo $vcs $vcode $tmpname $url $project] repo forks
    set store [m repo store $repo]

    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]]"
    # 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"]"
    }
    
    if {$forks ne {}} {
	m msg "  Github: Currently tracking [color note [llength $forks]] additional forks"
	foreach f $forks {
	    m msg "  - [color note $f]"
	}
    }
    m rolodex push $repo

    return [list $vcs $project $store]
    return [list $vcs $mset $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 "  Project    [color note $name]"
    m msg "  Mirror set [color note $name]"

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

    }
    # Relevant entities
    #  1. repository
    #  2. store
    #  3. project
    #

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

    # TODO MAYBE: stuff how much of this logic into `repo add` ?
    m msg "Actions ..."

    # ---------------------------------- Project
    if {![m project has $name]} {
	m msg* "  Setting up the project ... "
	set project [m project add $name]
    set mset [m mset add $name]
	OKx
    } else {
	m msg "  Project is known"
    }

    m rolodex push [m repo add $vcs $mset $url]

    m msg "  Setting up the $vcode store ..."
    lassign [AddStoreRepo $vcs $vcode $name $url $project] repo forks

    lassign [m store add $vcs $mset $name $url] _ spent forks
    
    # ---------------------------------- Forks
    m rolodex commit
    m msg "  [color note Done] in [color note [m format interval $spent]]"
    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"
	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 {
	foreach f $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"
	    m msg "  - [color note $f]"
	    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]
}

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







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











-
+



















-
-
-
-
-


-
+

-
+












-
+














-
+









-
-
+
+



-
+








-
+







-
+







-
+












-
+







-
+


-
+


-
+

-
+



-
-
+
+

-
+


-
-
+
+







	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
		#    project: project id
		# -> name   : project name
		#    store  : id of backing store for repo

		# -> 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
		
		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 project has $prefix]} { return $prefix }
    if {![m mset has $prefix]} { return $prefix }
    set n 1
    while {[m project has ${prefix}#$n]} { incr n }
    while {[m mset 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 Project Time Size Commits} \
		     {0       0   1       0    0    0} \
		     {Changed VCS {Mirror Set} 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 projects which where
    # Creates a mail reporting on all the mirror sets 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::UpdateRepos {start now repos} {
proc ::m::glue::UpdateSets {start now msets} {
    debug.m/glue {[debug caller] | }

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

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

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

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

proc ::m::glue::Dedup {values} {
    debug.m/glue {[debug caller] | }
    # While keeping the order
    set res {}
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
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







-
+


















-
-
+
+













-
+


-
+








-
+








-
+







}

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

    if {!$n} {
	# No project. Use the projects for current and previous
	# No mirror sets. Use the mirror sets 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 project is the merge origin. Use the project of the
	# current repository as merge target.
	# A single mirror set is the merge origin. Use the mirror set
	# 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 project rename $mset $newname
    m mset rename $mset $newname

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

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

    # Target and origin are projects
    # Target and origin are mirror sets.
    #
    # - 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 project used-vcs $origin]
    set vcss [m mset 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

2647
2648
2649
2650
2651
2652
2653
2654
2655


2656
2657
2658
2659
2660
2661
2662
2410
2411
2412
2413
2414
2415
2416


2417
2418
2419
2420
2421
2422
2423
2424
2425







-
-
+
+







	    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/project $origin $target
    m project remove $origin
    m repo move/mset $origin $target
    m mset remove    $origin
    return
}

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

    set u [m state mail-user]
2790
2791
2792
2793
2794
2795
2796
2797
2798


2799
2800
2801
2802
2803
2804
2805
2553
2554
2555
2556
2557
2558
2559


2560
2561
2562
2563
2564
2565
2566
2567
2568







-
-
+
+







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







-
+















-
+









-
+










-
+






-
+




-
+







-
+




-
+
-
-
-

-
-
-





-
+



-
+
















-
+







-
+







    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} {
	if {$w <= 0} continue
	    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 many characters behind.
    # Rounding may cause removed < over, leaving too much chracters 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 {
2943
2944
2945
2946
2947
2948
2949
2950

2951
2952
2953
2954
2955
2956
2957
2700
2701
2702
2703
2704
2705
2706

2707
2708
2709
2710
2711
2712
2713
2714







-
+







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







-
-


-
-
+
+
+

-
-
+
+
+
+
+
+



-
-
+
+







    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 [m format win $lastn]
    set n [llength $lastn]
    set lastn [split [string trim $lastn ,] ,]
    set n     [llength $lastn]

    if {!$n} { return $text }

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

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

proc ::m::glue::DeltaSizeFull {current previous} {
    append text [m format size $current]
    if {$previous != $current} {
	if {$current < $previous} {
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
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







-
+















-
+






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







-
-
+
+




















-
+




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





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
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
247
248
249
250
251
252
253

































254
255
256
257
258







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





    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

Added 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

Deleted 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
47
48
49
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







-











-
-
+
+
-
-







#         ( 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/project move/1 has get name \
	store known get-n for forks project search id count \
	add remove enable move/mset move/1 has get name \
	known get-n mset 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] | }
62
63
64
65
66
67
68
69
70


71
72
73
74
75
76
77
59
60
61
62
63
64
65


66
67
68
69
70
71
72
73
74







-
-
+
+







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

    # See also m::project::known
    # Note, different ids! repository, not project
    # See also m::mset::known
    # Note, different ids! repo, not mset
    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
    }
85
86
87
88
89
90
91
92

93
94

95
96

97
98

99
100
101
102
103
104
105
82
83
84
85
86
87
88

89
90

91
92

93
94

95
96
97
98
99
100
101
102







-
+

-
+

-
+

-
+







    }
    
    return $map
}

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

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







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


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





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

-
-
+
+



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








-
+






-
+
-


-
-
+
+

-
-
-
-
-

-
+


-
-
-
-
+
+
+
+
+












-
+
-




-
-
-
-
+
+
+
+




-
+


+
-
+

+
+
-
-
+
+







-







    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 project store url duration {origin {}}} {
proc ::m::repo::add {vcs mset url} {
    debug.m/repo {}

    set now [clock seconds]
    
    if {$origin ne {}} {
        m db eval {
	    INSERT
	    INTO   repository
	    VALUES ( NULL	-- id
    m db eval {
	INSERT
	INTO   repository
	VALUES ( NULL, :url, :vcs, :mset, 1 )
		   , :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::forks {repo} {
proc ::m::repo::mset {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 project [m db onecolumn {
	SELECT project
    set mset [m db onecolumn {
	SELECT mset
	FROM   repository
	WHERE  id = :repo
    }]
    debug.m/repo {=> ($project)}
    return $project
    debug.m/repo {=> ($mset)}
    return $mset
}

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

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)
    # - project    (id, and name)
    # - mirror set (id, and name)
    # - vcs        (id, and code)
    # - store      (id)
    # - active
    
    set details [m db eval {
	SELECT 'url'    , R.url
	,      'active' , R.is_active
	,      'active' , R.active
	,      'issues' , R.has_issues
	,      'vcs'    , R.vcs
	,      'vcode'  , V.code
	,      'project', R.project
	,      'name'   , P.name
	,      'mset'   , R.mset
	,      'name'   , M.name
	,      'store'  , S.id
	,      'min_sec', min_duration
	,      'max_sec', max_duration
	,      'win_sec', window_duration
	,      'checked', checked
	,      'origin' , fork_origin
	FROM   repository             R
	,      project                P
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	WHERE  R.id = :repo
	AND    P.id = R.project
	AND    V.id = R.vcs
	AND    S.id = R.store
	WHERE  R.id   = :repo
	AND    M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.vcs  = R.vcs
	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 P.name             AS name
	SELECT M.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.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      project                P
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  P.id   = R.project
	WHERE  M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = R.store
	ORDER BY P.name ASC
	AND    S.id   = T.store
	ORDER BY M.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 \
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
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







-
+
-




-
-
-
-
+
+
+
+




-
+


+
-
+

+
+
-
+

-
-
+
+

-
+




-







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

    set lim [expr {$n + 1}]
    set replist {}
    m db eval {
	SELECT P.name             AS name
	SELECT M.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.is_active        AS active
	,      R.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	,      R.active           AS active
	,      T.min_seconds      AS mins
	,      T.max_seconds      AS maxs
	,      T.window_seconds   AS lastn
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	FROM   repository             R
	,      project                P
	,      mirror_set             M
	,      version_control_system V
	,      store                  S
	,      store_times            T
	WHERE  P.id   = R.project
	WHERE  M.id   = R.mset
	AND    V.id   = R.vcs
	AND    S.mset = R.mset
	AND    S.vcs  = R.vcs
	AND    S.id   = R.store
	AND    S.id   = T.store
	-- cursor start clause ...
	AND ((P.name > :mname) OR
	     ((P.name = :mname) AND
	AND ((M.name > :mname) OR
	     ((M.name = :mname) AND
	      (R.url >= :uname)))
	ORDER BY P.name ASC
	ORDER BY M.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 \
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
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







-
-
-
-







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

-


-
+

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

-
-
+







-
+


-
+


-
-
-
+
+
+





-
-
-
-




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    is_active = :flag
	SET    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
	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::pending {} {
proc ::m::repo::move/mset {msetold msetnew} {
    debug.m/repo {}
    return [m db eval {
    m db eval {
	SELECT P.name        AS name
	,      R.url         AS url
	,      R.fork_origin AS origin
	,      (SELECT count (*)
		FROM repository X
	UPDATE repository
		WHERE fork_origin = R.id) AS nforks
	FROM repository R
	,    project    P
	WHERE R.project = P.id
	SET    mset = :msetnew
	WHERE  mset = :msetold
	AND   R.is_active
	ORDER BY R.ROWID
    }]
}

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

proc ::m::repo::move/1 {repo msetnew} {
    }]
    if {[llength $taken] < $take} {
	# Short read. Clear taken (fast), and refill for next
	# invokation.
	m db eval {
    debug.m/repo {}
    m db eval {
	    DELETE
	    FROM   repo_pending
	    ;
	    INSERT
	    INTO   repo_pending
	    SELECT id
	    FROM   repository
	UPDATE repository
	}

	SET    mset = :msetnew
	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 (%%)
	WHERE  id   = :repo
	}]
    }

    return $taken
    return
}

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

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

    return [m db eval {
	SELECT P.name
	SELECT M.name
	,      R.url
	FROM   repository R
	,      project    P
	WHERE  R.project = P.id
	ORDER BY P.name ASC
	,      mirror_set M
	WHERE  R.mset = M.id
	ORDER BY M.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
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







-
+

-
+
-










-
+

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

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







+
+
+
+








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

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

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

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

proc ::m::store::add {vcs name url} {
proc ::m::store::add {vcs mset name url} {
    debug.m/store {}
    
    set store [Add $vcs]
    set state [m vcs setup $store $vcs $name $url]

    set store [Add $vcs $mset]

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

    Spent   $store $spent
    Size    $store $size
    Commits $store $commits

    return [list $store $duration $commits $size $forks]
    Size    $store
    Commits $store $after
    if {$forks ne {}} {
	ForksSetNew $store [llength $forks]
    }

    return [list $store $spent $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
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
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







-
-
-
+
+
+
+

+
+
-
-
+
+

-
-
+
+
-
-
-
+

+
-
+

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







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

proc ::m::store::has-issues {store} {
    return [expr {[lindex [m vcs caps $store] 1] ne {}}]
}
proc ::m::store::update {store cycle now} {
    debug.m/store {}

    set vcs [VCS $store]

    # Get all repositories for this store (same VCS, same mirror set),
    # then feed everything to the vcs layer.
proc ::m::store::update {primary url store cycle now before} {
    debug.m/store {}

    set remotes [Remotes $store 1]

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

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

    if {!$ok} {
	Size    $store $size
	Commits $store $commits
	Times   $store $cycle $now [expr {$commits != $before}]
    }

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

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

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







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


-
+

+





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

+


-
+
-
-
-
+
+




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







}

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 {}
    set details [m db eval {
    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' , 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)
	,      'active'  , (SELECT sum          (is_active)         FROM repository R WHERE R.store = S.id)
	,      '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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      'active'  , (SELECT count (*)
		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   S.vcs   = V.id
	AND   T.store = S.id
    }]
    debug.m/store {=> ($details)}
    return $details
	AND   V.id    = S.vcs
    }
}

proc ::m::store::remotes {store} {
    debug.m/store {}
    return [Remotes $store]
    set vcs [VCS $store]
}

proc ::m::store::repos {store} {
    lappend r [Remotes            $store] ;# Database
    lappend r [m vcs remotes $vcs $store] ;# Plugin supplied
    debug.m/store {}
    return [m db eval {
    return $r
	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
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
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







-



-
-



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

-
+

+
+
+












-
+


-
-



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

-
+

-
-
-
-
-
+
+
+
+
+




-
+




-
+


-
-



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

-
+

+
-
-
-
-
+
+
+
+
+
+









-
+


-
-



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

-
+

-
-
-
+
+
+















-
+


-
-


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

-
+

-
-
-
+
+
+









-
+


-
-


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

-
+

-
-
-
+
+
+









-
+


-
-
-
+










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



-
-
+
-

-
+

-
-
-
-
-
+
+
+
+
+














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



-
-
+
-

-
+

-
-
-
-
-
+
+
+
+
+




















-
+




-
+
-

-
+


-
-









-
+
-




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






-
+




-
-







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
	,      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
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    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 {} {	;# XXX REWORK move to repo package
proc ::m::store::issues {} {
    debug.m/store {}

    # List repositories ...
    
    set series {}
    set last {}
    m db eval {
	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
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
	,      R.fork_origin AS origin
	,      R.url         AS url
	,      R.id          AS rid
	FROM repository             R
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	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
	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
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow+origin series ;# upvar column variables
	Srow series ;# upvar column variables
    }
    return $series
}

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

    # List repositories ...
    
    set series {}
    set last {}
    m db eval {
	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
	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
	FROM repository             R
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	,    repository             R
	WHERE R.store     = S.id
	AND   R.is_active = 0    -- Flag for disabled
	AND   R.project   = P.id
	AND   R.vcs       = V.id
	WHERE T.store   = S.id
	AND   R.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
	ORDER BY mname ASC
	,        vcode ASC
	,        size  ASC
    } {
	Srow+rid+url series ;# upvar column variables
    }
    return $series
}

proc ::m::store::by-name {} {	;# XXX REWORK move to repo package
proc ::m::store::by-name {} {
    debug.m/store {}

    # List stores ...
    
    set series {}
    set last {}
    m db eval {
	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
	FROM repository             R
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.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 {} {	;# XXX REWORK move to repo package
proc ::m::store::by-vcs {} {
    debug.m/store {}

    # List repositories ...
    
    set series {}
    m db eval {
	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
	FROM repository             R
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	ORDER BY vcode ASC
	,        mname ASC
	,        size  ASC
    } {
	Srow series
    }
    return $series
}

proc ::m::store::by-size {} {	;# XXX REWORK move to repo package
proc ::m::store::by-size {} {
    debug.m/store {}

    # List repositories ...
    
    set series {}
    m db eval {
	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
	FROM repository             R
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs
		AND   R.active) AS active
	FROM store_times            T
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	WHERE R.store   = S.id
	AND   R.project = P.id
	AND   R.vcs     = V.id
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	AND   S.vcs     = V.id
	ORDER BY size  DESC
	,        mname ASC
	,        vcode ASC
    } {
	Srow series
    }
    return $series
}

proc ::m::store::updates {} {	;# XXX REWORK move to repo package
proc ::m::store::updates {} {
    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
	,      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.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) 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
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	,      R.fork_origin      AS origin
	,      R.url              AS url
	FROM store_times            T
	FROM repository             R
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	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
	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
    } {
	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
	,      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.min_duration     AS mins
	,      R.max_duration     AS maxs
	,      R.window_duration  AS lastn
	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 (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) 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
	,      S.size_previous    AS sizep
	,      S.commits_current  AS commits
	,      S.commits_previous AS commitp
	,      R.fork_origin      AS origin
	,      R.url              AS url
	FROM store_times            T
	FROM repository             R
	,    store                  S
	,    project                P
	,    mirror_set             M
	,    version_control_system V
	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
	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
    } {
	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} {	;# XXX REWORK move to repo package
proc ::m::store::Srow {sv} {
    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, oring=4origin, 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 \
		remote  $remote \
		active  $active \
		attend  $attend \
		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 \
		origin  $origin
		]
    lappend series $row
    return
}

proc ::m::store::Srow+delta {sv} {	;# XXX REWORK move to repo package
proc ::m::store::Srow+delta {sv} {
    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 origin origin url url
	maxs maxs lastn lastn

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







-
+





-
+

-
+













-
+
-




-
+



















-
-
-
+
+
+
+







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

-
-
-
-
-
-
-
-
-
+


+







-
+






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







		commits $commits \
		commitp $commitp \
		]
    lappend series $row
    return
}

proc ::m::store::Srow+rid+url {sv} {	;# XXX REWORK move to repo package
proc ::m::store::Srow+rid+url {sv} {
    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 origin origin
	rid rid url url

    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}
    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 \
		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} {	;# XXX REWORK move to repo package
proc ::m::store::Sep {sv} {
    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.store = S.id
	    AND   R.is_active
	    WHERE S.id   = :store
	    AND   R.vcs  = S.vcs
	    AND   R.mset = S.mset
	    AND   R.active
	}]
    }
    
    return [m db eval {
	SELECT R.url
	FROM   repository R
	,      store      S
	WHERE S.id    = :store
	AND   R.store = S.id
	WHERE S.id   = :store
	AND   R.vcs  = S.vcs
    }]
}

	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 new} {
proc ::m::store::Size {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::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 {
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
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







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

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





+




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


-
-
+











-
+



-
-
+
+



-
-
-
-
-
-
-
-











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






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












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







	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::Add {vcs} {
proc ::m::store::Attend {store} {
    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
	)
    }

    set store [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
	,	 :now   -- created
	,	 :now   -- updated
	,	 :now   -- changed
	,	 0      -- attend
	,	 -1     -- min_seconds (+Infinity)
	,	 0      -- max_seconds
	,	 ''     -- window_seconds
	)
    }

    return [m db last_insert_rowid]
    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 {project} {
proc ::m::store::MSName {mset} {
    debug.m/store {}
    return [m db onecolumn {
	SELECT name
	FROM   project
	WHERE  id = :project
	FROM   mirror_set
	WHERE  id = :mset
    }]
}

##
# # ## ### ##### ######## ############# ######################
## 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
    } {
	InitialSize $id
	Size $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
974
975
976
977
978
979
980























981
982
983
984
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







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




	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

Added 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

Deleted 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
70
71
72
53
54
55
56
57
58
59

60
61
62


63
64
65
66
67
68
69







-



-
-








    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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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







-
+









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

-







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

# # ## ### ##### ######## ############# #####################
## Definition

namespace eval m::format {
    namespace export size epoch epoch/short interval win win-trim
    namespace export size epoch epoch/short interval
    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
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
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







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




















+
+
+
+
+
+
+







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}
	export      {Store}
	version     {}
	url-to-name {Url}
    }
    if {![dict exists $ops $operation]} {
	Usage "Unknown operation `$operation`"
    }
    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 {}}} {

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 < <= / R
    namespace export D C U T T^ I I+ > >+ X < <= /
}

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: to not lose rows in the change we count before, then count again after
    # constraint: do no lose rows. count, then count again.
    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
242
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







-
-
+










-
+






}

# # ## ### ##### ######## ############# #####################
## Helpers

proc ::m::vcs::github::ReportForks {url} {
    debug.m/vcs/github {}
    upvar 1 path path
    # for `git::Get` - TODO - redesign with proper state in the low-level code.
    upvar 1 path path ;# 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 https://github.com/$fork
	m ops client fork $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 export path revs
	move size caps remotes 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     -  project name
    # name     -  mset 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  ;# Project
    m futil write $path/%name $name  ;# Mirror set
    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
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







-
+




-
-



-
+








-
-
-
-
+
+
+
+


-
-
-
+
+
+
+
+
-

+
+
+
-
-
-
+
+
+







-
+


-
-








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







    # [x] duration

    if {!$ok} {
	# Roll back filesystem changes
	file delete -force -- $path

	# Rethrow as something more distinguished for trapping
	E $msg CHILD
	return -code error -errorcode {M VCS CHILD} $msg
    }

    dict unset state results
    dict unset state msg
    dict unset state ok
    # commits, size, forks, duration
    return $state
}

proc ::m::vcs::update {store vcs url primary} {
proc ::m::vcs::update {store vcs urls} {
    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 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.
    # 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.

    m futil write $path/%stderr ""
    m futil write $path/%stdout "Verifying url ...\n"
    debug.m/vcs {Verifying $url ...}
    set ok [m url ok $url xr]
    m futil write $path/%stdout "Verifying urls ...\n"
    set failed 0
    foreach u $urls {
	debug.m/vcs {Verifying $u ...}
	if {[m url ok $u xr]} continue
    if {!$ok} {
	m futil append $path/%stderr "  Bad url: $u\n"
	set failed 1
    }
    if {$failed} {
	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}
	m futil append $path/%stderr "Unable to reach remotes\n"
	# Fake 'no changes', and error
	return {-1 -1 {}}
    }

    # 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 $url $primary]
	{*}[OpCmd $vcode $path $urls 0]
    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]
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
251
252
253
254
255
256
257

258
259
260
261
262
263
264
265







-
+







    # [ ] results
    # [x] msg
    # [ ] duration

    if {!$ok} {
	# Do not perform any filesystem changes.
	# Rethrow as something more distinguished for trapping
	E $msg CHILD
	return -code error -errorcode {M VCS CHILD} $msg
    }

    # ... and the store directory
    file delete -force -- $path
    return 
}

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







+
+
+




















-
+












+
+
+




















-
+







}

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 }
	E [join $issues \n] CHILD
	return
    } 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 }
	E [join $issues \n] CHILD
	return
    }
    
    # Destroy the merged store
    cleanup $origin $vcs
    return
}

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







+
+
+




















-








+
+
+
+
+
+
+
+
+














-
+











-
+

-
+











-







    file delete -force -- $pdst
    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 $vcode on $path"
	    lappend results "Failed to retrieve export script for $vcs on $path"
	}
	E [join $results \n] EXPORT
	return -errorcode {MIRROR VCS EXPORT} -code error [join $results \n]
    } 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 \
460
461
462
463
464
465
466
467

468
469
470
471
472
473
474
499
500
501
502
503
504
505

506
507
508
509
510
511
512
513







-
+








    github detect $url
    git    detect $url
    hg     detect $url
    svn    detect $url
    fossil detect $url

    E "Unable to determine vcs for $url" DETECT
    return -code error "Unable to determine vcs for $url"
}

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







+
+




















-
+







    }
    
    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 }
	E [join $issues \n] CHILD
	return
    } else {
	set name [lindex $results 0]
	debug.m/vcs {--> $name}
	return $name
    }
}

580
581
582
583
584
585
586
587

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

628
629
630
631
632
633
634
635







-
+







	    SELECT id
	    FROM   version_control_system
	    WHERE  name = :x
	}]
    }

    if {$id eq {}} {
	E "Invalid vcs code or name" INTERNAL
	return -code error "Invalid vcs code or name"
    }

    return $id
}

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

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







-
-
-
-

















-
-
-







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::project
package require m::mset
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
103
104
105
106








107
108
109
110
111
112
113
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







-
-
-
-
-
-
-
-
-
-
-







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







	! "= 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 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]
	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]

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







-
+
-
-
-
+
+





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



-
-
-
-
+
+
+
+
+
+















+
+
-
+
+

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




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


-
+

-
+

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










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







-












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


-
+





-
+






-
-
-
-
-
+
+
+
+
+

-
+





-
+









-
-
+
+






-
+
-
-




-

-
-


-
+




















-
+


-
-
+
+
-
-
-
+
+









-
-
-
-
-
-
-
-
-
-


-
+







    uplevel 1 $script
    unset dst silent
    return
}

proc ::m::web::site::Stores {} {
    debug.m/web/site {}

    foreach {mset mname} [m mset all] {
    foreach {project name} [m project all] {
	foreach store [m project stores $project] {
	    Store $project $name $store
	foreach store [m mset stores $mset] {
	    Store $mset $mname $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 {project pname store} {
proc ::m::web::site::Store {mset mname store} {
    debug.m/web/site {}

    # Get page pieces ...
    
    set urls  [m store remotes $store]
    set repos [lmap u $urls  { m repo id $u }]
    set links [lmap r $repos { RLink $r }]

    lassign [m store remotes $store] remotes plugin
    lappend r Remotes $remotes
    if {[llength $plugin]} {
	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 commits [Commits $commits $commitp]
    set dsize   [Size $size $sizep]
    set export  [ExportStore $vcs $store]
    set vcslogo [VCSLogo [m vcs code $vcs] $vcsname]
    

    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))"
    }

    set export [m vcs export $vcs $store]
    if {$export ne {}} {
	set f external/local_${store}
	WX static/$f $export
	set export [LB $f {Local Site}]
    }

    # Assemble page ...

    append text [H $pname]
    append text [H $mname]
    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
    # nprojects
    # nmsets
    # 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     Project               ]
    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     {Mirror Set} ]
    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  "Projects: " $nprojects ,
    append text  "Sets: " $nmsets ,
    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|Repository||$hvcs|$hsize|$hchan|Updated|Created|" \n
    append text "|---|---|---|---|---|---:|---|---|---|" \n
    append text "||$hname|$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   [VCSLogo $vcode $vcode]
	set vcode   "[IH 32 images/logo/${vcode}.svg $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 {$mname ne {}} {
	    set mname [LB store_${store}.html $mname]
	if {$origin ne {}} { append tag $fork }
	
	append text "|$img|$mname|$url|$tag|$vcode|$size|$changed|$updated|$created|" \n
	}
	append text "|$img|$mname|$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 project spec]
    W static/spec.txt [m mset spec]
    return
}

proc ::m::web::site::Search {} {
    debug.m/web/site {}
    WX static/search [CGI mirror-search]
    return
604
605
606
607
608
609
610
611

612
613
614
615
616
617



618

619
620
621
622
623
624
625
445
446
447
448
449
450
451

452
453
454
455
456


457
458
459
460
461
462
463
464
465
466
467
468







-
+




-
-
+
+
+

+







}

proc ::m::web::site::Sync {} {
    debug.m/web/site {}

    # Data flows
    # - Main
    #   - repo_pending			local, no sync
    #   - mset_pending			local, no sync
    #   - reply				local, no sync
    #   - rolodex			local, no sync
    #   - schema			local, no sync
    #
    #   - project			[1] join/view pushed to site
    #   - repository			[1] store_index, total replacement
    #   - mirror_set			[1] join/view pushed to site
    #   - name				[1] store_index, total replacement
    #   - repository			[1]
    #   - 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
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
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







-
+

-
-
-
-
+
+
+
+

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

+
+
+
-
+
+

-
+








-
+
+


-
+









-
+






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







    # 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
	,      (SELECT max (P.name) FROM project P, repository R WHERE P.id = R.project AND R.store = S.id) AS pname
	,      N.name    AS mname
	,      V.code    AS vcode
	,      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
	,      T.changed AS changed
	,      T.updated AS updated
	,      T.created AS created
	,      T.attend  AS attend
	,      S.size_kb AS size
	,      (SELECT count (*)         FROM repository R WHERE R.store = S.id) AS remote
	,      (SELECT sum   (is_active) FROM repository R WHERE R.store = S.id) AS active
	FROM store                  S
	,      (SELECT count (*)
		FROM  repository R
		WHERE R.mset = S.mset
		AND   R.vcs  = S.vcs) AS remote
	,      (SELECT count (*)
		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
	,    version_control_system V
	,    name                   N
	WHERE T.store   = S.id
	AND   S.mset    = M.id
	WHERE S.vcs = V.id
	AND   S.vcs     = V.id
	AND   M.name    = N.id
    } {
	# store, pname, vcode, changed, updated, created, size, remote, active, attend
	# store, mname, 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.id   = R.store
	    AND   S.vcs  = R.vcs
	    AND   S.mset = R.mset
	}]
	
	lappend remotes $pname
	lappend remotes $mname
	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,
		     :pname, :vcode, :page, :remotes, :status,
		     :mname, :vcode, :page, :remotes, :status,
		     :size, :changed, :updated, :created )
	}
    }

    # Copy the VCS information

    # Logically this ...
    if 0 {m site eval {
	DELETE FROM vcs
	;
    m site eval { 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 {
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1144
1145
1146
1147
1148
1149
1150











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